net.sf.jasperreports.engine.util.JRResourcesUtil Maven / Gradle / Ivy
/*
* JasperReports - Free Java Reporting Library.
* Copyright (C) 2001 - 2023 Cloud Software Group, Inc. All rights reserved.
* http://www.jaspersoft.com
*
* Unless you have purchased a commercial license agreement from Jaspersoft,
* the following license terms apply:
*
* This program is part of JasperReports.
*
* JasperReports is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* JasperReports is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with JasperReports. If not, see .
*/
package net.sf.jasperreports.engine.util;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.nio.file.InvalidPathException;
import java.nio.file.Paths;
import java.util.List;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.function.Function;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperReportsContext;
import net.sf.jasperreports.repo.RepositoryContext;
import net.sf.jasperreports.repo.RepositoryResourceContext;
import net.sf.jasperreports.repo.RepositoryUtil;
import net.sf.jasperreports.repo.ResourceBundleResource;
import net.sf.jasperreports.repo.SimpleRepositoryContext;
/**
* Provides methods for resource resolution via class loaders or URL stream handlers.
*
* @author Lucian Chirita ([email protected])
*/
public final class JRResourcesUtil
{
private static final Log log = LogFactory.getLog(JRResourcesUtil.class);
/**
*
*/
private static final String PROPERTIES_FILE_EXTENSION = ".properties";
/**
*
*/
private static ClassLoader globalClassLoader;
/**
*
*/
private static ThreadLocalStack localClassLoaderStack = new ThreadLocalStack();
/**
* Tries to parse a String
as an URL.
*
* @param spec the String
to parse
* @param urlHandlerFactory an URL stream handler factory to use
* @return an URL if the parsing is successful
* @see #getURLHandler(String, URLStreamHandlerFactory)
*/
public static URL createURL(String spec, URLStreamHandlerFactory urlHandlerFactory)
{
URLStreamHandler handler = getURLHandler(spec, urlHandlerFactory);
URL url;
try
{
if (handler == null)
{
url = new URL(spec);
}
else
{
url = new URL(null, spec, handler);
}
}
catch (MalformedURLException e)
{
url = null;
}
return url;
}
/**
* Returns an URL stream handler for an URL specified as a String
.
*
* @param spec the String
to parse as an URL
* @param urlHandlerFact an URL stream handler factory
* @return an URL stream handler if one was found for the protocol of the URL
*/
public static URLStreamHandler getURLHandler(String spec, URLStreamHandlerFactory urlHandlerFact)
{
URLStreamHandlerFactory urlHandlerFactory = urlHandlerFact;//getURLHandlerFactory(urlHandlerFact);
URLStreamHandler handler = null;
if (urlHandlerFactory != null)
{
String protocol = getURLProtocol(spec);
if (protocol != null)
{
handler = urlHandlerFactory.createURLStreamHandler(protocol);
}
}
return handler;
}
private static String getURLProtocol(String urlSpec)
{
String protocol = null;
String spec = urlSpec.trim();
int colon = spec.indexOf(':');
if (colon > 0)
{
String proto = spec.substring(0, colon);
if (protocolValid(proto))
{
protocol = proto;
}
}
return protocol;
}
private static boolean protocolValid(String protocol)
{
int length = protocol.length();
if (length < 1)
{
return false;
}
if (!Character.isLetter(protocol.charAt(0)))
{
return false;
}
for (int i = 1; i < length; ++i)
{
char c = protocol.charAt(i);
if (!(Character.isLetterOrDigit(c) || c == '+' || c == '-' || c == '.'))
{
return false;
}
}
return true;
}
/**
* Attempts to find a file using a file resolver.
*
* @param location file name
* @param fileRes a file resolver
* @return the file, if found
* @deprecated To be removed.
*/
public static File resolveFile(String location, FileResolver fileRes)
{
FileResolver fileResolver = fileRes;//getFileResolver(fileRes);
if (fileResolver != null)
{
return fileResolver.resolveFile(location);
}
return resolveFile(null, location);
}
public static File resolveFile(RepositoryContext context, String location)
{
return resolveFile(context, location, JRResourcesUtil::defaultLocateFile);
}
public static File resolveFile(RepositoryContext context, String location, Function rootLocator)
{
File file = locateFile(context == null ? null : context.getResourceContext(), location, rootLocator);
if (file != null && file.isFile())
{
return file;
}
return null;
}
protected static File defaultLocateFile(String location)
{
File file = new File(location);
if (file.exists())
{
return file;
}
return null;
}
protected static File locateFile(RepositoryResourceContext resourceContext, String location, Function rootLocator)
{
File file = rootLocator.apply(location);
if (file != null)
{
return file;
}
if (resourceContext != null)
{
RepositoryResourceContext context = resourceContext;
while (context != null)
{
File contextDir = locateContextDirectory(context, rootLocator);
if (contextDir != null)
{
file = new File(contextDir, location);
if (file.exists())
{
if (log.isDebugEnabled())
{
log.debug("resolved location " + location + " relative to the context " + contextDir);
}
return file;
}
}
context = context.getFallbackContext();
}
}
return null;
}
protected static File locateContextDirectory(RepositoryResourceContext resourceContext, Function rootLocator)
{
String contextLocation = resourceContext.getContextLocation();
if (contextLocation != null)
{
try
{
Paths.get(contextLocation);//valid patch check
File contextDir = rootLocator.apply(contextLocation);
if (contextDir != null && contextDir.isDirectory())
{
return contextDir;
}
}
catch (InvalidPathException e)
{
if (log.isDebugEnabled())
{
log.debug("location \"" + contextLocation + "\" is not a file path: " + e);
}
}
}
return null;
}
/**
* Returns a class loader.
*
* The first not null value from the following is returned:
*
* - the value of the parameter
* - the thread local class loader
* - the global class loader
*
*
* @param clsLoader a class loader that will be returned if not null
* @return a class loader.
* @see #setGlobalClassLoader(ClassLoader)
* @see #setThreadClassLoader(ClassLoader)
*/
public static ClassLoader getClassLoader(ClassLoader clsLoader)
{
ClassLoader classLoader = clsLoader;
if (classLoader == null)
{
classLoader = getThreadClassLoader();
if (classLoader == null)
{
classLoader = globalClassLoader;
}
}
return classLoader;
}
/**
* Returns the global class loader.
*
* @return the global class loader.
* @see #setGlobalClassLoader(ClassLoader)
*/
public static ClassLoader getGlobalClassLoader()
{
return globalClassLoader;
}
/**
* Returns the thread local class loader.
*
* @return the thread local class loader.
* @see #setThreadClassLoader(ClassLoader)
*/
public static ClassLoader getThreadClassLoader()
{
return (ClassLoader) localClassLoaderStack.top();
}
/**
* Sets the thread local class loader.
*
* @param classLoader a class loader
* @see #getClassLoader(ClassLoader)
*/
public static void setThreadClassLoader(ClassLoader classLoader)
{
localClassLoaderStack.push(classLoader);
}
/**
* Resets the the thread local class loader to its previous value.
*/
public static void resetClassLoader()
{
localClassLoaderStack.pop();
}
/**
* Sets a global class loader to be used for resource resolution.
*
* @param classLoader the class loader
* @see #getClassLoader(ClassLoader)
*/
public static void setGlobalClassLoader(ClassLoader classLoader)
{
globalClassLoader = classLoader;
}
/**
* Attempts to find a resource using a class loader.
*
* The following sources are tried:
*
* - the class loader returned by {@link #getClassLoader(ClassLoader) getClassLoader(ClassLoader)}
* - the context class loader
* clazz.getClassLoader()
* clazz.getResource()
*
*
* @param location the resource name
* @param clsLoader a class loader
* @param clazz a class
* @return the resource URL if found
* @deprecated Replaced by {@link #findClassLoaderResource(String, ClassLoader)}.
*/
public static URL findClassLoaderResource(String location, ClassLoader clsLoader, Class clazz)
{
ClassLoader classLoader = getClassLoader(clsLoader);
URL url = null;
if (classLoader != null)
{
url = classLoader.getResource(location);
}
if (url == null)
{
classLoader = Thread.currentThread().getContextClassLoader();
if (classLoader != null)
{
url = classLoader.getResource(location);
}
if (url == null)
{
classLoader = clazz.getClassLoader();
if (classLoader == null)
{
url = clazz.getResource("/" + location);
}
else
{
url = classLoader.getResource(location);
}
}
}
return url;
}
/**
* Attempts to find a resource using a class loader.
*
* The following sources are tried:
*
* - the class loader returned by {@link #getClassLoader(ClassLoader) getClassLoader(ClassLoader)}
* - the context class loader
* JRLoader.class.getClassLoader()
* JRLoader.class.getResource()
*
*
* @param location the resource name
* @param clsLoader a class loader
* @return the resource URL if found
*/
public static URL findClassLoaderResource(String location, ClassLoader clsLoader)
{
ClassLoader classLoader = getClassLoader(clsLoader);
URL url = null;
if (classLoader != null)
{
url = classLoader.getResource(location);
}
if (url == null)
{
classLoader = Thread.currentThread().getContextClassLoader();
if (classLoader != null)
{
url = classLoader.getResource(location);
}
if (url == null)
{
classLoader = JRLoader.class.getClassLoader();
if (classLoader == null)
{
url = JRLoader.class.getResource("/" + location);
}
else
{
url = classLoader.getResource(location);
}
}
}
return url;
}
/**
* Loads a resource bundle for a given base name and locale.
*
*
* This methods calls {@link #loadResourceBundle(String, Locale, ClassLoader)} with a null classloader.
*
*
* @param baseName the base name
* @param locale the locale
* @return the resource bundle for the given base name and locale
*/
public static ResourceBundle loadResourceBundle(JasperReportsContext jasperReportsContext, String baseName, Locale locale)
{
return loadResourceBundle(SimpleRepositoryContext.of(jasperReportsContext), baseName, locale);
}
public static ResourceBundle loadResourceBundle(RepositoryContext repositoryContext, String baseName, Locale locale)
{
ResourceBundle resourceBundle = null;
MissingResourceException ex = null;
try
{
resourceBundle = loadResourceBundle(baseName, locale, null);
}
catch (MissingResourceException e)
{
ex = e;
}
if (resourceBundle == null)
{
CustomControl control = new CustomControl();
List locales = control.getCandidateLocales(baseName, locale);
for (Locale lc : locales)
{
String suffix = lc.toString();
suffix = (suffix.trim().length() > 0 ? "_" : "") + suffix;
ResourceBundleResource resourceBundleResource = null;
try
{
resourceBundleResource =
RepositoryUtil.getInstance(repositoryContext).getResourceFromLocation(
baseName + suffix + PROPERTIES_FILE_EXTENSION,
ResourceBundleResource.class
);
}
catch (JRException e)
{
}
if (resourceBundleResource != null)
{
resourceBundle = resourceBundleResource.getResourceBundle();
break;
}
}
}
if (resourceBundle == null)
{
throw ex;
}
return resourceBundle;
}
/**
* Loads a resource bundle for a given base name and locale.
*
*
* This methods calls {@link #loadResourceBundle(String, Locale, ClassLoader)} with a null classloader.
*
*
* @param baseName the base name
* @param locale the locale
* @return the resource bundle for the given base name and locale
*/
public static ResourceBundle loadResourceBundle(String baseName, Locale locale)
{
return loadResourceBundle(baseName, locale, null);
}
/**
* Loads a resource bundle for a given base name and locale.
*
*
* The method attempts to load the resource bundle using the following classloaders
* (and stops at the first successful attempt):
*
* - the class loader returned by {@link #getClassLoader(ClassLoader) getClassLoader(ClassLoader)}
* - the context class loader
* JRClassLoader.class.getClassLoader()
*
*
*
* @param baseName the base name
* @param locale the locale
* @param clsLoader
* @return the resource bundle for the given base name and locale
* @see ResourceBundle#getBundle(String, Locale, ClassLoader)
*/
public static ResourceBundle loadResourceBundle(String baseName, Locale locale, ClassLoader clsLoader)
{
ResourceBundle resourceBundle = null;
ClassLoader classLoader = getClassLoader(clsLoader);
if (classLoader != null)
{
try
{
resourceBundle = ResourceBundle.getBundle(baseName, locale, classLoader);
}
catch (MissingResourceException e)
{
}
}
if (resourceBundle == null)
{
classLoader = Thread.currentThread().getContextClassLoader();
if (classLoader != null)
{
try
{
resourceBundle = ResourceBundle.getBundle(baseName, locale, classLoader);
}
catch (MissingResourceException e)
{
}
}
}
if (resourceBundle == null)
{
classLoader = JRClassLoader.class.getClassLoader();
if (classLoader == null)
{
resourceBundle = ResourceBundle.getBundle(baseName, locale);
}
else
{
resourceBundle = ResourceBundle.getBundle(baseName, locale, classLoader);
}
}
return resourceBundle;
}
private JRResourcesUtil()
{
}
}
/**
*
*/
class CustomControl extends ResourceBundle.Control
{
public CustomControl()
{
}
}