Support Velocity toolbox configurations from jar

Create an EmbeddedVelocityToolboxView which supports loading toolbox.xml
files from the application classpath as well as the ServletContext. The
VelocityAutoConfiguration class has been updated to use the new view.

This change allows the `spring.velocity.toolbox-config-location`
property to work with embedded servlet containers.

Fixes gh-2912
This commit is contained in:
Phillip Webb 2015-06-15 18:04:46 -07:00
parent 1cfc6f64f6
commit cca0b76ac8
9 changed files with 412 additions and 6 deletions

View File

@ -35,6 +35,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat
import org.springframework.boot.autoconfigure.template.TemplateLocation;
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.view.velocity.EmbeddedVelocityViewResolver;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -128,7 +129,7 @@ public class VelocityAutoConfiguration {
@ConditionalOnMissingBean(name = "velocityViewResolver")
@ConditionalOnProperty(name = "spring.velocity.enabled", matchIfMissing = true)
public VelocityViewResolver velocityViewResolver() {
VelocityViewResolver resolver = new VelocityViewResolver();
EmbeddedVelocityViewResolver resolver = new EmbeddedVelocityViewResolver();
this.properties.applyToViewResolver(resolver);
return resolver;
}

View File

@ -30,6 +30,7 @@ import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.boot.web.servlet.view.velocity.EmbeddedVelocityViewResolver;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
@ -42,6 +43,7 @@ import org.springframework.web.servlet.view.velocity.VelocityViewResolver;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
@ -136,7 +138,7 @@ public class VelocityAutoConfigurationTests {
}
@Test
public void customFreeMarkerSettings() {
public void customVelocitySettings() {
registerAndRefreshContext("spring.velocity.properties.directive.parse.max.depth:10");
assertThat(this.context.getBean(VelocityConfigurer.class).getVelocityEngine()
.getProperty("directive.parse.max.depth"), equalTo((Object) "10"));
@ -174,6 +176,13 @@ public class VelocityAutoConfigurationTests {
}
}
@Test
public void usesEmbeddedVelocityViewResolver() {
registerAndRefreshContext("spring.velocity.toolbox:/toolbox.xml");
VelocityViewResolver resolver = this.context.getBean(VelocityViewResolver.class);
assertThat(resolver, instanceOf(EmbeddedVelocityViewResolver.class));
}
private void registerAndRefreshContext(String... env) {
EnvironmentTestUtils.addEnvironment(this.context, env);
this.context.register(VelocityAutoConfiguration.class);

View File

@ -114,6 +114,16 @@
<artifactId>tomcat-embed-logging-juli</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-tools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.codehaus.btm</groupId>
<artifactId>btm</artifactId>
@ -184,6 +194,11 @@
<artifactId>spring-web</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
@ -202,13 +217,13 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

View File

@ -0,0 +1,111 @@
/*
* Copyright 2012-2015 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
*
* 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.
*/
package org.springframework.boot.web.servlet.view.velocity;
import java.io.InputStream;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.context.Context;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.web.servlet.view.velocity.VelocityToolboxView;
/**
* Extended version of {@link VelocityToolboxView} that can load toolbox locations from
* the classpath as well as the servlet context. This is useful when run an embedded web
* server.
*
* @author Phillip Webb
* @since 1.2.5
*/
@SuppressWarnings("deprecation")
public class EmbeddedVelocityToolboxView extends VelocityToolboxView {
@Override
protected Context createVelocityContext(Map<String, Object> model,
HttpServletRequest request, HttpServletResponse response) throws Exception {
org.apache.velocity.tools.view.context.ChainedContext context = new org.apache.velocity.tools.view.context.ChainedContext(
new VelocityContext(model), getVelocityEngine(), request, response,
getServletContext());
if (getToolboxConfigLocation() != null) {
setContextToolbox(context);
}
return context;
}
@SuppressWarnings("unchecked")
private void setContextToolbox(
org.apache.velocity.tools.view.context.ChainedContext context) {
org.apache.velocity.tools.view.ToolboxManager toolboxManager = org.apache.velocity.tools.view.servlet.ServletToolboxManager
.getInstance(getToolboxConfigFileAwareServletContext(),
getToolboxConfigLocation());
Map<String, Object> toolboxContext = toolboxManager.getToolbox(context);
context.setToolbox(toolboxContext);
}
private ServletContext getToolboxConfigFileAwareServletContext() {
ProxyFactory factory = new ProxyFactory();
factory.setTarget(getServletContext());
factory.addAdvice(new GetResourceMethodInterceptor(getToolboxConfigLocation()));
return (ServletContext) factory.getProxy();
}
/**
* {@link MethodInterceptor} to allow the calls to getResourceAsStream() to resolve
* the toolboxFile from the classpath.
*/
private static class GetResourceMethodInterceptor implements MethodInterceptor {
private final String toolboxFile;
public GetResourceMethodInterceptor(String toolboxFile) {
if (toolboxFile != null && !toolboxFile.startsWith("/")) {
toolboxFile = "/" + toolboxFile;
}
this.toolboxFile = toolboxFile;
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
if (invocation.getMethod().getName().equals("getResourceAsStream")
&& invocation.getArguments()[0].equals(this.toolboxFile)) {
InputStream inputStream = (InputStream) invocation.proceed();
if (inputStream == null) {
try {
inputStream = new ClassPathResource(this.toolboxFile, Thread
.currentThread().getContextClassLoader())
.getInputStream();
}
catch (Exception ex) {
// Ignore
}
}
return inputStream;
}
return invocation.proceed();
}
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2012-2015 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
*
* 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.
*/
package org.springframework.boot.web.servlet.view.velocity;
import org.springframework.web.servlet.view.velocity.VelocityView;
import org.springframework.web.servlet.view.velocity.VelocityViewResolver;
/**
* Extended version of {@link VelocityViewResolver} that uses
* {@link EmbeddedVelocityToolboxView} when the {@link #setToolboxConfigLocation(String)
* toolboxConfigLocation} is set.
*
* @author Phillip Webb
* @since 1.2.5
*/
public class EmbeddedVelocityViewResolver extends VelocityViewResolver {
private String toolboxConfigLocation;
@Override
protected void initApplicationContext() {
if (this.toolboxConfigLocation != null) {
if (VelocityView.class.equals(getViewClass())) {
this.logger.info("Using EmbeddedVelocityToolboxView instead of "
+ "default VelocityView due to specified toolboxConfigLocation");
setViewClass(EmbeddedVelocityToolboxView.class);
}
}
super.initApplicationContext();
}
@Override
public void setToolboxConfigLocation(String toolboxConfigLocation) {
super.setToolboxConfigLocation(toolboxConfigLocation);
this.toolboxConfigLocation = toolboxConfigLocation;
}
}

View File

@ -0,0 +1,19 @@
/*
* Copyright 2012-2015 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
*
* 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.
*/
/**
* @author pwebb
*/
package org.springframework.boot.web.servlet.view.velocity;

View File

@ -0,0 +1,95 @@
/*
* Copyright 2012-2015 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
*
* 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.
*/
package org.springframework.boot.web.servlet.view.velocity;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.mock.MockHttpServletRequest;
import org.apache.struts.mock.MockHttpServletResponse;
import org.apache.struts.mock.MockServletContext;
import org.apache.velocity.tools.ToolContext;
import org.junit.Test;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.view.velocity.VelocityConfigurer;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link EmbeddedVelocityToolboxView}.
*
* @author Phillip Webb
*/
public class EmbeddedVelocityToolboxViewTests {
private static final String PATH = EmbeddedVelocityToolboxViewTests.class
.getPackage().getName().replace(".", "/");
@Test
public void loadsContextFromClassPath() throws Exception {
ToolContext context = getToolContext(PATH + "/toolbox.xml");
assertThat(context.getToolbox().keySet(), contains("math"));
}
@Test
public void loadsWithoutConfig() throws Exception {
ToolContext context = getToolContext(null);
assertThat(context, notNullValue());
}
private ToolContext getToolContext(String toolboxConfigLocation) throws Exception {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.setServletContext(new MockServletContext());
context.register(Config.class);
context.refresh();
EmbeddedVelocityToolboxView view = context
.getBean(EmbeddedVelocityToolboxView.class);
view.setToolboxConfigLocation(toolboxConfigLocation);
Map<String, Object> model = new LinkedHashMap<String, Object>();
HttpServletRequest request = new MockHttpServletRequest();
HttpServletResponse response = new MockHttpServletResponse();
ToolContext toolContext = (ToolContext) view.createVelocityContext(model,
request, response);
context.close();
return toolContext;
}
@Configuration
static class Config {
@Bean
public EmbeddedVelocityToolboxView view() {
EmbeddedVelocityToolboxView view = new EmbeddedVelocityToolboxView();
view.setUrl("http://example.com");
return view;
}
@Bean
public VelocityConfigurer velocityConfigurer() {
return new VelocityConfigurer();
}
}
}

View File

@ -0,0 +1,96 @@
/*
* Copyright 2012-2015 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
*
* 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.
*/
package org.springframework.boot.web.servlet.view.velocity;
import org.apache.struts.mock.MockServletContext;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.view.velocity.VelocityConfigurer;
import org.springframework.web.servlet.view.velocity.VelocityView;
import static org.junit.Assert.assertEquals;
/**
* Tests for {@link EmbeddedVelocityViewResolver}.
*
* @author Phillip Webb
*/
public class EmbeddedVelocityViewResolverTests {
@Test
public void standardViewWithoutToolboxConfig() throws Exception {
ApplicationContext context = loadContext(WithoutToolboxConfig.class);
EmbeddedVelocityViewResolver resolver = context
.getBean(EmbeddedVelocityViewResolver.class);
Object viewClass = ReflectionTestUtils.getField(resolver, "viewClass");
assertEquals(VelocityView.class, viewClass);
}
@Test
public void embeddedViewWithToolboxConfig() throws Exception {
ApplicationContext context = loadContext(WithToolboxConfig.class);
EmbeddedVelocityViewResolver resolver = context
.getBean(EmbeddedVelocityViewResolver.class);
Object viewClass = ReflectionTestUtils.getField(resolver, "viewClass");
assertEquals(EmbeddedVelocityToolboxView.class, viewClass);
}
private ApplicationContext loadContext(Class<?> config) {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.setServletContext(new MockServletContext());
context.register(config);
context.refresh();
return context;
}
@Configuration
static class WithoutToolboxConfig {
@Bean
public EmbeddedVelocityViewResolver resolver() {
return new EmbeddedVelocityViewResolver();
}
@Bean
public VelocityConfigurer velocityConfigurer() {
return new VelocityConfigurer();
}
}
@Configuration
static class WithToolboxConfig {
@Bean
public EmbeddedVelocityViewResolver resolver() {
EmbeddedVelocityViewResolver resolver = new EmbeddedVelocityViewResolver();
resolver.setToolboxConfigLocation("/toolbox.xml");
return resolver;
}
@Bean
public VelocityConfigurer velocityConfigurer() {
return new VelocityConfigurer();
}
}
}

View File

@ -0,0 +1,8 @@
<?xml version="1.0"?>
<toolbox>
<tool>
<key>math</key>
<scope>application</scope>
<class>org.apache.velocity.tools.generic.MathTool</class>
</tool>
</toolbox>