Java tutorial
/* * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software * Foundation. * * You should have received a copy of the GNU Lesser General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * This program 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. * * Copyright (c) 2006 - 2017 Hitachi Vantara and Contributors. All rights reserved. */ package org.pentaho.reporting.libraries.resourceloader; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.pentaho.reporting.libraries.base.boot.ObjectFactory; import org.pentaho.reporting.libraries.base.util.IOUtils; import org.pentaho.reporting.libraries.base.util.StringUtils; import org.pentaho.reporting.libraries.resourceloader.cache.BundleCacheResourceWrapper; import org.pentaho.reporting.libraries.resourceloader.cache.NullResourceBundleDataCache; import org.pentaho.reporting.libraries.resourceloader.cache.NullResourceDataCache; import org.pentaho.reporting.libraries.resourceloader.cache.NullResourceFactoryCache; import org.pentaho.reporting.libraries.resourceloader.cache.ResourceBundleDataCache; import org.pentaho.reporting.libraries.resourceloader.cache.ResourceBundleDataCacheEntry; import org.pentaho.reporting.libraries.resourceloader.cache.ResourceBundleDataCacheProvider; import org.pentaho.reporting.libraries.resourceloader.cache.ResourceDataCache; import org.pentaho.reporting.libraries.resourceloader.cache.ResourceDataCacheEntry; import org.pentaho.reporting.libraries.resourceloader.cache.ResourceDataCacheProvider; import org.pentaho.reporting.libraries.resourceloader.cache.ResourceFactoryCache; import org.pentaho.reporting.libraries.resourceloader.cache.ResourceFactoryCacheProvider; import org.pentaho.reporting.libraries.resourceloader.modules.cache.ehcache.EHCacheModule; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.sql.Blob; import java.sql.SQLException; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * The resource manager takes care about the loaded resources, performs caching, if needed and is the central instance * when dealing with resources. Resource loading is a two-step process. In the first step, the {@link ResourceLoader} * accesses the physical storage or network connection to read in the binary data. The loaded {@link ResourceData} * carries versioning information with it an can be cached indendently from the produced result. Once the loading is * complete, a {@link ResourceFactory} interprets the binary data and produces a Java-Object from it. * <p/> * Resources are identified by an Resource-Key and some optional loader parameters (which can be used to parametrize the * resource-factories). * * @author Thomas Morgner * @see ResourceData * @see ResourceLoader * @see ResourceFactory */ public final class ResourceManager { private static final Log logger = LogFactory.getLog(ResourceManager.class); private ResourceManagerBackend backend; public static final String BUNDLE_LOADER_PREFIX = "org.pentaho.reporting.libraries.resourceloader.bundle.loader."; public static final String LOADER_PREFIX = "org.pentaho.reporting.libraries.resourceloader.loader."; public static final String FACTORY_TYPE_PREFIX = "org.pentaho.reporting.libraries.resourceloader.factory.type."; private ResourceDataCache dataCache; private ResourceBundleDataCache bundleCache; private ResourceFactoryCache factoryCache; /** * A set that contains the class-names of all cache-modules, which could not be instantiated correctly. This set is * used to limit the number of warnings in the log to exactly one per class. */ private static final Set<Class> failedModules = new HashSet<Class>(); /** * Default Constructor. */ public ResourceManager() { this(new DefaultResourceManagerBackend()); } public ResourceManager(final ResourceManagerBackend resourceManagerBackend) { if (resourceManagerBackend == null) { throw new NullPointerException(); } this.backend = resourceManagerBackend; this.bundleCache = new NullResourceBundleDataCache(); this.dataCache = new NullResourceDataCache(); this.factoryCache = new NullResourceFactoryCache(); registerDefaults(); } public ResourceManager(final ResourceManager parent, final ResourceManagerBackend backend) { if (backend == null) { throw new NullPointerException(); } if (parent == null) { throw new NullPointerException(); } this.backend = backend; this.bundleCache = parent.getBundleCache(); this.dataCache = parent.getDataCache(); this.factoryCache = parent.getFactoryCache(); registerDefaults(); } public ResourceManagerBackend getBackend() { return backend; } /** * Creates a ResourceKey that carries no Loader-Parameters from the given object. * * @param data the key-data * @return the generated resource-key, never null. * @throws ResourceKeyCreationException if the key-creation failed. */ public ResourceKey createKey(final Object data) throws ResourceKeyCreationException { return createKey(data, null); } /** * Creates a ResourceKey that carries the given Loader-Parameters contained in the optional map. * * @param data the key-data * @param parameters an optional map of parameters. * @return the generated resource-key, never null. * @throws ResourceKeyCreationException if the key-creation failed. */ public ResourceKey createKey(final Object data, final Map<? extends ParameterKey, ? extends Object> parameters) throws ResourceKeyCreationException { return backend.createKey(data, parameters); } /** * Derives a new key from the given resource-key. Only keys for a hierarchical storage system (like file-systems or * URLs) can have derived keys. Since LibLoader 0.3.0 only hierarchical keys can be derived. For that, the deriving * path must be given as String. * <p/> * Before trying to derive the key, the system tries to interpret the path as absolute key-value. * * @param parent the parent key, must never be null * @param path the relative path, that is used to derive the key. * @return the derived key. * @throws ResourceKeyCreationException if deriving the key failed. */ public ResourceKey deriveKey(final ResourceKey parent, final String path) throws ResourceKeyCreationException { return deriveKey(parent, path, null); } /** * Derives a new key from the given resource-key. Only keys for a hierarchical storage system (like file-systems or * URLs) can have derived keys. Since LibLoader 0.3.0 only hierarchical keys can be derived. For that, the deriving * path must be given as String. * <p/> * The optional parameter-map will be applied to the derived key after the parent's parameters have been copied to the * new key. * <p/> * Before trying to derive the key, the system tries to interpret the path as absolute key-value. * * @param parent the parent key, or null to interpret the path as absolute key. * @param path the relative path, that is used to derive the key. * @param parameters a optional map containing resource-key parameters. * @return the derived key. * @throws ResourceKeyCreationException if deriving the key failed. */ public ResourceKey deriveKey(final ResourceKey parent, final String path, final Map<? extends ParameterKey, ? extends Object> parameters) throws ResourceKeyCreationException { return backend.deriveKey(parent, path, parameters); } /** * Tries to convert the resource-key into an URL. Not all resource-keys have an URL representation. This method exists * to make it easier to connect LibLoader to other resource-loading frameworks. * * @param key the resource-key * @return the URL for the key, or null if there is no such key. */ public URL toURL(final ResourceKey key) { return backend.toURL(key); } public Resource createDirectly(final Object keyValue, final Class target) throws ResourceLoadingException, ResourceCreationException, ResourceKeyCreationException { final ResourceKey key = createKey(keyValue); return create(key, null, target); } /** * Tries to find the first resource-bundle-loader that would be able to process the key. * * @param key the resource-key. * @return the resourceloader for that key, or null, if no resource-loader is able to process the key. * @throws ResourceLoadingException if an error occured. */ public synchronized ResourceBundleData loadResourceBundle(final ResourceKey key) throws ResourceLoadingException { final ResourceBundleDataCache bundleCache = getBundleCache(); final ResourceBundleDataCacheEntry cached = bundleCache.get(key); if (cached != null) { final ResourceBundleData data = cached.getData(); // check, whether it is valid. final long version = data.getVersion(this); if ((cached.getStoredVersion() < 0) || (version >= 0 && cached.getStoredVersion() == version)) { // now also make sure that the underlying data has not changed. // This may look a bit superfluous, but the repository may not provide // sensible cacheable information. // // As condition of satisfaction, try to find the first piece of data that // is in the cache and see whether it has changed. ResourceKey bundleKey = data.getBundleKey(); int counter = 1; while (bundleKey != null) { final ResourceDataCacheEntry bundleRawDataCacheEntry = getDataCache().get(bundleKey); if (bundleRawDataCacheEntry != null) { final ResourceData bundleRawData = bundleRawDataCacheEntry.getData(); if (bundleRawData != null) { if (isValidData(bundleRawDataCacheEntry, bundleRawData)) { logger.debug("Returning cached entry [" + counter + "]"); return data; } getDataCache().remove(bundleRawData); } } bundleKey = bundleKey.getParent(); counter += 1; } } bundleCache.remove(data); } final ResourceBundleData data = backend.loadResourceBundle(this, key); if (data != null && isResourceDataCacheable(data)) { bundleCache.put(this, data); } return data; } private boolean isResourceDataCacheable(final ResourceData data) { try { return data.getVersion(this) != -1; } catch (ResourceLoadingException e) { return false; } } public ResourceData load(final ResourceKey key) throws ResourceLoadingException { final ResourceBundleData bundle = loadResourceBundle(key); if (bundle != null) { logger.debug("Loaded bundle for key " + key); return bundle; } final ResourceKey parent = key.getParent(); if (parent != null) { // try to load the bundle data of the parent final ResourceBundleData parentData = loadResourceBundle(parent); if (parentData != null) { logger.debug("Loaded bundle for key (derivate) " + key); return parentData.deriveData(key); } } return loadRawData(key); } private boolean isValidData(final ResourceDataCacheEntry cached, final ResourceData data) throws ResourceLoadingException { // check, whether it is valid. if (cached.getStoredVersion() < 0) { // a non versioned entry is always valid. (Maybe this is from a Jar-URL?) return true; } final long version = data.getVersion(this); if (version < 0) { // the system is no longer able to retrieve the version information? // (but versioning information must have been available in the past) // oh, that's bad. Assume the worst and re-read the data. return false; } if (cached.getStoredVersion() == version) { return true; } else { return false; } } public synchronized ResourceData loadRawData(final ResourceKey key) throws UnrecognizedLoaderException, ResourceLoadingException { final ResourceDataCache dataCache = getDataCache(); // Alternative 3: This is a plain resource and not contained in a bundle. Load as binary data final ResourceDataCacheEntry cached = dataCache.get(key); if (cached != null) { final ResourceData data = cached.getData(); if (data != null) { if (isValidData(cached, data)) { return data; } dataCache.remove(data); } } final ResourceData data = backend.loadRawData(this, key); if (data != null && isResourceDataCacheable(data)) { dataCache.put(this, data); } return data; } public Resource create(final ResourceKey key, final ResourceKey context, final Class target) throws ResourceLoadingException, ResourceCreationException { if (target == null) { throw new NullPointerException("Target must not be null"); } if (key == null) { throw new NullPointerException("Key must not be null."); } return create(key, context, new Class[] { target }); } public Resource create(final ResourceKey key, final ResourceKey context) throws ResourceLoadingException, ResourceCreationException { return create(key, context, (Class[]) null); } public Resource create(final ResourceKey key, final ResourceKey context, final Class[] target) throws ResourceLoadingException, ResourceCreationException { if (key == null) { throw new NullPointerException(); } final ResourceFactoryCache factoryCache = getFactoryCache(); // ok, we have a handle to the data, and the data is current. // Lets check whether we also have a cached result. final Resource resource = factoryCache.get(key, target); if (resource != null) { if (backend.isResourceUnchanged(this, resource)) { // mama, look i am a good cache manager ... return resource; } else { // someone evil changed one of the dependent resources ... factoryCache.remove(resource); } } final ResourceData loadedData = load(key); final Resource newResource; if (loadedData instanceof ResourceBundleData) { final ResourceBundleData resourceBundleData = (ResourceBundleData) loadedData; final ResourceManager derivedManager = resourceBundleData.deriveManager(this); newResource = backend.create(derivedManager, resourceBundleData, context, target); if (isResourceCacheable(newResource)) { if (EHCacheModule.CACHE_MONITOR.isDebugEnabled()) { EHCacheModule.CACHE_MONITOR.debug("Storing created bundle-resource for key: " + key); } factoryCache.put(newResource); if (key != newResource.getSource()) { factoryCache.put(new BundleCacheResourceWrapper(newResource, key)); } } else { if (EHCacheModule.CACHE_MONITOR.isDebugEnabled()) { EHCacheModule.CACHE_MONITOR.debug("Created bundle-resource is not cacheable for " + key); } } } else { newResource = backend.create(this, loadedData, context, target); if (isResourceCacheable(newResource)) { if (EHCacheModule.CACHE_MONITOR.isDebugEnabled()) { EHCacheModule.CACHE_MONITOR.debug("Storing created resource for key: " + key); } factoryCache.put(newResource); } else { if (EHCacheModule.CACHE_MONITOR.isDebugEnabled()) { EHCacheModule.CACHE_MONITOR.debug("Created resource is not cacheable for " + key); } } } return newResource; } private boolean isResourceCacheable(final Resource newResource) { final ResourceKey source = newResource.getSource(); if (newResource.isTemporaryResult()) { return false; } if (newResource.getVersion(source) == -1) { return false; } final ResourceKey[] keys = newResource.getDependencies(); for (int i = 0; i < keys.length; i++) { if (newResource.getVersion(keys[i]) == -1) { return false; } } return true; } public ResourceDataCache getDataCache() { return dataCache; } public void setDataCache(final ResourceDataCache dataCache) { if (dataCache == null) { throw new NullPointerException(); } this.dataCache = dataCache; } public ResourceFactoryCache getFactoryCache() { return factoryCache; } public void setFactoryCache(final ResourceFactoryCache factoryCache) { if (factoryCache == null) { throw new NullPointerException(); } this.factoryCache = factoryCache; } public ResourceBundleDataCache getBundleCache() { return bundleCache; } public void setBundleCache(final ResourceBundleDataCache bundleCache) { if (bundleCache == null) { throw new NullPointerException(); } this.bundleCache = bundleCache; } public void registerDefaults() { // Create all known resource loaders ... registerDefaultLoaders(); // Register all known factories ... registerDefaultFactories(); // add the caches .. registerDataCache(); registerBundleDataCache(); registerFactoryCache(); } public void registerDefaultFactories() { backend.registerDefaultFactories(); } public void registerBundleDataCache() { try { final ObjectFactory objectFactory = LibLoaderBoot.getInstance().getObjectFactory(); final ResourceBundleDataCacheProvider maybeDataCacheProvider = objectFactory .get(ResourceBundleDataCacheProvider.class); final ResourceBundleDataCache cache = maybeDataCacheProvider.createBundleDataCache(); if (cache != null) { setBundleCache(cache); } } catch (Throwable e) { // ok, did not work ... synchronized (failedModules) { if (failedModules.contains(ResourceBundleDataCacheProvider.class) == false) { logger.warn("Failed to create data cache: " + e.getLocalizedMessage()); failedModules.add(ResourceBundleDataCacheProvider.class); } } } } public void registerDataCache() { try { final ObjectFactory objectFactory = LibLoaderBoot.getInstance().getObjectFactory(); final ResourceDataCacheProvider maybeDataCacheProvider = objectFactory .get(ResourceDataCacheProvider.class); final ResourceDataCache cache = maybeDataCacheProvider.createDataCache(); if (cache != null) { setDataCache(cache); } } catch (Throwable e) { // ok, did not work ... synchronized (failedModules) { if (failedModules.contains(ResourceDataCacheProvider.class) == false) { logger.warn("Failed to create data cache: " + e.getLocalizedMessage()); failedModules.add(ResourceDataCacheProvider.class); } } } } public void registerFactoryCache() { try { final ObjectFactory objectFactory = LibLoaderBoot.getInstance().getObjectFactory(); final ResourceFactoryCacheProvider maybeDataCacheProvider = objectFactory .get(ResourceFactoryCacheProvider.class); final ResourceFactoryCache cache = maybeDataCacheProvider.createFactoryCache(); if (cache != null) { setFactoryCache(cache); } } catch (Throwable e) { synchronized (failedModules) { if (failedModules.contains(ResourceFactoryCacheProvider.class) == false) { logger.warn("Failed to create factory cache: " + e.getLocalizedMessage()); failedModules.add(ResourceFactoryCacheProvider.class); } } } } public void registerDefaultLoaders() { backend.registerDefaultLoaders(); } public void registerBundleLoader(final ResourceBundleLoader loader) { if (loader == null) { throw new NullPointerException(); } backend.registerBundleLoader(loader); } public void registerLoader(final ResourceLoader loader) { if (loader == null) { throw new NullPointerException(); } backend.registerLoader(loader); } public void registerFactory(final ResourceFactory factory) { if (factory == null) { throw new NullPointerException(); } backend.registerFactory(factory); } public void shutDown() { factoryCache.shutdown(); dataCache.shutdown(); } /** * Creates a String version of the <code>ResourceKey</code> that can be deserialized with the * <code>deserialize()</code> method. * * @param bundleKey the key to the bundle containing the resource, or null if no bundle exists. * @param key the key to be serialized * @throws ResourceException indicates an error trying to serialize the key * @throws NullPointerException indicates the supplied key is <code>null</code> */ public String serialize(final ResourceKey bundleKey, final ResourceKey key) throws ResourceException { return backend.serialize(bundleKey, key); } /** * Converts a serialized version of a <code>ResourceKey</code> into an actual <code>ResourceKey</code> by locating the * proper <code>ResourceLoader</code> that can perform the deserialization. * * @param serializedKey the String serialized key to be deserialized * @return the <code>ResourceKey</code> that has been deserialized * @throws ResourceKeyCreationException indicates an error trying to create the <code>ResourceKey</code> from the * deserialized version */ public ResourceKey deserialize(final ResourceKey bundleKey, final String serializedKey) throws ResourceKeyCreationException { return backend.deserialize(bundleKey, serializedKey); } public ResourceKey createOrDeriveKey(final ResourceKey context, final Object value, final Object baseURL) throws ResourceKeyCreationException { if (value == null) { throw new ResourceKeyCreationException("Empty key is invalid"); } final ResourceKey key; if (value instanceof ResourceKey) { key = (ResourceKey) value; } else if (value instanceof Blob) { try { final Blob b = (Blob) value; final byte[] data = IOUtils.getInstance().readBlob(b); key = createKey(data); } catch (IOException ioe) { throw new ResourceKeyCreationException("Failed to load data from blob", ioe); } catch (SQLException e) { throw new ResourceKeyCreationException("Failed to load data from blob", e); } } else if (value instanceof String) { final String source = (String) value; if (StringUtils.isEmpty(source)) { throw new ResourceKeyCreationException("Empty key is invalid"); } try { if (baseURL instanceof String) { final ResourceKey baseKey = createKeyFromString(null, (String) baseURL); return createKeyFromString(baseKey, source); } else if (baseURL instanceof ResourceKey) { final ResourceKey baseKey = (ResourceKey) baseURL; return createKeyFromString(baseKey, source); } else if (baseURL != null) { // if a base-url object is given, we assume that it is indeed valid. final ResourceKey baseKey = createKey(baseURL); return createKeyFromString(baseKey, source); } } catch (ResourceException rke) { logger.debug( "Failed to resolve key via given base-url. Try to treat resource as absolute resource instead", rke); } key = createKeyFromString(context, source); } else { // URLs, Files, byte-arrays etc are treated as absolute objects key = createKey(value); } return key; } private ResourceKey createKeyFromString(final ResourceKey contextKey, final String file) throws ResourceKeyCreationException { try { if (contextKey != null) { return deriveKey(contextKey, file); } } catch (ResourceException re) { // failed to load from context logger.debug("Failed to load datasource as derived path: ", re); } try { return createKey(new URL(file)); } catch (ResourceException re) { logger.debug("Failed to load datasource as URL: ", re); } catch (MalformedURLException e) { // } try { return createKey(new File(file)); } catch (ResourceException re) { // failed to load from context logger.debug("Failed to load datasource as file: ", re); } return createKey(file); } }