Merge branch 'spring-velocity-support'
diff --git a/README.md b/README.md
index f8b0ec1..ea45099 100644
--- a/README.md
+++ b/README.md
@@ -11,6 +11,7 @@
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 @@
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 bed7163..47d2765 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/README.md b/spring-velocity-support/README.md
new file mode 100644
index 0000000..f7507ae
--- /dev/null
+++ b/spring-velocity-support/README.md
@@ -0,0 +1,22 @@
+Title: Apache Velocity Spring Support
+
+# Apache Velocity Spring Support
+
+This module is an adaptation of the engine support initially hosted by the Spring project in its 3.x versions.
+
+Example configuration:
+
+```xml
+<bean id="velocityEngine"
+ class="org.apache.velocity.spring.VelocityEngineFactoryBean">
+ <property name="velocityProperties">
+ <props>
+ <prop key="resource.loader">class</prop>
+ <prop key="class.resource.loader.class">
+ org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
+ </prop>
+ </props>
+ </property>
+</bean>
+```
+
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"> <bean id="velocityEngine" class="org.springframework.ui.velocity.VelocityEngineFactoryBean">
+ * <property name="resourceLoaderPath" value="/WEB-INF/velocity/"/>
+ * </bean></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
+