From cca0b76ac8b9485a73f0a573a6f9edb08523198c Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Mon, 15 Jun 2015 18:04:46 -0700 Subject: [PATCH] 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 --- .../velocity/VelocityAutoConfiguration.java | 3 +- .../VelocityAutoConfigurationTests.java | 11 +- spring-boot/pom.xml | 23 +++- .../velocity/EmbeddedVelocityToolboxView.java | 111 ++++++++++++++++++ .../EmbeddedVelocityViewResolver.java | 52 ++++++++ .../servlet/view/velocity/package-info.java | 19 +++ .../EmbeddedVelocityToolboxViewTests.java | 95 +++++++++++++++ .../EmbeddedVelocityViewResolverTests.java | 96 +++++++++++++++ .../web/servlet/view/velocity/toolbox.xml | 8 ++ 9 files changed, 412 insertions(+), 6 deletions(-) create mode 100644 spring-boot/src/main/java/org/springframework/boot/web/servlet/view/velocity/EmbeddedVelocityToolboxView.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/web/servlet/view/velocity/EmbeddedVelocityViewResolver.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/web/servlet/view/velocity/package-info.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/web/servlet/view/velocity/EmbeddedVelocityToolboxViewTests.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/web/servlet/view/velocity/EmbeddedVelocityViewResolverTests.java create mode 100644 spring-boot/src/test/resources/org/springframework/boot/web/servlet/view/velocity/toolbox.xml diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/velocity/VelocityAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/velocity/VelocityAutoConfiguration.java index cf2b628995c..7c08177f841 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/velocity/VelocityAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/velocity/VelocityAutoConfiguration.java @@ -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; } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/velocity/VelocityAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/velocity/VelocityAutoConfigurationTests.java index c4682beb05d..2779101f86e 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/velocity/VelocityAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/velocity/VelocityAutoConfigurationTests.java @@ -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); diff --git a/spring-boot/pom.xml b/spring-boot/pom.xml index 5b3c9fc1b33..caa9c3963cf 100644 --- a/spring-boot/pom.xml +++ b/spring-boot/pom.xml @@ -114,6 +114,16 @@ tomcat-embed-logging-juli true + + org.apache.velocity + velocity + true + + + org.apache.velocity + velocity-tools + true + org.codehaus.btm btm @@ -184,6 +194,11 @@ spring-web true + + org.springframework + spring-webmvc + true + org.yaml snakeyaml @@ -202,13 +217,13 @@ test - org.springframework - spring-webmvc + org.slf4j + jcl-over-slf4j test - org.slf4j - jcl-over-slf4j + org.springframework + spring-context-support test diff --git a/spring-boot/src/main/java/org/springframework/boot/web/servlet/view/velocity/EmbeddedVelocityToolboxView.java b/spring-boot/src/main/java/org/springframework/boot/web/servlet/view/velocity/EmbeddedVelocityToolboxView.java new file mode 100644 index 00000000000..a32ac85cae4 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/web/servlet/view/velocity/EmbeddedVelocityToolboxView.java @@ -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 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 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(); + } + + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/web/servlet/view/velocity/EmbeddedVelocityViewResolver.java b/spring-boot/src/main/java/org/springframework/boot/web/servlet/view/velocity/EmbeddedVelocityViewResolver.java new file mode 100644 index 00000000000..0d7132a9725 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/web/servlet/view/velocity/EmbeddedVelocityViewResolver.java @@ -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; + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/web/servlet/view/velocity/package-info.java b/spring-boot/src/main/java/org/springframework/boot/web/servlet/view/velocity/package-info.java new file mode 100644 index 00000000000..b1722c40ed0 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/web/servlet/view/velocity/package-info.java @@ -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; \ No newline at end of file diff --git a/spring-boot/src/test/java/org/springframework/boot/web/servlet/view/velocity/EmbeddedVelocityToolboxViewTests.java b/spring-boot/src/test/java/org/springframework/boot/web/servlet/view/velocity/EmbeddedVelocityToolboxViewTests.java new file mode 100644 index 00000000000..73ff4948f81 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/web/servlet/view/velocity/EmbeddedVelocityToolboxViewTests.java @@ -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 model = new LinkedHashMap(); + 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(); + } + + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/web/servlet/view/velocity/EmbeddedVelocityViewResolverTests.java b/spring-boot/src/test/java/org/springframework/boot/web/servlet/view/velocity/EmbeddedVelocityViewResolverTests.java new file mode 100644 index 00000000000..3c0ed944545 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/web/servlet/view/velocity/EmbeddedVelocityViewResolverTests.java @@ -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(); + } + + } + +} diff --git a/spring-boot/src/test/resources/org/springframework/boot/web/servlet/view/velocity/toolbox.xml b/spring-boot/src/test/resources/org/springframework/boot/web/servlet/view/velocity/toolbox.xml new file mode 100644 index 00000000000..6be5e9f4d89 --- /dev/null +++ b/spring-boot/src/test/resources/org/springframework/boot/web/servlet/view/velocity/toolbox.xml @@ -0,0 +1,8 @@ + + + + math + application + org.apache.velocity.tools.generic.MathTool + +