Package org.pentaho.reporting.libraries.resourceloader

Source Code of org.pentaho.reporting.libraries.resourceloader.ResourceManager

/*
* 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 - 2013 Pentaho Corporation and Contributors.  All rights reserved.
*/

package org.pentaho.reporting.libraries.resourceloader;

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;

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;

/**
* 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);
  }
}
TOP

Related Classes of org.pentaho.reporting.libraries.resourceloader.ResourceManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.