You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@velocity.apache.org by cb...@apache.org on 2020/10/27 13:21:20 UTC

[velocity-engine] branch spring-velocity-support created (now 35e9229)

This is an automated email from the ASF dual-hosted git repository.

cbrisson pushed a change to branch spring-velocity-support
in repository https://gitbox.apache.org/repos/asf/velocity-engine.git.


      at 35e9229  Initial commit for spring-velocity-support

This branch includes the following new commits:

     new 35e9229  Initial commit for spring-velocity-support

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[velocity-engine] 01/01: Initial commit for spring-velocity-support

Posted by cb...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

cbrisson pushed a commit to branch spring-velocity-support
in repository https://gitbox.apache.org/repos/asf/velocity-engine.git

commit 35e922975b4b48124ce2737905a215a5a6377025
Author: Claude Brisson <cl...@renegat.net>
AuthorDate: Tue Oct 27 14:19:49 2020 +0100

    Initial commit for spring-velocity-support
---
 README.md                                          |   3 +-
 pom.xml                                            |   1 +
 spring-velocity-support/pom.xml                    | 106 +++++++
 .../velocity/spring/SpringResourceLoader.java      | 140 +++++++++
 .../velocity/spring/VelocityEngineFactory.java     | 337 +++++++++++++++++++++
 .../velocity/spring/VelocityEngineFactoryBean.java |  79 +++++
 .../velocity/spring/VelocityEngineUtils.java       | 112 +++++++
 .../test/VelocityEngineFactoryBeanTests.java       |  75 +++++
 .../spring/test/VelocityEngineFactoryTests.java    |  70 +++++
 .../src/test/resources/simple.vm                   |   1 +
 .../src/test/resources/velocity.properties         |  19 ++
 11 files changed, 942 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 934b436..ccafa6b 100644
--- a/README.md
+++ b/README.md
@@ -11,6 +11,7 @@ Here's a description of the top level directories:
     velocity-engine-core/       The Velocity Engine core module
     velocity-engine-examples/   Several simple examples
     velocity-engine-scripting/  JSR-223 implementation for Velocity scripting
+    spring-velocity-support     Velocity Engine factory bean for Spring framework
     src/                        Source for parent modules, mainly changelog
 
 ## REQUIREMENTS
@@ -19,7 +20,7 @@ Apache Velocity 2.2 will run with any Java runtime engine v1.8 or greater.
 
 Building from source requires Java development kit v1.8 or greater and Maven 3 (3.0.5+).
 
-At compile time, Maven should fetch all needed dependencies, which are:
+At compile time, Maven should fetch all engine needed dependencies, which are:
 
 * commons-lang v3.9
 * slf4j-api v1.7.30
diff --git a/pom.xml b/pom.xml
index 5e0065d..c1cc53a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -234,6 +234,7 @@
         <module>velocity-engine-examples</module>
         <module>velocity-engine-scripting</module>
         <module>velocity-custom-parser-example</module>
+        <module>spring-velocity-support</module>
     </modules>
 
     <!-- This project is an effort by many people. If you feel that your name
diff --git a/spring-velocity-support/pom.xml b/spring-velocity-support/pom.xml
new file mode 100644
index 0000000..f4701ff
--- /dev/null
+++ b/spring-velocity-support/pom.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you 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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.velocity</groupId>
+        <artifactId>velocity-engine-parent</artifactId>
+        <version>2.3-SNAPSHOT</version>
+    </parent>
+    <artifactId>spring-velocity-support</artifactId>
+    <name>Spring framework Velocity support</name>
+    <description>Velocity Engine factory bean for Spring framework</description>
+    <properties>
+        <springframework.version>5.2.6.RELEASE</springframework.version>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.velocity</groupId>
+            <artifactId>velocity-engine-core</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-core</artifactId>
+            <version>${springframework.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-beans</artifactId>
+            <version>${springframework.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context</artifactId>
+            <version>${springframework.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>${junit.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <version>${slf4j.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-source-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <version>${surefire.plugin.version}</version>
+                <configuration>
+                    <systemProperties>
+                        <property>
+                            <name>test.resources.dir</name>
+                            <value>${project.build.testOutputDirectory}</value>
+                        </property>
+                    </systemProperties>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>integration-test</id>
+                        <phase>integration-test</phase>
+                        <goals>
+                            <goal>test</goal>
+                        </goals>
+                        <configuration>
+                            <skip>false</skip>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/spring-velocity-support/src/main/java/org/apache/velocity/spring/SpringResourceLoader.java b/spring-velocity-support/src/main/java/org/apache/velocity/spring/SpringResourceLoader.java
new file mode 100644
index 0000000..befe4c5
--- /dev/null
+++ b/spring-velocity-support/src/main/java/org/apache/velocity/spring/SpringResourceLoader.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2002-2012 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
+ *
+ *      https://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.apache.velocity.spring;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.Arrays;
+
+import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.velocity.runtime.resource.Resource;
+import org.apache.velocity.runtime.resource.loader.ResourceLoader;
+
+import org.apache.velocity.util.ExtProperties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.StringUtils;
+
+/**
+ * Velocity ResourceLoader adapter that loads via a Spring ResourceLoader.
+ * Used by VelocityEngineFactory for any resource loader path that cannot
+ * be resolved to a {@code java.io.File}.
+ *
+ * <p>Note that this loader does not allow for modification detection:
+ * Use Velocity's default FileResourceLoader for {@code java.io.File}
+ * resources.
+ *
+ * <p>Expects "spring.resource.loader" and "spring.resource.loader.path"
+ * application attributes in the Velocity runtime: the former of type
+ * {@code org.springframework.core.io.ResourceLoader}, the latter a String.
+ *
+ * @author Juergen Hoeller
+ * @author Claude Brisson
+ * @since 2020-05-29
+ * @see VelocityEngineFactory#setResourceLoaderPath
+ * @see org.springframework.core.io.ResourceLoader
+ * @see org.apache.velocity.runtime.resource.loader.FileResourceLoader
+ */
+public class SpringResourceLoader extends ResourceLoader {
+
+	public static final String NAME = "spring";
+
+	public static final String SPRING_RESOURCE_LOADER_CLASS = "spring.resource.loader.class";
+
+	public static final String SPRING_RESOURCE_LOADER_CACHE = "spring.resource.loader.cache";
+
+	public static final String SPRING_RESOURCE_LOADER = "spring.resource.loader";
+
+	public static final String SPRING_RESOURCE_LOADER_PATH = "spring.resource.loader.path";
+
+
+	protected final Logger logger = LoggerFactory.getLogger(getClass());
+
+	private org.springframework.core.io.ResourceLoader resourceLoader;
+
+	private String[] resourceLoaderPaths;
+
+
+	@Override
+	public void init(ExtProperties configuration) {
+		this.resourceLoader = (org.springframework.core.io.ResourceLoader)
+				this.rsvc.getApplicationAttribute(SPRING_RESOURCE_LOADER);
+		String resourceLoaderPath = (String) this.rsvc.getApplicationAttribute(SPRING_RESOURCE_LOADER_PATH);
+		if (this.resourceLoader == null) {
+			throw new IllegalArgumentException(
+					"'resourceLoader' application attribute must be present for SpringResourceLoader");
+		}
+		if (resourceLoaderPath == null) {
+			throw new IllegalArgumentException(
+					"'resourceLoaderPath' application attribute must be present for SpringResourceLoader");
+		}
+		this.resourceLoaderPaths = StringUtils.commaDelimitedListToStringArray(resourceLoaderPath);
+		for (int i = 0; i < this.resourceLoaderPaths.length; i++) {
+			String path = this.resourceLoaderPaths[i];
+			if (!path.endsWith("/")) {
+				this.resourceLoaderPaths[i] = path + "/";
+			}
+		}
+		if (logger.isInfoEnabled()) {
+			logger.info("SpringResourceLoader for Velocity: using resource loader [" + this.resourceLoader +
+					"] and resource loader paths " + Arrays.asList(this.resourceLoaderPaths));
+		}
+	}
+
+	/**
+	 * Get the Reader that the Runtime will parse
+	 * to create a template.
+	 *
+	 * @param source resource name
+	 * @param encoding resource encoding
+	 * @return The reader for the requested resource.
+	 * @throws ResourceNotFoundException
+	 * @since 2.0
+	 */
+	@Override
+	public Reader getResourceReader(String source, String encoding) throws ResourceNotFoundException {
+		if (logger.isDebugEnabled()) {
+			logger.debug("Looking for Velocity resource with name [" + source + "]");
+		}
+		for (String resourceLoaderPath : this.resourceLoaderPaths) {
+			org.springframework.core.io.Resource resource =
+					this.resourceLoader.getResource(resourceLoaderPath + source);
+			try {
+				return new InputStreamReader(resource.getInputStream(), encoding);
+			}
+			catch (IOException ex) {
+				if (logger.isDebugEnabled()) {
+					logger.debug("Could not find Velocity resource: " + resource);
+				}
+			}
+		}
+		throw new ResourceNotFoundException(
+				"Could not find resource [" + source + "] in Spring resource loader path");
+	}
+
+	@Override
+	public boolean isSourceModified(Resource resource) {
+		return false;
+	}
+
+	@Override
+	public long getLastModified(Resource resource) {
+		return 0;
+	}
+
+}
diff --git a/spring-velocity-support/src/main/java/org/apache/velocity/spring/VelocityEngineFactory.java b/spring-velocity-support/src/main/java/org/apache/velocity/spring/VelocityEngineFactory.java
new file mode 100644
index 0000000..6b3ee25
--- /dev/null
+++ b/spring-velocity-support/src/main/java/org/apache/velocity/spring/VelocityEngineFactory.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ *      https://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.apache.velocity.spring;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.runtime.RuntimeConstants;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+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>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
+ * @author Claude Brisson
+ * @since 2020-05-29
+ * @see #setConfigLocation
+ * @see #setVelocityProperties
+ * @see #setResourceLoaderPath
+ * @see #createVelocityEngine
+ * @see VelocityEngineFactoryBean
+ * @see org.springframework.web.servlet.view.velocity.VelocityConfigurer
+ * @see org.apache.velocity.app.VelocityEngine
+ */
+public class VelocityEngineFactory {
+
+	protected static final Logger logger = LoggerFactory.getLogger(VelocityEngineFactory.class);
+
+	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},
+	 * 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;
+	}
+
+	/**
+	 * 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);
+		}
+
+		// Apply properties to VelocityEngine.
+		for (Map.Entry<String, Object> entry : props.entrySet()) {
+			velocityEngine.setProperty(entry.getKey(), entry.getValue());
+		}
+
+		postProcessVelocityEngine(velocityEngine);
+
+		// Perform actual initialization.
+		velocityEngine.init();
+
+		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()}.
+	 * @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()}.
+	 * @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}.
+	 * @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 perform custom
+	 * post-processing of the VelocityEngine after this FactoryBean
+	 * performed its default configuration (but before VelocityEngine.init).
+	 * <p>Called by {@code createVelocityEngine()}.
+	 * @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 {
+	}
+
+}
diff --git a/spring-velocity-support/src/main/java/org/apache/velocity/spring/VelocityEngineFactoryBean.java b/spring-velocity-support/src/main/java/org/apache/velocity/spring/VelocityEngineFactoryBean.java
new file mode 100644
index 0000000..7705aab
--- /dev/null
+++ b/spring-velocity-support/src/main/java/org/apache/velocity/spring/VelocityEngineFactoryBean.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2002-2012 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
+ *
+ *      https://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.apache.velocity.spring;
+
+import java.io.IOException;
+
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.exception.VelocityException;
+
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.ResourceLoaderAware;
+
+/**
+ * Factory bean that configures a VelocityEngine and provides it as bean
+ * reference. This bean is intended for any kind of usage of Velocity in
+ * application code, e.g. for generating email content. For web views,
+ * VelocityConfigurer is used to set up a VelocityEngine for views.
+ *
+ * <p>The simplest way to use this class is to specify a "resourceLoaderPath";
+ * you do not need any further configuration then. For example, in a web
+ * application context:
+ *
+ * <pre class="code"> &lt;bean id="velocityEngine" class="org.springframework.ui.velocity.VelocityEngineFactoryBean"&gt;
+ *   &lt;property name="resourceLoaderPath" value="/WEB-INF/velocity/"/&gt;
+ * &lt;/bean&gt;</pre>
+ *
+ * See the base class VelocityEngineFactory for configuration details.
+ *
+ * @author Juergen Hoeller
+ * @author Claude Brisson
+ * @since 2020-05-29
+ * @see #setConfigLocation
+ * @see #setVelocityProperties
+ * @see #setResourceLoaderPath
+ * @see org.springframework.web.servlet.view.velocity.VelocityConfigurer
+ */
+public class VelocityEngineFactoryBean extends VelocityEngineFactory
+		implements FactoryBean<VelocityEngine>, InitializingBean, ResourceLoaderAware {
+
+	private VelocityEngine velocityEngine;
+
+
+	@Override
+	public void afterPropertiesSet() throws IOException, VelocityException {
+		this.velocityEngine = createVelocityEngine();
+	}
+
+
+	@Override
+	public VelocityEngine getObject() {
+		return this.velocityEngine;
+	}
+
+	@Override
+	public Class<? extends VelocityEngine> getObjectType() {
+		return VelocityEngine.class;
+	}
+
+	@Override
+	public boolean isSingleton() {
+		return true;
+	}
+
+}
diff --git a/spring-velocity-support/src/main/java/org/apache/velocity/spring/VelocityEngineUtils.java b/spring-velocity-support/src/main/java/org/apache/velocity/spring/VelocityEngineUtils.java
new file mode 100644
index 0000000..7e0be72
--- /dev/null
+++ b/spring-velocity-support/src/main/java/org/apache/velocity/spring/VelocityEngineUtils.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2002-2013 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
+ *
+ *      https://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.apache.velocity.spring;
+
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.Map;
+
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.exception.VelocityException;
+
+/**
+ * Utility class for working with a VelocityEngine.
+ * Provides convenience methods to merge a Velocity template with a model.
+ *
+ * @author Juergen Hoeller
+ * @author Claude Brisson
+ * @since 2020-05-29
+ */
+public abstract class VelocityEngineUtils {
+
+	/**
+	 * Merge the specified Velocity template with the given model and write
+	 * the result to the given Writer.
+	 * @param velocityEngine VelocityEngine to work with
+	 * @param templateLocation the location of template, relative to Velocity's resource loader path
+	 * @param model the Map that contains model names as keys and model objects as values
+	 * @param writer the Writer to write the result to
+	 * @throws VelocityException if the template wasn't found or rendering failed
+	 * @deprecated Use {@link #mergeTemplate(VelocityEngine, String, String, Map, Writer)}
+	 * instead, following Velocity 1.6's corresponding deprecation in its own API.
+	 */
+	@Deprecated
+	public static void mergeTemplate(
+			VelocityEngine velocityEngine, String templateLocation, Map<String, Object> model, Writer writer)
+			throws VelocityException {
+		mergeTemplate(velocityEngine, templateLocation, null, writer);
+	}
+
+	/**
+	 * Merge the specified Velocity template with the given model and write the result
+	 * to the given Writer.
+	 * @param velocityEngine VelocityEngine to work with
+	 * @param templateLocation the location of template, relative to Velocity's resource loader path
+	 * @param encoding the encoding of the template file
+	 * @param model the Map that contains model names as keys and model objects as values
+	 * @param writer the Writer to write the result to
+	 * @throws VelocityException if the template wasn't found or rendering failed
+	 */
+	public static void mergeTemplate(
+			VelocityEngine velocityEngine, String templateLocation, String encoding,
+			Map<String, Object> model, Writer writer) throws VelocityException {
+
+		VelocityContext velocityContext = new VelocityContext(model);
+		velocityEngine.mergeTemplate(templateLocation, encoding, velocityContext, writer);
+	}
+
+	/**
+	 * Merge the specified Velocity template with the given model into a String.
+	 * <p>When using this method to prepare a text for a mail to be sent with Spring's
+	 * mail support, consider wrapping VelocityException in MailPreparationException.
+	 * @param velocityEngine VelocityEngine to work with
+	 * @param templateLocation the location of template, relative to Velocity's resource loader path
+	 * @param model the Map that contains model names as keys and model objects as values
+	 * @return the result as String
+	 * @throws VelocityException if the template wasn't found or rendering failed
+	 * @see org.springframework.mail.MailPreparationException
+	 * @deprecated Use {@link #mergeTemplateIntoString(VelocityEngine, String, String, Map)}
+	 * instead, following Velocity 1.6's corresponding deprecation in its own API.
+	 */
+	@Deprecated
+	public static String mergeTemplateIntoString(VelocityEngine velocityEngine, String templateLocation,
+			Map<String, Object> model) throws VelocityException {
+		return mergeTemplateIntoString(velocityEngine, templateLocation, null, model);
+	}
+
+	/**
+	 * Merge the specified Velocity template with the given model into a String.
+	 * <p>When using this method to prepare a text for a mail to be sent with Spring's
+	 * mail support, consider wrapping VelocityException in MailPreparationException.
+	 * @param velocityEngine VelocityEngine to work with
+	 * @param templateLocation the location of template, relative to Velocity's resource loader path
+	 * @param encoding the encoding of the template file
+	 * @param model the Map that contains model names as keys and model objects as values
+	 * @return the result as String
+	 * @throws VelocityException if the template wasn't found or rendering failed
+	 * @see org.springframework.mail.MailPreparationException
+	 */
+	public static String mergeTemplateIntoString(VelocityEngine velocityEngine, String templateLocation,
+			String encoding, Map<String, Object> model) throws VelocityException {
+
+		StringWriter result = new StringWriter();
+		mergeTemplate(velocityEngine, templateLocation, encoding, model, result);
+		return result.toString();
+	}
+
+}
diff --git a/spring-velocity-support/src/test/java/org/apache/velocity/spring/test/VelocityEngineFactoryBeanTests.java b/spring-velocity-support/src/test/java/org/apache/velocity/spring/test/VelocityEngineFactoryBeanTests.java
new file mode 100644
index 0000000..816af21
--- /dev/null
+++ b/spring-velocity-support/src/test/java/org/apache/velocity/spring/test/VelocityEngineFactoryBeanTests.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2002-2019 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
+ *
+ *      https://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.apache.velocity.spring.test;
+
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.spring.SpringResourceLoader;
+import org.apache.velocity.spring.VelocityEngineFactoryBean;
+import org.apache.velocity.spring.VelocityEngineUtils;
+import org.junit.Test;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.core.io.*;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author Juergen Hoeller
+ * @author Issam El-atif
+ * @author Sam Brannen
+ */
+public class VelocityEngineFactoryBeanTests
+{
+	private static final String resourcesPath = System.getProperty("test.resources.dir");
+	private final VelocityEngineFactoryBean vefb = new VelocityEngineFactoryBean();
+
+	@Test
+	public void velocityFactoryBeanWithConfigLocation() throws Exception {
+		vefb.setConfigLocation(new ClassPathResource("velocity.properties"));
+		vefb.afterPropertiesSet();
+		VelocityEngine engine = vefb.getObject();
+		assertEquals("bean config location failed", "bar", engine.getProperty("foo"));
+	}
+
+	@Test
+	public void velocityFactoryBeanWithResourceLoaderPath() throws Exception {
+		vefb.setResourceLoaderPath("file:" + resourcesPath);
+		vefb.afterPropertiesSet();
+		VelocityEngine engine = vefb.getObject();
+		Map<String, Object> model = new HashMap<String, Object>();
+		model.put("foo", "bar");
+		String merged = VelocityEngineUtils.mergeTemplateIntoString(engine, "simple.vm", "utf-8", model).trim();
+		assertEquals("resource loader failed", "foo=bar", merged);
+	}
+
+	@Test  // SPR-12448
+	public void velocityConfigurationAsBean() {
+		DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
+		RootBeanDefinition loaderDef = new RootBeanDefinition(SpringResourceLoader.class);
+		loaderDef.getConstructorArgumentValues().addGenericArgumentValue(new DefaultResourceLoader());
+		loaderDef.getConstructorArgumentValues().addGenericArgumentValue("/freemarker");
+		// RootBeanDefinition configDef = new RootBeanDefinition(Configuration.class);
+		//configDef.getPropertyValues().add("templateLoader", loaderDef);
+		//beanFactory.registerBeanDefinition("freeMarkerConfig", configDef);
+		// assertThat(beanFactory.getBean(Configuration.class)).isNotNull();
+	}
+
+}
diff --git a/spring-velocity-support/src/test/java/org/apache/velocity/spring/test/VelocityEngineFactoryTests.java b/spring-velocity-support/src/test/java/org/apache/velocity/spring/test/VelocityEngineFactoryTests.java
new file mode 100644
index 0000000..68b440b
--- /dev/null
+++ b/spring-velocity-support/src/test/java/org/apache/velocity/spring/test/VelocityEngineFactoryTests.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2002-2019 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
+ *
+ *      https://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.apache.velocity.spring.test;
+
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.spring.VelocityEngineFactory;
+import org.apache.velocity.spring.VelocityEngineUtils;
+import org.junit.Test;
+import org.springframework.core.io.ClassPathResource;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author Juergen Hoeller
+ * @author Issam El-atif
+ * @author Sam Brannen
+ */
+public class VelocityEngineFactoryTests {
+
+	private static final String resourcesPath = System.getProperty("test.resources.dir");
+
+	@Test
+	public void testCreateEngineDefaultFileLoader() throws Exception {
+		VelocityEngineFactory factory = new VelocityEngineFactory();
+		factory.setResourceLoaderPath("."); // defaults to target/test-classes file resource loading
+		VelocityEngine engine = factory.createVelocityEngine();
+		Map<String, Object> model = new HashMap<String, Object>();
+		model.put("foo", "bar");
+		String merged = VelocityEngineUtils.mergeTemplateIntoString(engine, "simple.vm", "utf-8", model).trim();
+		assertEquals("file loader failed", "foo=bar", merged);
+	}
+
+	@Test
+	public void testCreateEngineDefaultClasspathLoader() throws Exception {
+		VelocityEngineFactory factory = new VelocityEngineFactory();
+		factory.setResourceLoaderPath("/"); // defaults to classpath resource loading
+		VelocityEngine engine = factory.createVelocityEngine();
+		Map<String, Object> model = new HashMap<String, Object>();
+		model.put("foo", "bar");
+		String merged = VelocityEngineUtils.mergeTemplateIntoString(engine, "simple.vm", "utf-8", model).trim();
+		assertEquals("classpath loader failed", "foo=bar", merged);
+	}
+
+	@Test
+	public void testCreateEngineCustomConfig() throws Exception {
+		VelocityEngineFactory factory = new VelocityEngineFactory();
+		factory.setResourceLoaderPath(".");
+		factory.setConfigLocation(new ClassPathResource("/velocity.properties"));
+		VelocityEngine engine = factory.createVelocityEngine();
+		assertEquals("custom config failed", "bar", engine.getProperty("foo"));
+	}
+
+}
diff --git a/spring-velocity-support/src/test/resources/simple.vm b/spring-velocity-support/src/test/resources/simple.vm
new file mode 100644
index 0000000..c9f453e
--- /dev/null
+++ b/spring-velocity-support/src/test/resources/simple.vm
@@ -0,0 +1 @@
+foo=$foo
diff --git a/spring-velocity-support/src/test/resources/velocity.properties b/spring-velocity-support/src/test/resources/velocity.properties
new file mode 100644
index 0000000..8e09e19
--- /dev/null
+++ b/spring-velocity-support/src/test/resources/velocity.properties
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you 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.
+
+foo = bar
+