/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed 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.
*/
package org.springframework.ui.velocity;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.exception.VelocityException;
import org.apache.velocity.runtime.RuntimeConstants;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
* Factory that configures a VelocityEngine. Can be used standalone,
* but typically you will either use {@link VelocityEngineFactoryBean}
* for preparing a VelocityEngine as bean reference, or
* {@link org.springframework.web.servlet.view.velocity.VelocityConfigurer}
* for web views.
*
* <p>The optional "configLocation" property sets the location of the Velocity
* properties file, within the current application. Velocity properties can be
* overridden via "velocityProperties", or even completely specified locally,
* avoiding the need for an external properties file.
*
* <p>The "resourceLoaderPath" property can be used to specify the Velocity
* resource loader path via Spring's Resource abstraction, possibly relative
* to the Spring application context.
*
* <p>If "overrideLogging" is true (the default), the VelocityEngine will be
* configured to log via Commons Logging, that is, using the Spring-provided
* {@link CommonsLoggingLogSystem} as log system.
*
* <p>The simplest way to use this class is to specify a
* {@link #setResourceLoaderPath(String) "resourceLoaderPath"}; the
* VelocityEngine typically then does not need any further configuration.
*
* @author Juergen Hoeller
* @see #setConfigLocation
* @see #setVelocityProperties
* @see #setResourceLoaderPath
* @see #setOverrideLogging
* @see #createVelocityEngine
* @see CommonsLoggingLogSystem
* @see VelocityEngineFactoryBean
* @see org.springframework.web.servlet.view.velocity.VelocityConfigurer
* @see org.apache.velocity.app.VelocityEngine
*/
public class VelocityEngineFactory {
protected final Log logger = LogFactory.getLog(getClass());
private Resource configLocation;
private final Map<String, Object> velocityProperties = new HashMap<String, Object>();
private String resourceLoaderPath;
private ResourceLoader resourceLoader = new DefaultResourceLoader();
private boolean preferFileSystemAccess = true;
private boolean overrideLogging = true;
/**
* Set the location of the Velocity config file.
* Alternatively, you can specify all properties locally.
* @see #setVelocityProperties
* @see #setResourceLoaderPath
*/
public void setConfigLocation(Resource configLocation) {
this.configLocation = configLocation;
}
/**
* Set Velocity properties, like "file.resource.loader.path".
* Can be used to override values in a Velocity config file,
* or to specify all necessary properties locally.
* <p>Note that the Velocity resource loader path also be set to any
* Spring resource location via the "resourceLoaderPath" property.
* Setting it here is just necessary when using a non-file-based
* resource loader.
* @see #setVelocityPropertiesMap
* @see #setConfigLocation
* @see #setResourceLoaderPath
*/
public void setVelocityProperties(Properties velocityProperties) {
CollectionUtils.mergePropertiesIntoMap(velocityProperties, this.velocityProperties);
}
/**
* Set Velocity properties as Map, to allow for non-String values
* like "ds.resource.loader.instance".
* @see #setVelocityProperties
*/
public void setVelocityPropertiesMap(Map<String, Object> velocityPropertiesMap) {
if (velocityPropertiesMap != null) {
this.velocityProperties.putAll(velocityPropertiesMap);
}
}
/**
* Set the Velocity resource loader path via a Spring resource location.
* Accepts multiple locations in Velocity's comma-separated path style.
* <p>When populated via a String, standard URLs like "file:" and "classpath:"
* pseudo URLs are supported, as understood by ResourceLoader. Allows for
* relative paths when running in an ApplicationContext.
* <p>Will define a path for the default Velocity resource loader with the name
* "file". If the specified resource cannot be resolved to a <code>java.io.File</code>,
* a generic SpringResourceLoader will be used under the name "spring", without
* modification detection.
* <p>Note that resource caching will be enabled in any case. With the file
* resource loader, the last-modified timestamp will be checked on access to
* detect changes. With SpringResourceLoader, the resource will be cached
* forever (for example for class path resources).
* <p>To specify a modification check interval for files, use Velocity's
* standard "file.resource.loader.modificationCheckInterval" property. By default,
* the file timestamp is checked on every access (which is surprisingly fast).
* Of course, this just applies when loading resources from the file system.
* <p>To enforce the use of SpringResourceLoader, i.e. to not resolve a path
* as file system resource in any case, turn off the "preferFileSystemAccess"
* flag. See the latter's javadoc for details.
* @see #setResourceLoader
* @see #setVelocityProperties
* @see #setPreferFileSystemAccess
* @see SpringResourceLoader
* @see org.apache.velocity.runtime.resource.loader.FileResourceLoader
*/
public void setResourceLoaderPath(String resourceLoaderPath) {
this.resourceLoaderPath = resourceLoaderPath;
}
/**
* Set the Spring ResourceLoader to use for loading Velocity template files.
* The default is DefaultResourceLoader. Will get overridden by the
* ApplicationContext if running in a context.
* @see org.springframework.core.io.DefaultResourceLoader
* @see org.springframework.context.ApplicationContext
*/
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
/**
* Return the Spring ResourceLoader to use for loading Velocity template files.
*/
protected ResourceLoader getResourceLoader() {
return this.resourceLoader;
}
/**
* Set whether to prefer file system access for template loading.
* File system access enables hot detection of template changes.
* <p>If this is enabled, VelocityEngineFactory will try to resolve the
* specified "resourceLoaderPath" as file system resource (which will work
* for expanded class path resources and ServletContext resources too).
* <p>Default is "true". Turn this off to always load via SpringResourceLoader
* (i.e. as stream, without hot detection of template changes), which might
* be necessary if some of your templates reside in an expanded classes
* directory while others reside in jar files.
* @see #setResourceLoaderPath
*/
public void setPreferFileSystemAccess(boolean preferFileSystemAccess) {
this.preferFileSystemAccess = preferFileSystemAccess;
}
/**
* Return whether to prefer file system access for template loading.
*/
protected boolean isPreferFileSystemAccess() {
return this.preferFileSystemAccess;
}
/**
* Set whether Velocity should log via Commons Logging, i.e. whether Velocity's
* log system should be set to CommonsLoggingLogSystem. Default value is true.
* @see CommonsLoggingLogSystem
*/
public void setOverrideLogging(boolean overrideLogging) {
this.overrideLogging = overrideLogging;
}
/**
* Prepare the VelocityEngine instance and return it.
* @return the VelocityEngine instance
* @throws IOException if the config file wasn't found
* @throws VelocityException on Velocity initialization failure
*/
public VelocityEngine createVelocityEngine() throws IOException, VelocityException {
VelocityEngine velocityEngine = newVelocityEngine();
Map<String, Object> props = new HashMap<String, Object>();
// Load config file if set.
if (this.configLocation != null) {
if (logger.isInfoEnabled()) {
logger.info("Loading Velocity config from [" + this.configLocation + "]");
}
CollectionUtils.mergePropertiesIntoMap(PropertiesLoaderUtils.loadProperties(this.configLocation), props);
}
// Merge local properties if set.
if (!this.velocityProperties.isEmpty()) {
props.putAll(this.velocityProperties);
}
// Set a resource loader path, if required.
if (this.resourceLoaderPath != null) {
initVelocityResourceLoader(velocityEngine, this.resourceLoaderPath);
}
// Log via Commons Logging?
if (this.overrideLogging) {
velocityEngine.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, new CommonsLoggingLogSystem());
}
// Apply properties to VelocityEngine.
for (Map.Entry<String, Object> entry : props.entrySet()) {
velocityEngine.setProperty(entry.getKey(), entry.getValue());
}
postProcessVelocityEngine(velocityEngine);
try {
// Perform actual initialization.
velocityEngine.init();
}
catch (IOException ex) {
throw ex;
}
catch (VelocityException ex) {
throw ex;
}
catch (RuntimeException ex) {
throw ex;
}
catch (Exception ex) {
logger.error("Why does VelocityEngine throw a generic checked exception, after all?", ex);
throw new VelocityException(ex.toString());
}
return velocityEngine;
}
/**
* Return a new VelocityEngine. Subclasses can override this for
* custom initialization, or for using a mock object for testing.
* <p>Called by <code>createVelocityEngine()</code>.
* @return the VelocityEngine instance
* @throws IOException if a config file wasn't found
* @throws VelocityException on Velocity initialization failure
* @see #createVelocityEngine()
*/
protected VelocityEngine newVelocityEngine() throws IOException, VelocityException {
return new VelocityEngine();
}
/**
* Initialize a Velocity resource loader for the given VelocityEngine:
* either a standard Velocity FileResourceLoader or a SpringResourceLoader.
* <p>Called by <code>createVelocityEngine()</code>.
* @param velocityEngine the VelocityEngine to configure
* @param resourceLoaderPath the path to load Velocity resources from
* @see org.apache.velocity.runtime.resource.loader.FileResourceLoader
* @see SpringResourceLoader
* @see #initSpringResourceLoader
* @see #createVelocityEngine()
*/
protected void initVelocityResourceLoader(VelocityEngine velocityEngine, String resourceLoaderPath) {
if (isPreferFileSystemAccess()) {
// Try to load via the file system, fall back to SpringResourceLoader
// (for hot detection of template changes, if possible).
try {
StringBuilder resolvedPath = new StringBuilder();
String[] paths = StringUtils.commaDelimitedListToStringArray(resourceLoaderPath);
for (int i = 0; i < paths.length; i++) {
String path = paths[i];
Resource resource = getResourceLoader().getResource(path);
File file = resource.getFile(); // will fail if not resolvable in the file system
if (logger.isDebugEnabled()) {
logger.debug("Resource loader path [" + path + "] resolved to file [" + file.getAbsolutePath() + "]");
}
resolvedPath.append(file.getAbsolutePath());
if (i < paths.length - 1) {
resolvedPath.append(',');
}
}
velocityEngine.setProperty(RuntimeConstants.RESOURCE_LOADER, "file");
velocityEngine.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_CACHE, "true");
velocityEngine.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, resolvedPath.toString());
}
catch (IOException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Cannot resolve resource loader path [" + resourceLoaderPath +
"] to [java.io.File]: using SpringResourceLoader", ex);
}
initSpringResourceLoader(velocityEngine, resourceLoaderPath);
}
}
else {
// Always load via SpringResourceLoader
// (without hot detection of template changes).
if (logger.isDebugEnabled()) {
logger.debug("File system access not preferred: using SpringResourceLoader");
}
initSpringResourceLoader(velocityEngine, resourceLoaderPath);
}
}
/**
* Initialize a SpringResourceLoader for the given VelocityEngine.
* <p>Called by <code>initVelocityResourceLoader</code>.
* @param velocityEngine the VelocityEngine to configure
* @param resourceLoaderPath the path to load Velocity resources from
* @see SpringResourceLoader
* @see #initVelocityResourceLoader
*/
protected void initSpringResourceLoader(VelocityEngine velocityEngine, String resourceLoaderPath) {
velocityEngine.setProperty(
RuntimeConstants.RESOURCE_LOADER, SpringResourceLoader.NAME);
velocityEngine.setProperty(
SpringResourceLoader.SPRING_RESOURCE_LOADER_CLASS, SpringResourceLoader.class.getName());
velocityEngine.setProperty(
SpringResourceLoader.SPRING_RESOURCE_LOADER_CACHE, "true");
velocityEngine.setApplicationAttribute(
SpringResourceLoader.SPRING_RESOURCE_LOADER, getResourceLoader());
velocityEngine.setApplicationAttribute(
SpringResourceLoader.SPRING_RESOURCE_LOADER_PATH, resourceLoaderPath);
}
/**
* To be implemented by subclasses that want to to perform custom
* post-processing of the VelocityEngine after this FactoryBean
* performed its default configuration (but before VelocityEngine.init).
* <p>Called by <code>createVelocityEngine()</code>.
* @param velocityEngine the current VelocityEngine
* @throws IOException if a config file wasn't found
* @throws VelocityException on Velocity initialization failure
* @see #createVelocityEngine()
* @see org.apache.velocity.app.VelocityEngine#init
*/
protected void postProcessVelocityEngine(VelocityEngine velocityEngine)
throws IOException, VelocityException {
}
}
|