org.apache.velocity.runtime.resource.ResourceManagerImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of com.liferay.portal.template.velocity
Show all versions of com.liferay.portal.template.velocity
Liferay Portal Template Velocity
The newest version!
package org.apache.velocity.runtime.resource;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import org.apache.commons.collections.ExtendedProperties;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.RuntimeServices;
import org.apache.velocity.runtime.log.Log;
import org.apache.velocity.runtime.resource.loader.ResourceLoader;
import org.apache.velocity.runtime.resource.loader.ResourceLoaderFactory;
import org.apache.velocity.util.ClassUtils;
import org.apache.velocity.util.StringUtils;
/**
* Class to manage the text resource for the Velocity Runtime.
*
* @author Will Glass-Husain
* @author Jason van Zyl
* @author Paulo Gaspar
* @author Geir Magnusson Jr.
* @author Henning P. Schmiedehausen
* @version $Id: ResourceManagerImpl.java 745760 2009-02-19 07:00:42Z nbubna $
*/
public class ResourceManagerImpl
implements ResourceManager
{
/** A template resources. */
public static final int RESOURCE_TEMPLATE = 1;
/** A static content resource. */
public static final int RESOURCE_CONTENT = 2;
/** token used to identify the loader internally. */
private static final String RESOURCE_LOADER_IDENTIFIER = "_RESOURCE_LOADER_IDENTIFIER_";
/** Object implementing ResourceCache to be our resource manager's Resource cache. */
protected ResourceCache globalCache = null;
/** The List of templateLoaders that the Runtime will use to locate the InputStream source of a template. */
protected final List resourceLoaders = new ArrayList();
/**
* This is a list of the template input stream source initializers, basically properties for a particular template stream
* source. The order in this list reflects numbering of the properties i.e.
*
* <loader-id>.resource.loader.<property> = <value>
*/
private final List sourceInitializerList = new ArrayList();
/**
* Has this Manager been initialized?
*/
private boolean isInit = false;
/** switch to turn off log notice when a resource is found for the first time. */
private boolean logWhenFound = true;
/** The internal RuntimeServices object. */
protected RuntimeServices rsvc = null;
/** Logging. */
protected Log log = null;
/**
* Initialize the ResourceManager.
*
* @param rsvc The Runtime Services object which is associated with this Resource Manager.
*
* @throws Exception
*/
public synchronized void initialize(final RuntimeServices rsvc)
throws Exception
{
if (isInit)
{
log.debug("Re-initialization of ResourceLoader attempted and ignored.");
return;
}
ResourceLoader resourceLoader = null;
this.rsvc = rsvc;
log = rsvc.getLog();
log.trace("Default ResourceManager initializing. (" + this.getClass() + ")");
assembleResourceLoaderInitializers();
for (Iterator it = sourceInitializerList.iterator(); it.hasNext();)
{
/**
* Resource loader can be loaded either via class name or be passed
* in as an instance.
*/
ExtendedProperties configuration = (ExtendedProperties) it.next();
String loaderClass = StringUtils.nullTrim(configuration.getString("class"));
ResourceLoader loaderInstance = (ResourceLoader) configuration.get("instance");
if (loaderInstance != null)
{
resourceLoader = loaderInstance;
}
else if (loaderClass != null)
{
resourceLoader = ResourceLoaderFactory.getLoader(rsvc, loaderClass);
}
else
{
String msg = "Unable to find '" +
configuration.getString(RESOURCE_LOADER_IDENTIFIER) +
".resource.loader.class' specification in configuration." +
" This is a critical value. Please adjust configuration.";
log.error(msg);
throw new Exception(msg);
}
resourceLoader.commonInit(rsvc, configuration);
resourceLoader.init(configuration);
resourceLoaders.add(resourceLoader);
}
/*
* now see if this is overridden by configuration
*/
logWhenFound = rsvc.getBoolean(RuntimeConstants.RESOURCE_MANAGER_LOGWHENFOUND, true);
/*
* now, is a global cache specified?
*/
String cacheClassName = rsvc.getString(RuntimeConstants.RESOURCE_MANAGER_CACHE_CLASS);
Object cacheObject = null;
if (org.apache.commons.lang.StringUtils.isNotEmpty(cacheClassName))
{
try
{
cacheObject = ClassUtils.getNewInstance(cacheClassName);
}
catch (ClassNotFoundException cnfe)
{
String msg = "The specified class for ResourceCache (" + cacheClassName +
") does not exist or is not accessible to the current classloader.";
log.error(msg, cnfe);
throw cnfe;
}
if (!(cacheObject instanceof ResourceCache))
{
String msg = "The specified resource cache class (" + cacheClassName +
") must implement " + ResourceCache.class.getName();
log.error(msg);
throw new RuntimeException(msg);
}
}
/*
* if we didn't get through that, just use the default.
*/
if (cacheObject == null)
{
cacheObject = new ResourceCacheImpl();
}
globalCache = (ResourceCache) cacheObject;
globalCache.initialize(rsvc);
log.trace("Default ResourceManager initialization complete.");
}
/**
* This will produce a List of Hashtables, each hashtable contains the intialization info for a particular resource loader. This
* Hashtable will be passed in when initializing the the template loader.
*/
private void assembleResourceLoaderInitializers()
{
Vector resourceLoaderNames = rsvc.getConfiguration().getVector(RuntimeConstants.RESOURCE_LOADER);
StringUtils.trimStrings(resourceLoaderNames);
for (Iterator it = resourceLoaderNames.iterator(); it.hasNext(); )
{
/*
* The loader id might look something like the following:
*
* file.resource.loader
*
* The loader id is the prefix used for all properties
* pertaining to a particular loader.
*/
String loaderName = (String) it.next();
StringBuffer loaderID = new StringBuffer(loaderName);
loaderID.append(".").append(RuntimeConstants.RESOURCE_LOADER);
ExtendedProperties loaderConfiguration =
rsvc.getConfiguration().subset(loaderID.toString());
/*
* we can't really count on ExtendedProperties to give us an empty set
*/
if (loaderConfiguration == null)
{
log.debug("ResourceManager : No configuration information found "+
"for resource loader named '" + loaderName +
"' (id is "+loaderID+"). Skipping it...");
continue;
}
/*
* add the loader name token to the initializer if we need it
* for reference later. We can't count on the user to fill
* in the 'name' field
*/
loaderConfiguration.setProperty(RESOURCE_LOADER_IDENTIFIER, loaderName);
/*
* Add resources to the list of resource loader
* initializers.
*/
sourceInitializerList.add(loaderConfiguration);
}
}
/**
* Gets the named resource. Returned class type corresponds to specified type (i.e. Template
to
* RESOURCE_TEMPLATE
).
*
* This method is now unsynchronized which requires that ResourceCache
* implementations be thread safe (as the default is).
*
* @param resourceName The name of the resource to retrieve.
* @param resourceType The type of resource (RESOURCE_TEMPLATE
, RESOURCE_CONTENT
, etc.).
* @param encoding The character encoding to use.
*
* @return Resource with the template parsed and ready.
*
* @throws ResourceNotFoundException if template not found from any available source.
* @throws ParseErrorException if template cannot be parsed due to syntax (or other) error.
* @throws Exception if a problem in parse
*/
public Resource getResource(final String resourceName, final int resourceType, final String encoding)
throws ResourceNotFoundException,
ParseErrorException,
Exception
{
/*
* Check to see if the resource was placed in the cache.
* If it was placed in the cache then we will use
* the cached version of the resource. If not we
* will load it.
*
* Note: the type is included in the key to differentiate ContentResource
* (static content from #include) with a Template.
*/
String resourceKey = resourceType + resourceName;
Resource resource = globalCache.get(resourceKey);
if (resource != null)
{
try
{
// avoids additional method call to refreshResource
if (resource.requiresChecking())
{
/*
* both loadResource() and refreshResource() now return
* a new Resource instance when they are called
* (put in the cache when appropriate) in order to allow
* several threads to parse the same template simultaneously.
* It is redundant work and will cause more garbage collection but the
* benefit is that it allows concurrent parsing and processing
* without race conditions when multiple requests try to
* refresh/load the same template at the same time.
*
* Another alternative is to limit template parsing/retrieval
* so that only one thread can parse each template at a time
* but that creates a scalability bottleneck.
*
* See VELOCITY-606, VELOCITY-595 and VELOCITY-24
*/
resource = refreshResource(resource, encoding);
}
}
catch (ResourceNotFoundException rnfe)
{
/*
* something exceptional happened to that resource
* this could be on purpose,
* so clear the cache and try again
*/
globalCache.remove(resourceKey);
return getResource(resourceName, resourceType, encoding);
}
catch (ParseErrorException pee)
{
log.error("ResourceManager.getResource() exception", pee);
throw pee;
}
catch (RuntimeException re)
{
log.error("ResourceManager.getResource() exception", re);
throw re;
}
catch (Exception e)
{
log.error("ResourceManager.getResource() exception", e);
throw e;
}
}
else
{
try
{
/*
* it's not in the cache, so load it.
*/
resource = loadResource(resourceName, resourceType, encoding);
if (resource.getResourceLoader().isCachingOn())
{
globalCache.put(resourceKey, resource);
}
}
catch (ResourceNotFoundException rnfe)
{
log.error("ResourceManager : unable to find resource '" +
resourceName + "' in any resource loader.");
throw rnfe;
}
catch (ParseErrorException pee)
{
log.error("ResourceManager.getResource() parse exception", pee);
throw pee;
}
catch (RuntimeException re)
{
log.error("ResourceManager.getResource() load exception", re);
throw re;
}
catch (Exception e)
{
log.error("ResourceManager.getResource() exception new", e);
throw e;
}
}
return resource;
}
/**
* Create a new Resource of the specified type.
*
* @param resourceName The name of the resource to retrieve.
* @param resourceType The type of resource (RESOURCE_TEMPLATE
, RESOURCE_CONTENT
, etc.).
* @return new instance of appropriate resource type
* @since 1.6
*/
protected Resource createResource(String resourceName, int resourceType)
{
return ResourceFactory.getResource(resourceName, resourceType);
}
/**
* Loads a resource from the current set of resource loaders.
*
* @param resourceName The name of the resource to retrieve.
* @param resourceType The type of resource (RESOURCE_TEMPLATE
, RESOURCE_CONTENT
, etc.).
* @param encoding The character encoding to use.
*
* @return Resource with the template parsed and ready.
*
* @throws ResourceNotFoundException if template not found from any available source.
* @throws ParseErrorException if template cannot be parsed due to syntax (or other) error.
* @throws Exception if a problem in parse
*/
protected Resource loadResource(String resourceName, int resourceType, String encoding)
throws ResourceNotFoundException,
ParseErrorException,
Exception
{
Resource resource = createResource(resourceName, resourceType);
resource.setRuntimeServices(rsvc);
resource.setName(resourceName);
resource.setEncoding(encoding);
/*
* Now we have to try to find the appropriate
* loader for this resource. We have to cycle through
* the list of available resource loaders and see
* which one gives us a stream that we can use to
* make a resource with.
*/
long howOldItWas = 0;
for (Iterator it = resourceLoaders.iterator(); it.hasNext();)
{
ResourceLoader resourceLoader = (ResourceLoader) it.next();
resource.setResourceLoader(resourceLoader);
/*
* catch the ResourceNotFound exception
* as that is ok in our new multi-loader environment
*/
try
{
if (resource.process())
{
/*
* FIXME (gmj)
* moved in here - technically still
* a problem - but the resource needs to be
* processed before the loader can figure
* it out due to to the new
* multi-path support - will revisit and fix
*/
if (logWhenFound && log.isDebugEnabled())
{
log.debug("ResourceManager : found " + resourceName +
" with loader " +
resourceLoader.getClassName());
}
howOldItWas = resourceLoader.getLastModified(resource);
break;
}
}
catch (ResourceNotFoundException rnfe)
{
/*
* that's ok - it's possible to fail in
* multi-loader environment
*/
}
}
/*
* Return null if we can't find a resource.
*/
if (resource.getData() == null)
{
throw new ResourceNotFoundException("Unable to find resource '" + resourceName + "'");
}
/*
* some final cleanup
*/
resource.setLastModified(howOldItWas);
resource.setModificationCheckInterval(resource.getResourceLoader().getModificationCheckInterval());
resource.touch();
return resource;
}
/**
* Takes an existing resource, and 'refreshes' it. This generally means that the source of the resource is checked for changes
* according to some cache/check algorithm and if the resource changed, then the resource data is reloaded and re-parsed.
*
* @param resource resource to refresh
* @param encoding character encoding of the resource to refresh.
*
* @throws ResourceNotFoundException if template not found from current source for this Resource
* @throws ParseErrorException if template cannot be parsed due to syntax (or other) error.
* @throws Exception if a problem in parse
*/
protected Resource refreshResource(Resource resource, final String encoding)
throws ResourceNotFoundException,
ParseErrorException,
Exception
{
/*
* The resource knows whether it needs to be checked
* or not, and the resource's loader can check to
* see if the source has been modified. If both
* these conditions are true then we must reload
* the input stream and parse it to make a new
* AST for the resource.
*/
/*
* touch() the resource to reset the counters
*/
resource.touch();
/* check whether this can now be found in a higher priority
* resource loader. if so, pass the request off to loadResource.
*/
ResourceLoader loader = resource.getResourceLoader();
if (resourceLoaders.size() > 0 && resourceLoaders.indexOf(loader) > 0)
{
String name = resource.getName();
if (loader != getLoaderForResource(name))
{
return loadResource(name, resource.getType(), encoding);
}
}
if (resource.isSourceModified())
{
/*
* now check encoding info. It's possible that the newly declared
* encoding is different than the encoding already in the resource
* this strikes me as bad...
*/
if (!org.apache.commons.lang.StringUtils.equals(resource.getEncoding(), encoding))
{
log.warn("Declared encoding for template '" +
resource.getName() +
"' is different on reload. Old = '" +
resource.getEncoding() + "' New = '" + encoding);
resource.setEncoding(encoding);
}
/*
* read how old the resource is _before_
* processing (=>reading) it
*/
long howOldItWas = loader.getLastModified(resource);
String resourceKey = resource.getType() + resource.getName();
/*
* we create a copy to avoid partially overwriting a
* template which may be in use in another thread
*/
Resource newResource =
ResourceFactory.getResource(resource.getName(), resource.getType());
newResource.setRuntimeServices(rsvc);
newResource.setName(resource.getName());
newResource.setEncoding(resource.getEncoding());
newResource.setResourceLoader(loader);
newResource.setModificationCheckInterval(loader.getModificationCheckInterval());
newResource.process();
newResource.setLastModified(howOldItWas);
resource = newResource;
globalCache.put(resourceKey, newResource);
}
return resource;
}
/**
* Gets the named resource. Returned class type corresponds to specified type (i.e. Template
to
* RESOURCE_TEMPLATE
).
*
* @param resourceName The name of the resource to retrieve.
* @param resourceType The type of resource (RESOURCE_TEMPLATE
, RESOURCE_CONTENT
, etc.).
*
* @return Resource with the template parsed and ready.
*
* @throws ResourceNotFoundException if template not found from any available source.
* @throws ParseErrorException if template cannot be parsed due to syntax (or other) error.
* @throws Exception if a problem in parse
*
* @deprecated Use {@link #getResource(String resourceName, int resourceType, String encoding )}
*/
public Resource getResource(String resourceName, int resourceType)
throws ResourceNotFoundException,
ParseErrorException,
Exception
{
return getResource(resourceName, resourceType, RuntimeConstants.ENCODING_DEFAULT);
}
/**
* Determines if a template exists, and returns name of the loader that provides it. This is a slightly less hokey way to
* support the Velocity.templateExists() utility method, which was broken when per-template encoding was introduced. We can
* revisit this.
*
* @param resourceName Name of template or content resource
*
* @return class name of loader than can provide it
*/
public String getLoaderNameForResource(String resourceName)
{
ResourceLoader loader = getLoaderForResource(resourceName);
if (loader == null)
{
return null;
}
return loader.getClass().toString();
}
/**
* Returns the first {@link ResourceLoader} in which the specified
* resource exists.
*/
private ResourceLoader getLoaderForResource(String resourceName)
{
for (Iterator i = resourceLoaders.iterator(); i.hasNext(); )
{
ResourceLoader loader = (ResourceLoader)i.next();
if (loader.resourceExists(resourceName))
{
return loader;
}
}
return null;
}
}