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}