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