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 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