diff --git a/pom.xml b/pom.xml
index 62c994fc7d..713646e3a2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -146,6 +146,7 @@
velocity-engine-scripting
velocity-custom-parser-example
spring-velocity-support
+ spring6-velocity-support
+
+ 4.0.0
+
+ org.apache.velocity
+ velocity-engine-parent
+ 2.5-SNAPSHOT
+
+ spring6-velocity-support
+ Spring 6 framework Velocity support
+ Velocity Engine factory bean for Spring 6 framework
+
+ 17
+ 6.2.12
+
+
+
+
+ org.springframework
+ spring-framework-bom
+ ${spring.version}
+ pom
+ import
+
+
+
+
+
+ org.apache.velocity
+ velocity-engine-core
+ ${project.version}
+
+
+ org.springframework
+ spring-core
+
+
+ org.springframework
+ spring-beans
+
+
+ org.springframework
+ spring-context
+
+
+ junit
+ junit
+ test
+
+
+ org.slf4j
+ slf4j-simple
+ test
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+
+ test.resources.dir
+ ${project.build.testOutputDirectory}
+
+
+
+
+
+ integration-test
+ integration-test
+
+ test
+
+
+ false
+
+
+
+
+
+ org.apache.felix
+ maven-bundle-plugin
+
+
+
+
diff --git a/spring6-velocity-support/src/main/java/org/apache/velocity/spring/SpringResourceLoader.java b/spring6-velocity-support/src/main/java/org/apache/velocity/spring/SpringResourceLoader.java
new file mode 100644
index 0000000000..dcd1662baf
--- /dev/null
+++ b/spring6-velocity-support/src/main/java/org/apache/velocity/spring/SpringResourceLoader.java
@@ -0,0 +1,154 @@
+/*
+ * 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}.
+ *
+ *
Note that this loader does not allow for modification detection:
+ * Use Velocity's default FileResourceLoader for {@code java.io.File}
+ * resources.
+ *
+ *
Expects "resource.loader.spring.instance" and "resource.loader.spring.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 = "resource.loader.spring.class";
+
+ public static final String SPRING_RESOURCE_LOADER_CACHE = "resource.loader.spring.cache";
+
+ public static final String SPRING_RESOURCE_LOADER_INSTANCE = "resource.loader.spring.instance";
+
+ public static final String SPRING_RESOURCE_LOADER_PATH = "resource.loader.spring.path";
+
+ // Deprecated constants
+ public static final String DEPRECATED_SPRING_RESOURCE_LOADER_INSTANCE = "spring.resource.loader";
+ public static final String DEPRECATED_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_INSTANCE);
+ if (this.resourceLoader == null) {
+ this.resourceLoader = (org.springframework.core.io.ResourceLoader)
+ this.rsvc.getApplicationAttribute(DEPRECATED_SPRING_RESOURCE_LOADER_INSTANCE);
+ if (this.resourceLoader != null) {
+ logger.warn("Deprecated property: 'spring.resource.loader'. Please use 'resource.loader.spring.instance' instead.");
+ }
+ }
+ String resourceLoaderPath = (String) this.rsvc.getApplicationAttribute(SPRING_RESOURCE_LOADER_PATH);
+ if (resourceLoaderPath == null) {
+ resourceLoaderPath = (String) this.rsvc.getApplicationAttribute(DEPRECATED_SPRING_RESOURCE_LOADER_PATH);
+ if (resourceLoaderPath != null) {
+ logger.warn("Deprecated property: 'spring.resource.loader.path'. Please use 'resource.loader.spring.path' instead.");
+ }
+ }
+ 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 [{}] and resource loader paths {}",
+ resourceLoader, 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 {
+ 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 {
+ if (resource != null) {
+ return new InputStreamReader(resource.getInputStream(), encoding);
+ }
+ }
+ catch (IOException ex) {
+ 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/spring6-velocity-support/src/main/java/org/apache/velocity/spring/VelocityEngineFactory.java b/spring6-velocity-support/src/main/java/org/apache/velocity/spring/VelocityEngineFactory.java
new file mode 100644
index 0000000000..568358903e
--- /dev/null
+++ b/spring6-velocity-support/src/main/java/org/apache/velocity/spring/VelocityEngineFactory.java
@@ -0,0 +1,381 @@
+/*
+ * 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.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+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
+ * org.springframework.web.servlet.view.velocity.VelocityConfigurer
+ * for web views.
+ *
+ *
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.
+ *
+ *
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.
+ *
+ *
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 velocityProperties = new HashMap();
+
+ private String resourceLoaderPath;
+
+ private ResourceLoader resourceLoader = new DefaultResourceLoader();
+
+ private boolean preferFileSystemAccess = true;
+
+
+ /**
+ * Set the location of the Velocity config file.
+ * Alternatively, you can specify all properties locally.
+ *
+ * @param configLocation config resource
+ *
+ * @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.
+ *
+ * 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.
+ *
+ * @param velocityProperties engine properties to include
+ *
+ * @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".
+ *
+ * @param velocityPropertiesMap engine properties to include
+ *
+ * @see #setVelocityProperties
+ */
+ public void setVelocityPropertiesMap(Map 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.
+ * 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.
+ *
+ *
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.
+ *
+ * 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).
+ *
+ * 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.
+ *
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.
+ *
+ * @param resourceLoaderPath comma-separated resource paths
+ *
+ * @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.
+ *
+ * @param resourceLoader Spring resource loader to ue
+ *
+ * @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.
+ *
+ * @return Spring resource loader to use
+ */
+ 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.
+ *
+ * If this is enabled, VelocityEngineFactory will try to resolve the
+ * specified "resourceLoaderPath" as file system resources, but only when
+ * non-classpath resource paths are included.
+ *
+ * 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.
+ *
+ * @param preferFileSystemAccess whether to rely on file-based loading when possible
+ *
+ * @see #setResourceLoaderPath
+ */
+ public void setPreferFileSystemAccess(boolean preferFileSystemAccess) {
+ this.preferFileSystemAccess = preferFileSystemAccess;
+ }
+
+ /**
+ * Return whether to prefer file system access for template loading.
+ *
+ * @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 props = new HashMap();
+
+ // Load config file if set.
+ if (this.configLocation != null) {
+ 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 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.
+ * 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.
+ *
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) {
+
+ final ResourceLoader loader = getResourceLoader();
+ if (loader != null && isPreferFileSystemAccess()) {
+
+ // Try to load via the file system, fall back to SpringResourceLoader
+ // (for hot detection of template changes, if possible).
+
+ final List filePaths = new ArrayList<>();
+ final List nonFilePaths = new ArrayList<>();
+
+ String[] paths = StringUtils.commaDelimitedListToStringArray(resourceLoaderPath);
+
+ // Try to load via the file system, fall back to SpringResourceLoader
+ // (for hot detection of template changes, if possible).
+ for (int i = 0; i < paths.length; i++) {
+ String path = paths[i];
+
+ // Don't check classpath: locations for existence, they're not file-based.
+ // Some containers will expand jars and trigger false positives.
+ if (path.startsWith(ResourceLoader.CLASSPATH_URL_PREFIX)) {
+ logger.debug("Using SpringResourceLoader for '{}'", path);
+ nonFilePaths.add(path);
+ continue;
+ }
+
+ try {
+ Resource resource = loader.getResource(path);
+ File file = resource.getFile(); // will fail if not resolvable in the file system
+
+ logger.debug("Resource loader path [{}] resolved to file [{}]", path, file.getAbsolutePath());
+ filePaths.add(file.getAbsolutePath());
+ }
+ catch (IOException ex) {
+ logger.debug("Cannot resolve resource loader path '{}' to filesystem, will use SpringResourceLoader", path, ex);
+ nonFilePaths.add(path);
+ }
+ }
+
+ if (!filePaths.isEmpty()) {
+ velocityEngine.setProperty(RuntimeConstants.RESOURCE_LOADERS, "file");
+ velocityEngine.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_CACHE, "true");
+ velocityEngine.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH,
+ StringUtils.collectionToCommaDelimitedString(filePaths));
+ }
+
+ if (!nonFilePaths.isEmpty()) {
+ initSpringResourceLoader(velocityEngine, StringUtils.collectionToCommaDelimitedString(nonFilePaths));
+ }
+ }
+ else {
+ // Always load via SpringResourceLoader
+ // (without hot detection of template changes).
+ logger.debug("File system access not preferred: using SpringResourceLoader");
+ initSpringResourceLoader(velocityEngine, resourceLoaderPath);
+ }
+ }
+
+ /**
+ * Initialize a SpringResourceLoader for the given VelocityEngine.
+ * 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.addProperty(
+ RuntimeConstants.RESOURCE_LOADERS, 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_INSTANCE, 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).
+ *
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/spring6-velocity-support/src/main/java/org/apache/velocity/spring/VelocityEngineFactoryBean.java b/spring6-velocity-support/src/main/java/org/apache/velocity/spring/VelocityEngineFactoryBean.java
new file mode 100644
index 0000000000..b84d99872c
--- /dev/null
+++ b/spring6-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.
+ *
+ *
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:
+ *
+ *
<bean id="velocityEngine" class="org.springframework.ui.velocity.VelocityEngineFactoryBean">
+ * <property name="resourceLoaderPath" value="/WEB-INF/velocity/"/>
+ * </bean>
+ *
+ * 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, 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/spring6-velocity-support/src/main/java/org/apache/velocity/spring/VelocityEngineUtils.java b/spring6-velocity-support/src/main/java/org/apache/velocity/spring/VelocityEngineUtils.java
new file mode 100644
index 0000000000..dba341082e
--- /dev/null
+++ b/spring6-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 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 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.
+ * 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 model) throws VelocityException {
+ return mergeTemplateIntoString(velocityEngine, templateLocation, null, model);
+ }
+
+ /**
+ * Merge the specified Velocity template with the given model into a String.
+ * 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 model) throws VelocityException {
+
+ StringWriter result = new StringWriter();
+ mergeTemplate(velocityEngine, templateLocation, encoding, model, result);
+ return result.toString();
+ }
+
+}
diff --git a/spring6-velocity-support/src/test/java/org/apache/velocity/spring/test/VelocityEngineFactoryBeanTests.java b/spring6-velocity-support/src/test/java/org/apache/velocity/spring/test/VelocityEngineFactoryBeanTests.java
new file mode 100644
index 0000000000..ad814ca058
--- /dev/null
+++ b/spring6-velocity-support/src/test/java/org/apache/velocity/spring/test/VelocityEngineFactoryBeanTests.java
@@ -0,0 +1,74 @@
+/*
+ * 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 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 model = new HashMap();
+ 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/spring6-velocity-support/src/test/java/org/apache/velocity/spring/test/VelocityEngineFactoryTests.java b/spring6-velocity-support/src/test/java/org/apache/velocity/spring/test/VelocityEngineFactoryTests.java
new file mode 100644
index 0000000000..46c0fc83ec
--- /dev/null
+++ b/spring6-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 model = new HashMap();
+ 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 model = new HashMap();
+ 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/spring6-velocity-support/src/test/resources/simple.vm b/spring6-velocity-support/src/test/resources/simple.vm
new file mode 100644
index 0000000000..c9f453e001
--- /dev/null
+++ b/spring6-velocity-support/src/test/resources/simple.vm
@@ -0,0 +1 @@
+foo=$foo
diff --git a/spring6-velocity-support/src/test/resources/velocity.properties b/spring6-velocity-support/src/test/resources/velocity.properties
new file mode 100644
index 0000000000..8e09e19765
--- /dev/null
+++ b/spring6-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
+
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 111b9af9f9..d1acf7c0f9 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -39,6 +39,11 @@
+
+ Add a new spring6-velocity-support sibling module targeting
+ Spring 6.x and Java 17. The legacy spring-velocity-support
+ module remains in place for Spring 5.x on Java 8.
+
Add a new introspector.restrict.methods property to restrict
method calls in the secure introspector. Block by default