Skip to content

Commit cca0b76

Browse files
committed
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
1 parent 1cfc6f6 commit cca0b76

File tree

9 files changed

+412
-6
lines changed

9 files changed

+412
-6
lines changed

spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/velocity/VelocityAutoConfiguration.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.springframework.boot.autoconfigure.template.TemplateLocation;
3636
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
3737
import org.springframework.boot.context.properties.EnableConfigurationProperties;
38+
import org.springframework.boot.web.servlet.view.velocity.EmbeddedVelocityViewResolver;
3839
import org.springframework.context.ApplicationContext;
3940
import org.springframework.context.annotation.Bean;
4041
import org.springframework.context.annotation.Configuration;
@@ -128,7 +129,7 @@ public VelocityEngine velocityEngine(VelocityConfigurer configurer)
128129
@ConditionalOnMissingBean(name = "velocityViewResolver")
129130
@ConditionalOnProperty(name = "spring.velocity.enabled", matchIfMissing = true)
130131
public VelocityViewResolver velocityViewResolver() {
131-
VelocityViewResolver resolver = new VelocityViewResolver();
132+
EmbeddedVelocityViewResolver resolver = new EmbeddedVelocityViewResolver();
132133
this.properties.applyToViewResolver(resolver);
133134
return resolver;
134135
}

spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/velocity/VelocityAutoConfigurationTests.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.junit.Test;
3131
import org.springframework.beans.factory.BeanCreationException;
3232
import org.springframework.boot.test.EnvironmentTestUtils;
33+
import org.springframework.boot.web.servlet.view.velocity.EmbeddedVelocityViewResolver;
3334
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
3435
import org.springframework.mock.web.MockHttpServletRequest;
3536
import org.springframework.mock.web.MockHttpServletResponse;
@@ -42,6 +43,7 @@
4243

4344
import static org.hamcrest.Matchers.containsString;
4445
import static org.hamcrest.Matchers.equalTo;
46+
import static org.hamcrest.Matchers.instanceOf;
4547
import static org.hamcrest.Matchers.notNullValue;
4648
import static org.junit.Assert.assertThat;
4749

@@ -136,7 +138,7 @@ public void disableCache() {
136138
}
137139

138140
@Test
139-
public void customFreeMarkerSettings() {
141+
public void customVelocitySettings() {
140142
registerAndRefreshContext("spring.velocity.properties.directive.parse.max.depth:10");
141143
assertThat(this.context.getBean(VelocityConfigurer.class).getVelocityEngine()
142144
.getProperty("directive.parse.max.depth"), equalTo((Object) "10"));
@@ -174,6 +176,13 @@ public void renderNonWebAppTemplate() throws Exception {
174176
}
175177
}
176178

179+
@Test
180+
public void usesEmbeddedVelocityViewResolver() {
181+
registerAndRefreshContext("spring.velocity.toolbox:/toolbox.xml");
182+
VelocityViewResolver resolver = this.context.getBean(VelocityViewResolver.class);
183+
assertThat(resolver, instanceOf(EmbeddedVelocityViewResolver.class));
184+
}
185+
177186
private void registerAndRefreshContext(String... env) {
178187
EnvironmentTestUtils.addEnvironment(this.context, env);
179188
this.context.register(VelocityAutoConfiguration.class);

spring-boot/pom.xml

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,16 @@
114114
<artifactId>tomcat-embed-logging-juli</artifactId>
115115
<optional>true</optional>
116116
</dependency>
117+
<dependency>
118+
<groupId>org.apache.velocity</groupId>
119+
<artifactId>velocity</artifactId>
120+
<optional>true</optional>
121+
</dependency>
122+
<dependency>
123+
<groupId>org.apache.velocity</groupId>
124+
<artifactId>velocity-tools</artifactId>
125+
<optional>true</optional>
126+
</dependency>
117127
<dependency>
118128
<groupId>org.codehaus.btm</groupId>
119129
<artifactId>btm</artifactId>
@@ -184,6 +194,11 @@
184194
<artifactId>spring-web</artifactId>
185195
<optional>true</optional>
186196
</dependency>
197+
<dependency>
198+
<groupId>org.springframework</groupId>
199+
<artifactId>spring-webmvc</artifactId>
200+
<optional>true</optional>
201+
</dependency>
187202
<dependency>
188203
<groupId>org.yaml</groupId>
189204
<artifactId>snakeyaml</artifactId>
@@ -202,13 +217,13 @@
202217
<scope>test</scope>
203218
</dependency>
204219
<dependency>
205-
<groupId>org.springframework</groupId>
206-
<artifactId>spring-webmvc</artifactId>
220+
<groupId>org.slf4j</groupId>
221+
<artifactId>jcl-over-slf4j</artifactId>
207222
<scope>test</scope>
208223
</dependency>
209224
<dependency>
210-
<groupId>org.slf4j</groupId>
211-
<artifactId>jcl-over-slf4j</artifactId>
225+
<groupId>org.springframework</groupId>
226+
<artifactId>spring-context-support</artifactId>
212227
<scope>test</scope>
213228
</dependency>
214229
</dependencies>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* Copyright 2012-2015 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.web.servlet.view.velocity;
18+
19+
import java.io.InputStream;
20+
import java.util.Map;
21+
22+
import javax.servlet.ServletContext;
23+
import javax.servlet.http.HttpServletRequest;
24+
import javax.servlet.http.HttpServletResponse;
25+
26+
import org.aopalliance.intercept.MethodInterceptor;
27+
import org.aopalliance.intercept.MethodInvocation;
28+
import org.apache.velocity.VelocityContext;
29+
import org.apache.velocity.context.Context;
30+
import org.springframework.aop.framework.ProxyFactory;
31+
import org.springframework.core.io.ClassPathResource;
32+
import org.springframework.web.servlet.view.velocity.VelocityToolboxView;
33+
34+
/**
35+
* Extended version of {@link VelocityToolboxView} that can load toolbox locations from
36+
* the classpath as well as the servlet context. This is useful when run an embedded web
37+
* server.
38+
*
39+
* @author Phillip Webb
40+
* @since 1.2.5
41+
*/
42+
@SuppressWarnings("deprecation")
43+
public class EmbeddedVelocityToolboxView extends VelocityToolboxView {
44+
45+
@Override
46+
protected Context createVelocityContext(Map<String, Object> model,
47+
HttpServletRequest request, HttpServletResponse response) throws Exception {
48+
org.apache.velocity.tools.view.context.ChainedContext context = new org.apache.velocity.tools.view.context.ChainedContext(
49+
new VelocityContext(model), getVelocityEngine(), request, response,
50+
getServletContext());
51+
if (getToolboxConfigLocation() != null) {
52+
setContextToolbox(context);
53+
}
54+
return context;
55+
}
56+
57+
@SuppressWarnings("unchecked")
58+
private void setContextToolbox(
59+
org.apache.velocity.tools.view.context.ChainedContext context) {
60+
org.apache.velocity.tools.view.ToolboxManager toolboxManager = org.apache.velocity.tools.view.servlet.ServletToolboxManager
61+
.getInstance(getToolboxConfigFileAwareServletContext(),
62+
getToolboxConfigLocation());
63+
Map<String, Object> toolboxContext = toolboxManager.getToolbox(context);
64+
context.setToolbox(toolboxContext);
65+
}
66+
67+
private ServletContext getToolboxConfigFileAwareServletContext() {
68+
ProxyFactory factory = new ProxyFactory();
69+
factory.setTarget(getServletContext());
70+
factory.addAdvice(new GetResourceMethodInterceptor(getToolboxConfigLocation()));
71+
return (ServletContext) factory.getProxy();
72+
}
73+
74+
/**
75+
* {@link MethodInterceptor} to allow the calls to getResourceAsStream() to resolve
76+
* the toolboxFile from the classpath.
77+
*/
78+
private static class GetResourceMethodInterceptor implements MethodInterceptor {
79+
80+
private final String toolboxFile;
81+
82+
public GetResourceMethodInterceptor(String toolboxFile) {
83+
if (toolboxFile != null && !toolboxFile.startsWith("/")) {
84+
toolboxFile = "/" + toolboxFile;
85+
}
86+
this.toolboxFile = toolboxFile;
87+
}
88+
89+
@Override
90+
public Object invoke(MethodInvocation invocation) throws Throwable {
91+
if (invocation.getMethod().getName().equals("getResourceAsStream")
92+
&& invocation.getArguments()[0].equals(this.toolboxFile)) {
93+
InputStream inputStream = (InputStream) invocation.proceed();
94+
if (inputStream == null) {
95+
try {
96+
inputStream = new ClassPathResource(this.toolboxFile, Thread
97+
.currentThread().getContextClassLoader())
98+
.getInputStream();
99+
}
100+
catch (Exception ex) {
101+
// Ignore
102+
}
103+
}
104+
return inputStream;
105+
}
106+
return invocation.proceed();
107+
}
108+
109+
}
110+
111+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2012-2015 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.web.servlet.view.velocity;
18+
19+
import org.springframework.web.servlet.view.velocity.VelocityView;
20+
import org.springframework.web.servlet.view.velocity.VelocityViewResolver;
21+
22+
/**
23+
* Extended version of {@link VelocityViewResolver} that uses
24+
* {@link EmbeddedVelocityToolboxView} when the {@link #setToolboxConfigLocation(String)
25+
* toolboxConfigLocation} is set.
26+
*
27+
* @author Phillip Webb
28+
* @since 1.2.5
29+
*/
30+
public class EmbeddedVelocityViewResolver extends VelocityViewResolver {
31+
32+
private String toolboxConfigLocation;
33+
34+
@Override
35+
protected void initApplicationContext() {
36+
if (this.toolboxConfigLocation != null) {
37+
if (VelocityView.class.equals(getViewClass())) {
38+
this.logger.info("Using EmbeddedVelocityToolboxView instead of "
39+
+ "default VelocityView due to specified toolboxConfigLocation");
40+
setViewClass(EmbeddedVelocityToolboxView.class);
41+
}
42+
}
43+
super.initApplicationContext();
44+
}
45+
46+
@Override
47+
public void setToolboxConfigLocation(String toolboxConfigLocation) {
48+
super.setToolboxConfigLocation(toolboxConfigLocation);
49+
this.toolboxConfigLocation = toolboxConfigLocation;
50+
}
51+
52+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Copyright 2012-2015 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
/**
17+
* @author pwebb
18+
*/
19+
package org.springframework.boot.web.servlet.view.velocity;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright 2012-2015 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.web.servlet.view.velocity;
18+
19+
import java.util.LinkedHashMap;
20+
import java.util.Map;
21+
22+
import javax.servlet.http.HttpServletRequest;
23+
import javax.servlet.http.HttpServletResponse;
24+
25+
import org.apache.struts.mock.MockHttpServletRequest;
26+
import org.apache.struts.mock.MockHttpServletResponse;
27+
import org.apache.struts.mock.MockServletContext;
28+
import org.apache.velocity.tools.ToolContext;
29+
import org.junit.Test;
30+
import org.springframework.context.annotation.Bean;
31+
import org.springframework.context.annotation.Configuration;
32+
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
33+
import org.springframework.web.servlet.view.velocity.VelocityConfigurer;
34+
35+
import static org.hamcrest.Matchers.contains;
36+
import static org.hamcrest.Matchers.notNullValue;
37+
import static org.junit.Assert.assertThat;
38+
39+
/**
40+
* Tests for {@link EmbeddedVelocityToolboxView}.
41+
*
42+
* @author Phillip Webb
43+
*/
44+
public class EmbeddedVelocityToolboxViewTests {
45+
46+
private static final String PATH = EmbeddedVelocityToolboxViewTests.class
47+
.getPackage().getName().replace(".", "/");
48+
49+
@Test
50+
public void loadsContextFromClassPath() throws Exception {
51+
ToolContext context = getToolContext(PATH + "/toolbox.xml");
52+
assertThat(context.getToolbox().keySet(), contains("math"));
53+
}
54+
55+
@Test
56+
public void loadsWithoutConfig() throws Exception {
57+
ToolContext context = getToolContext(null);
58+
assertThat(context, notNullValue());
59+
}
60+
61+
private ToolContext getToolContext(String toolboxConfigLocation) throws Exception {
62+
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
63+
context.setServletContext(new MockServletContext());
64+
context.register(Config.class);
65+
context.refresh();
66+
EmbeddedVelocityToolboxView view = context
67+
.getBean(EmbeddedVelocityToolboxView.class);
68+
view.setToolboxConfigLocation(toolboxConfigLocation);
69+
Map<String, Object> model = new LinkedHashMap<String, Object>();
70+
HttpServletRequest request = new MockHttpServletRequest();
71+
HttpServletResponse response = new MockHttpServletResponse();
72+
ToolContext toolContext = (ToolContext) view.createVelocityContext(model,
73+
request, response);
74+
context.close();
75+
return toolContext;
76+
}
77+
78+
@Configuration
79+
static class Config {
80+
81+
@Bean
82+
public EmbeddedVelocityToolboxView view() {
83+
EmbeddedVelocityToolboxView view = new EmbeddedVelocityToolboxView();
84+
view.setUrl("http://example.com");
85+
return view;
86+
}
87+
88+
@Bean
89+
public VelocityConfigurer velocityConfigurer() {
90+
return new VelocityConfigurer();
91+
}
92+
93+
}
94+
95+
}

0 commit comments

Comments
 (0)