001: /*
002: * Copyright 2002-2006 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.ui.velocity;
018:
019: import java.io.File;
020: import java.io.IOException;
021: import java.util.HashMap;
022: import java.util.Iterator;
023: import java.util.Map;
024: import java.util.Properties;
025:
026: import org.apache.commons.logging.Log;
027: import org.apache.commons.logging.LogFactory;
028: import org.apache.velocity.app.VelocityEngine;
029: import org.apache.velocity.exception.VelocityException;
030: import org.apache.velocity.runtime.RuntimeConstants;
031:
032: import org.springframework.core.io.DefaultResourceLoader;
033: import org.springframework.core.io.Resource;
034: import org.springframework.core.io.ResourceLoader;
035: import org.springframework.core.io.support.PropertiesLoaderUtils;
036: import org.springframework.util.StringUtils;
037:
038: /**
039: * Factory that configures a VelocityEngine. Can be used standalone,
040: * but typically you will either use {@link VelocityEngineFactoryBean}
041: * for preparing a VelocityEngine as bean reference, or
042: * {@link org.springframework.web.servlet.view.velocity.VelocityConfigurer}
043: * for web views.
044: *
045: * <p>The optional "configLocation" property sets the location of the Velocity
046: * properties file, within the current application. Velocity properties can be
047: * overridden via "velocityProperties", or even completely specified locally,
048: * avoiding the need for an external properties file.
049: *
050: * <p>The "resourceLoaderPath" property can be used to specify the Velocity
051: * resource loader path via Spring's Resource abstraction, possibly relative
052: * to the Spring application context.
053: *
054: * <p>If "overrideLogging" is true (the default), the VelocityEngine will be
055: * configured to log via Commons Logging, that is, using the Spring-provided
056: * {@link CommonsLoggingLogSystem} as log system.
057: *
058: * <p>The simplest way to use this class is to specify a
059: * {@link #setResourceLoaderPath(String) "resourceLoaderPath"}; the
060: * VelocityEngine typically then does not need any further configuration.
061: *
062: * @author Juergen Hoeller
063: * @see #setConfigLocation
064: * @see #setVelocityProperties
065: * @see #setResourceLoaderPath
066: * @see #setOverrideLogging
067: * @see #createVelocityEngine
068: * @see CommonsLoggingLogSystem
069: * @see VelocityEngineFactoryBean
070: * @see org.springframework.web.servlet.view.velocity.VelocityConfigurer
071: * @see org.apache.velocity.app.VelocityEngine
072: */
073: public class VelocityEngineFactory {
074:
075: protected final Log logger = LogFactory.getLog(getClass());
076:
077: private Resource configLocation;
078:
079: private final Map velocityProperties = new HashMap();
080:
081: private String resourceLoaderPath;
082:
083: private ResourceLoader resourceLoader = new DefaultResourceLoader();
084:
085: private boolean preferFileSystemAccess = true;
086:
087: private boolean overrideLogging = true;
088:
089: /**
090: * Set the location of the Velocity config file.
091: * Alternatively, you can specify all properties locally.
092: * @see #setVelocityProperties
093: * @see #setResourceLoaderPath
094: */
095: public void setConfigLocation(Resource configLocation) {
096: this .configLocation = configLocation;
097: }
098:
099: /**
100: * Set Velocity properties, like "file.resource.loader.path".
101: * Can be used to override values in a Velocity config file,
102: * or to specify all necessary properties locally.
103: * <p>Note that the Velocity resource loader path also be set to any
104: * Spring resource location via the "resourceLoaderPath" property.
105: * Setting it here is just necessary when using a non-file-based
106: * resource loader.
107: * @see #setVelocityPropertiesMap
108: * @see #setConfigLocation
109: * @see #setResourceLoaderPath
110: */
111: public void setVelocityProperties(Properties velocityProperties) {
112: setVelocityPropertiesMap(velocityProperties);
113: }
114:
115: /**
116: * Set Velocity properties as Map, to allow for non-String values
117: * like "ds.resource.loader.instance".
118: * @see #setVelocityProperties
119: */
120: public void setVelocityPropertiesMap(Map velocityPropertiesMap) {
121: if (velocityPropertiesMap != null) {
122: this .velocityProperties.putAll(velocityPropertiesMap);
123: }
124: }
125:
126: /**
127: * Set the Velocity resource loader path via a Spring resource location.
128: * Accepts multiple locations in Velocity's comma-separated path style.
129: * <p>When populated via a String, standard URLs like "file:" and "classpath:"
130: * pseudo URLs are supported, as understood by ResourceLoader. Allows for
131: * relative paths when running in an ApplicationContext.
132: * <p>Will define a path for the default Velocity resource loader with the name
133: * "file". If the specified resource cannot be resolved to a <code>java.io.File</code>,
134: * a generic SpringResourceLoader will be used under the name "spring", without
135: * modification detection.
136: * <p>Note that resource caching will be enabled in any case. With the file
137: * resource loader, the last-modified timestamp will be checked on access to
138: * detect changes. With SpringResourceLoader, the resource will be cached
139: * forever (for example for class path resources).
140: * <p>To specify a modification check interval for files, use Velocity's
141: * standard "file.resource.loader.modificationCheckInterval" property. By default,
142: * the file timestamp is checked on every access (which is surprisingly fast).
143: * Of course, this just applies when loading resources from the file system.
144: * <p>To enforce the use of SpringResourceLoader, i.e. to not resolve a path
145: * as file system resource in any case, turn off the "preferFileSystemAccess"
146: * flag. See the latter's javadoc for details.
147: * @see #setResourceLoader
148: * @see #setVelocityProperties
149: * @see #setPreferFileSystemAccess
150: * @see SpringResourceLoader
151: * @see org.apache.velocity.runtime.resource.loader.FileResourceLoader
152: */
153: public void setResourceLoaderPath(String resourceLoaderPath) {
154: this .resourceLoaderPath = resourceLoaderPath;
155: }
156:
157: /**
158: * Set the Spring ResourceLoader to use for loading Velocity template files.
159: * The default is DefaultResourceLoader. Will get overridden by the
160: * ApplicationContext if running in a context.
161: * @see org.springframework.core.io.DefaultResourceLoader
162: * @see org.springframework.context.ApplicationContext
163: */
164: public void setResourceLoader(ResourceLoader resourceLoader) {
165: this .resourceLoader = resourceLoader;
166: }
167:
168: /**
169: * Return the Spring ResourceLoader to use for loading Velocity template files.
170: */
171: protected ResourceLoader getResourceLoader() {
172: return this .resourceLoader;
173: }
174:
175: /**
176: * Set whether to prefer file system access for template loading.
177: * File system access enables hot detection of template changes.
178: * <p>If this is enabled, VelocityEngineFactory will try to resolve the
179: * specified "resourceLoaderPath" as file system resource (which will work
180: * for expanded class path resources and ServletContext resources too).
181: * <p>Default is "true". Turn this off to always load via SpringResourceLoader
182: * (i.e. as stream, without hot detection of template changes), which might
183: * be necessary if some of your templates reside in an expanded classes
184: * directory while others reside in jar files.
185: * @see #setResourceLoaderPath
186: */
187: public void setPreferFileSystemAccess(boolean preferFileSystemAccess) {
188: this .preferFileSystemAccess = preferFileSystemAccess;
189: }
190:
191: /**
192: * Return whether to prefer file system access for template loading.
193: */
194: protected boolean isPreferFileSystemAccess() {
195: return this .preferFileSystemAccess;
196: }
197:
198: /**
199: * Set whether Velocity should log via Commons Logging, i.e. whether Velocity's
200: * log system should be set to CommonsLoggingLogSystem. Default value is true.
201: * @see CommonsLoggingLogSystem
202: */
203: public void setOverrideLogging(boolean overrideLogging) {
204: this .overrideLogging = overrideLogging;
205: }
206:
207: /**
208: * Prepare the VelocityEngine instance and return it.
209: * @return the VelocityEngine instance
210: * @throws IOException if the config file wasn't found
211: * @throws VelocityException on Velocity initialization failure
212: */
213: public VelocityEngine createVelocityEngine() throws IOException,
214: VelocityException {
215: VelocityEngine velocityEngine = newVelocityEngine();
216: Properties props = new Properties();
217:
218: // Load config file if set.
219: if (this .configLocation != null) {
220: if (logger.isInfoEnabled()) {
221: logger.info("Loading Velocity config from ["
222: + this .configLocation + "]");
223: }
224: PropertiesLoaderUtils.fillProperties(props,
225: this .configLocation);
226: }
227:
228: // Merge local properties if set.
229: if (!this .velocityProperties.isEmpty()) {
230: props.putAll(this .velocityProperties);
231: }
232:
233: // Set a resource loader path, if required.
234: if (this .resourceLoaderPath != null) {
235: initVelocityResourceLoader(velocityEngine,
236: this .resourceLoaderPath);
237: }
238:
239: // Log via Commons Logging?
240: if (this .overrideLogging) {
241: velocityEngine.setProperty(
242: RuntimeConstants.RUNTIME_LOG_LOGSYSTEM,
243: new CommonsLoggingLogSystem());
244: }
245:
246: // Apply properties to VelocityEngine.
247: for (Iterator it = props.entrySet().iterator(); it.hasNext();) {
248: Map.Entry entry = (Map.Entry) it.next();
249: if (!(entry.getKey() instanceof String)) {
250: throw new IllegalArgumentException(
251: "Illegal property key [" + entry.getKey()
252: + "]: only Strings allowed");
253: }
254: velocityEngine.setProperty((String) entry.getKey(), entry
255: .getValue());
256: }
257:
258: postProcessVelocityEngine(velocityEngine);
259:
260: try {
261: // Perform actual initialization.
262: velocityEngine.init();
263: } catch (IOException ex) {
264: throw ex;
265: } catch (VelocityException ex) {
266: throw ex;
267: } catch (RuntimeException ex) {
268: throw ex;
269: } catch (Exception ex) {
270: logger
271: .error(
272: "Why does VelocityEngine throw a generic checked exception, after all?",
273: ex);
274: throw new VelocityException(ex.toString());
275: }
276:
277: return velocityEngine;
278: }
279:
280: /**
281: * Return a new VelocityEngine. Subclasses can override this for
282: * custom initialization, or for using a mock object for testing.
283: * <p>Called by <code>createVelocityEngine()</code>.
284: * @return the VelocityEngine instance
285: * @throws IOException if a config file wasn't found
286: * @throws VelocityException on Velocity initialization failure
287: * @see #createVelocityEngine()
288: */
289: protected VelocityEngine newVelocityEngine() throws IOException,
290: VelocityException {
291: return new VelocityEngine();
292: }
293:
294: /**
295: * Initialize a Velocity resource loader for the given VelocityEngine:
296: * either a standard Velocity FileResourceLoader or a SpringResourceLoader.
297: * <p>Called by <code>createVelocityEngine()</code>.
298: * @param velocityEngine the VelocityEngine to configure
299: * @param resourceLoaderPath the path to load Velocity resources from
300: * @see org.apache.velocity.runtime.resource.loader.FileResourceLoader
301: * @see SpringResourceLoader
302: * @see #initSpringResourceLoader
303: * @see #createVelocityEngine()
304: */
305: protected void initVelocityResourceLoader(
306: VelocityEngine velocityEngine, String resourceLoaderPath) {
307: if (isPreferFileSystemAccess()) {
308: // Try to load via the file system, fall back to SpringResourceLoader
309: // (for hot detection of template changes, if possible).
310: try {
311: StringBuffer resolvedPath = new StringBuffer();
312: String[] paths = StringUtils
313: .commaDelimitedListToStringArray(resourceLoaderPath);
314: for (int i = 0; i < paths.length; i++) {
315: String path = paths[i];
316: Resource resource = getResourceLoader()
317: .getResource(path);
318: File file = resource.getFile(); // will fail if not resolvable in the file system
319: if (logger.isDebugEnabled()) {
320: logger.debug("Resource loader path [" + path
321: + "] resolved to file ["
322: + file.getAbsolutePath() + "]");
323: }
324: resolvedPath.append(file.getAbsolutePath());
325: if (i < paths.length - 1) {
326: resolvedPath.append(',');
327: }
328: }
329: velocityEngine.setProperty(
330: RuntimeConstants.RESOURCE_LOADER, "file");
331: velocityEngine.setProperty(
332: RuntimeConstants.FILE_RESOURCE_LOADER_CACHE,
333: "true");
334: velocityEngine.setProperty(
335: RuntimeConstants.FILE_RESOURCE_LOADER_PATH,
336: resolvedPath.toString());
337: } catch (IOException ex) {
338: if (logger.isDebugEnabled()) {
339: logger
340: .debug(
341: "Cannot resolve resource loader path ["
342: + resourceLoaderPath
343: + "] to [java.io.File]: using SpringResourceLoader",
344: ex);
345: }
346: initSpringResourceLoader(velocityEngine,
347: resourceLoaderPath);
348: }
349: } else {
350: // Always load via SpringResourceLoader
351: // (without hot detection of template changes).
352: if (logger.isDebugEnabled()) {
353: logger
354: .debug("File system access not preferred: using SpringResourceLoader");
355: }
356: initSpringResourceLoader(velocityEngine, resourceLoaderPath);
357: }
358: }
359:
360: /**
361: * Initialize a SpringResourceLoader for the given VelocityEngine.
362: * <p>Called by <code>initVelocityResourceLoader</code>.
363: * @param velocityEngine the VelocityEngine to configure
364: * @param resourceLoaderPath the path to load Velocity resources from
365: * @see SpringResourceLoader
366: * @see #initVelocityResourceLoader
367: */
368: protected void initSpringResourceLoader(
369: VelocityEngine velocityEngine, String resourceLoaderPath) {
370: velocityEngine.setProperty(RuntimeConstants.RESOURCE_LOADER,
371: SpringResourceLoader.NAME);
372: velocityEngine.setProperty(
373: SpringResourceLoader.SPRING_RESOURCE_LOADER_CLASS,
374: SpringResourceLoader.class.getName());
375: velocityEngine.setProperty(
376: SpringResourceLoader.SPRING_RESOURCE_LOADER_CACHE,
377: "true");
378: velocityEngine.setApplicationAttribute(
379: SpringResourceLoader.SPRING_RESOURCE_LOADER,
380: getResourceLoader());
381: velocityEngine.setApplicationAttribute(
382: SpringResourceLoader.SPRING_RESOURCE_LOADER_PATH,
383: resourceLoaderPath);
384: }
385:
386: /**
387: * To be implemented by subclasses that want to to perform custom
388: * post-processing of the VelocityEngine after this FactoryBean
389: * performed its default configuration (but before VelocityEngine.init).
390: * <p>Called by <code>createVelocityEngine()</code>.
391: * @param velocityEngine the current VelocityEngine
392: * @throws IOException if a config file wasn't found
393: * @throws VelocityException on Velocity initialization failure
394: * @see #createVelocityEngine()
395: * @see org.apache.velocity.app.VelocityEngine#init
396: */
397: protected void postProcessVelocityEngine(
398: VelocityEngine velocityEngine) throws IOException,
399: VelocityException {
400: }
401:
402: }
|