• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2002-2013 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      https://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package org.apache.velocity.spring;
18 
19 import java.io.File;
20 import java.io.IOException;
21 import java.util.HashMap;
22 import java.util.Map;
23 import java.util.Properties;
24 
25 import org.apache.velocity.app.VelocityEngine;
26 import org.apache.velocity.exception.VelocityException;
27 import org.apache.velocity.runtime.RuntimeConstants;
28 
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31 import org.springframework.core.io.DefaultResourceLoader;
32 import org.springframework.core.io.Resource;
33 import org.springframework.core.io.ResourceLoader;
34 import org.springframework.core.io.support.PropertiesLoaderUtils;
35 import org.springframework.util.CollectionUtils;
36 import org.springframework.util.StringUtils;
37 
38 /**
39  * Factory that configures a VelocityEngine. Can be used standalone,
40  * but typically you will either use {@link VelocityEngineFactoryBean}
41  * for preparing a VelocityEngine as bean reference, or
42  * <a href="https://docs.spring.io/spring-framework/docs/4.3.29.RELEASE/javadoc-api/org/springframework/web/servlet/view/velocity/VelocityConfigurer.html">org.springframework.web.servlet.view.velocity.VelocityConfigurer</a>
43  * for web views.
44  *
45  * <p>The optional "configLocation" property sets the location of the Velocity
46  * properties file, within the current application. Velocity properties can be
47  * overridden via "velocityProperties", or even completely specified locally,
48  * avoiding the need for an external properties file.
49  *
50  * <p>The "resourceLoaderPath" property can be used to specify the Velocity
51  * resource loader path via Spring's Resource abstraction, possibly relative
52  * to the Spring application context.
53  *
54  * <p>The simplest way to use this class is to specify a
55  * {@link #setResourceLoaderPath(String) "resourceLoaderPath"}; the
56  * VelocityEngine typically then does not need any further configuration.
57  *
58  * @author Juergen Hoeller
59  * @author Claude Brisson
60  * @since 2020-05-29
61  * @see #setConfigLocation
62  * @see #setVelocityProperties
63  * @see #setResourceLoaderPath
64  * @see #createVelocityEngine
65  * @see VelocityEngineFactoryBean
66  * @see <a href="https://docs.spring.io/spring-framework/docs/4.3.29.RELEASE/javadoc-api/org/springframework/web/servlet/view/velocity/VelocityConfigurer.html">org.springframework.web.servlet.view.velocity.VelocityConfigurer</a>
67  * @see org.apache.velocity.app.VelocityEngine
68  */
69 public class VelocityEngineFactory {
70 
71 	protected static final Logger logger = LoggerFactory.getLogger(VelocityEngineFactory.class);
72 
73 	private Resource configLocation;
74 
75 	private final Map<String, Object> velocityProperties = new HashMap<String, Object>();
76 
77 	private String resourceLoaderPath;
78 
79 	private ResourceLoader resourceLoader = new DefaultResourceLoader();
80 
81 	private boolean preferFileSystemAccess = true;
82 
83 	private boolean overrideLogging = true;
84 
85 
86 	/**
87 	 * Set the location of the Velocity config file.
88 	 * Alternatively, you can specify all properties locally.
89 	 * @see #setVelocityProperties
90 	 * @see #setResourceLoaderPath
91 	 */
setConfigLocation(Resource configLocation)92 	public void setConfigLocation(Resource configLocation) {
93 		this.configLocation = configLocation;
94 	}
95 
96 	/**
97 	 * Set Velocity properties, like "file.resource.loader.path".
98 	 * Can be used to override values in a Velocity config file,
99 	 * or to specify all necessary properties locally.
100 	 * <p>Note that the Velocity resource loader path also be set to any
101 	 * Spring resource location via the "resourceLoaderPath" property.
102 	 * Setting it here is just necessary when using a non-file-based
103 	 * resource loader.
104 	 * @see #setVelocityPropertiesMap
105 	 * @see #setConfigLocation
106 	 * @see #setResourceLoaderPath
107 	 */
setVelocityProperties(Properties velocityProperties)108 	public void setVelocityProperties(Properties velocityProperties) {
109 		CollectionUtils.mergePropertiesIntoMap(velocityProperties, this.velocityProperties);
110 	}
111 
112 	/**
113 	 * Set Velocity properties as Map, to allow for non-String values
114 	 * like "ds.resource.loader.instance".
115 	 * @see #setVelocityProperties
116 	 */
setVelocityPropertiesMap(Map<String, Object> velocityPropertiesMap)117 	public void setVelocityPropertiesMap(Map<String, Object> velocityPropertiesMap) {
118 		if (velocityPropertiesMap != null) {
119 			this.velocityProperties.putAll(velocityPropertiesMap);
120 		}
121 	}
122 
123 	/**
124 	 * Set the Velocity resource loader path via a Spring resource location.
125 	 * Accepts multiple locations in Velocity's comma-separated path style.
126 	 * <p>When populated via a String, standard URLs like "file:" and "classpath:"
127 	 * pseudo URLs are supported, as understood by ResourceLoader. Allows for
128 	 * relative paths when running in an ApplicationContext.
129 	 * <p>Will define a path for the default Velocity resource loader with the name
130 	 * "file". If the specified resource cannot be resolved to a {@code java.io.File},
131 	 * a generic SpringResourceLoader will be used under the name "spring", without
132 	 * modification detection.
133 	 * <p>Note that resource caching will be enabled in any case. With the file
134 	 * resource loader, the last-modified timestamp will be checked on access to
135 	 * detect changes. With SpringResourceLoader, the resource will be cached
136 	 * forever (for example for class path resources).
137 	 * <p>To specify a modification check interval for files, use Velocity's
138 	 * standard "file.resource.loader.modificationCheckInterval" property. By default,
139 	 * the file timestamp is checked on every access (which is surprisingly fast).
140 	 * Of course, this just applies when loading resources from the file system.
141 	 * <p>To enforce the use of SpringResourceLoader, i.e. to not resolve a path
142 	 * as file system resource in any case, turn off the "preferFileSystemAccess"
143 	 * flag. See the latter's javadoc for details.
144 	 * @see #setResourceLoader
145 	 * @see #setVelocityProperties
146 	 * @see #setPreferFileSystemAccess
147 	 * @see SpringResourceLoader
148 	 * @see org.apache.velocity.runtime.resource.loader.FileResourceLoader
149 	 */
setResourceLoaderPath(String resourceLoaderPath)150 	public void setResourceLoaderPath(String resourceLoaderPath) {
151 		this.resourceLoaderPath = resourceLoaderPath;
152 	}
153 
154 	/**
155 	 * Set the Spring ResourceLoader to use for loading Velocity template files.
156 	 * The default is DefaultResourceLoader. Will get overridden by the
157 	 * ApplicationContext if running in a context.
158 	 * @see org.springframework.core.io.DefaultResourceLoader
159 	 * @see org.springframework.context.ApplicationContext
160 	 */
setResourceLoader(ResourceLoader resourceLoader)161 	public void setResourceLoader(ResourceLoader resourceLoader) {
162 		this.resourceLoader = resourceLoader;
163 	}
164 
165 	/**
166 	 * Return the Spring ResourceLoader to use for loading Velocity template files.
167 	 */
getResourceLoader()168 	protected ResourceLoader getResourceLoader() {
169 		return this.resourceLoader;
170 	}
171 
172 	/**
173 	 * Set whether to prefer file system access for template loading.
174 	 * File system access enables hot detection of template changes.
175 	 * <p>If this is enabled, VelocityEngineFactory will try to resolve the
176 	 * specified "resourceLoaderPath" as file system resource (which will work
177 	 * for expanded class path resources and ServletContext resources too).
178 	 * <p>Default is "true". Turn this off to always load via SpringResourceLoader
179 	 * (i.e. as stream, without hot detection of template changes), which might
180 	 * be necessary if some of your templates reside in an expanded classes
181 	 * directory while others reside in jar files.
182 	 * @see #setResourceLoaderPath
183 	 */
setPreferFileSystemAccess(boolean preferFileSystemAccess)184 	public void setPreferFileSystemAccess(boolean preferFileSystemAccess) {
185 		this.preferFileSystemAccess = preferFileSystemAccess;
186 	}
187 
188 	/**
189 	 * Return whether to prefer file system access for template loading.
190 	 */
isPreferFileSystemAccess()191 	protected boolean isPreferFileSystemAccess() {
192 		return this.preferFileSystemAccess;
193 	}
194 
195 	/**
196 	 * Prepare the VelocityEngine instance and return it.
197 	 * @return the VelocityEngine instance
198 	 * @throws IOException if the config file wasn't found
199 	 * @throws VelocityException on Velocity initialization failure
200 	 */
createVelocityEngine()201 	public VelocityEngine createVelocityEngine() throws IOException, VelocityException {
202 		VelocityEngine velocityEngine = newVelocityEngine();
203 		Map<String, Object> props = new HashMap<String, Object>();
204 
205 		// Load config file if set.
206 		if (this.configLocation != null) {
207 			if (logger.isInfoEnabled()) {
208 				logger.info("Loading Velocity config from [" + this.configLocation + "]");
209 			}
210 			CollectionUtils.mergePropertiesIntoMap(PropertiesLoaderUtils.loadProperties(this.configLocation), props);
211 		}
212 
213 		// Merge local properties if set.
214 		if (!this.velocityProperties.isEmpty()) {
215 			props.putAll(this.velocityProperties);
216 		}
217 
218 		// Set a resource loader path, if required.
219 		if (this.resourceLoaderPath != null) {
220 			initVelocityResourceLoader(velocityEngine, this.resourceLoaderPath);
221 		}
222 
223 		// Apply properties to VelocityEngine.
224 		for (Map.Entry<String, Object> entry : props.entrySet()) {
225 			velocityEngine.setProperty(entry.getKey(), entry.getValue());
226 		}
227 
228 		postProcessVelocityEngine(velocityEngine);
229 
230 		// Perform actual initialization.
231 		velocityEngine.init();
232 
233 		return velocityEngine;
234 	}
235 
236 	/**
237 	 * Return a new VelocityEngine. Subclasses can override this for
238 	 * custom initialization, or for using a mock object for testing.
239 	 * <p>Called by {@code createVelocityEngine()}.
240 	 * @return the VelocityEngine instance
241 	 * @throws IOException if a config file wasn't found
242 	 * @throws VelocityException on Velocity initialization failure
243 	 * @see #createVelocityEngine()
244 	 */
newVelocityEngine()245 	protected VelocityEngine newVelocityEngine() throws IOException, VelocityException {
246 		return new VelocityEngine();
247 	}
248 
249 	/**
250 	 * Initialize a Velocity resource loader for the given VelocityEngine:
251 	 * either a standard Velocity FileResourceLoader or a SpringResourceLoader.
252 	 * <p>Called by {@code createVelocityEngine()}.
253 	 * @param velocityEngine the VelocityEngine to configure
254 	 * @param resourceLoaderPath the path to load Velocity resources from
255 	 * @see org.apache.velocity.runtime.resource.loader.FileResourceLoader
256 	 * @see SpringResourceLoader
257 	 * @see #initSpringResourceLoader
258 	 * @see #createVelocityEngine()
259 	 */
initVelocityResourceLoader(VelocityEngine velocityEngine, String resourceLoaderPath)260 	protected void initVelocityResourceLoader(VelocityEngine velocityEngine, String resourceLoaderPath) {
261 		if (isPreferFileSystemAccess()) {
262 			// Try to load via the file system, fall back to SpringResourceLoader
263 			// (for hot detection of template changes, if possible).
264 			try {
265 				StringBuilder resolvedPath = new StringBuilder();
266 				String[] paths = StringUtils.commaDelimitedListToStringArray(resourceLoaderPath);
267 				for (int i = 0; i < paths.length; i++) {
268 					String path = paths[i];
269 					Resource resource = getResourceLoader().getResource(path);
270 					File file = resource.getFile();  // will fail if not resolvable in the file system
271 					if (logger.isDebugEnabled()) {
272 						logger.debug("Resource loader path [" + path + "] resolved to file [" + file.getAbsolutePath() + "]");
273 					}
274 					resolvedPath.append(file.getAbsolutePath());
275 					if (i < paths.length - 1) {
276 						resolvedPath.append(',');
277 					}
278 				}
279 				velocityEngine.setProperty(RuntimeConstants.RESOURCE_LOADER, "file");
280 				velocityEngine.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_CACHE, "true");
281 				velocityEngine.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, resolvedPath.toString());
282 			}
283 			catch (IOException ex) {
284 				if (logger.isDebugEnabled()) {
285 					logger.debug("Cannot resolve resource loader path [" + resourceLoaderPath +
286 							"] to [java.io.File]: using SpringResourceLoader", ex);
287 				}
288 				initSpringResourceLoader(velocityEngine, resourceLoaderPath);
289 			}
290 		}
291 		else {
292 			// Always load via SpringResourceLoader
293 			// (without hot detection of template changes).
294 			if (logger.isDebugEnabled()) {
295 				logger.debug("File system access not preferred: using SpringResourceLoader");
296 			}
297 			initSpringResourceLoader(velocityEngine, resourceLoaderPath);
298 		}
299 	}
300 
301 	/**
302 	 * Initialize a SpringResourceLoader for the given VelocityEngine.
303 	 * <p>Called by {@code initVelocityResourceLoader}.
304 	 * @param velocityEngine the VelocityEngine to configure
305 	 * @param resourceLoaderPath the path to load Velocity resources from
306 	 * @see SpringResourceLoader
307 	 * @see #initVelocityResourceLoader
308 	 */
initSpringResourceLoader(VelocityEngine velocityEngine, String resourceLoaderPath)309 	protected void initSpringResourceLoader(VelocityEngine velocityEngine, String resourceLoaderPath) {
310 		velocityEngine.setProperty(
311 				RuntimeConstants.RESOURCE_LOADER, SpringResourceLoader.NAME);
312 		velocityEngine.setProperty(
313 				SpringResourceLoader.SPRING_RESOURCE_LOADER_CLASS, SpringResourceLoader.class.getName());
314 		velocityEngine.setProperty(
315 				SpringResourceLoader.SPRING_RESOURCE_LOADER_CACHE, "true");
316 		velocityEngine.setApplicationAttribute(
317 				SpringResourceLoader.SPRING_RESOURCE_LOADER, getResourceLoader());
318 		velocityEngine.setApplicationAttribute(
319 				SpringResourceLoader.SPRING_RESOURCE_LOADER_PATH, resourceLoaderPath);
320 	}
321 
322 	/**
323 	 * To be implemented by subclasses that want to perform custom
324 	 * post-processing of the VelocityEngine after this FactoryBean
325 	 * performed its default configuration (but before VelocityEngine.init).
326 	 * <p>Called by {@code createVelocityEngine()}.
327 	 * @param velocityEngine the current VelocityEngine
328 	 * @throws IOException if a config file wasn't found
329 	 * @throws VelocityException on Velocity initialization failure
330 	 * @see #createVelocityEngine()
331 	 * @see org.apache.velocity.app.VelocityEngine#init
332 	 */
postProcessVelocityEngine(VelocityEngine velocityEngine)333 	protected void postProcessVelocityEngine(VelocityEngine velocityEngine)
334 			throws IOException, VelocityException {
335 	}
336 
337 }
338