Skip to content

Commit 80f4ea1

Browse files
sdeleuzerstoyanchev
authored andcommitted
Add Groovy markup templating support to the MVC config
Issue: SPR-11998
1 parent b18ed6b commit 80f4ea1

File tree

14 files changed

+200
-14
lines changed

14 files changed

+200
-14
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2002-2014 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.web.servlet.config;
18+
19+
import org.w3c.dom.Element;
20+
21+
import org.springframework.beans.factory.support.AbstractBeanDefinition;
22+
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
23+
import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser;
24+
import org.springframework.beans.factory.xml.ParserContext;
25+
26+
/**
27+
* Parse the <mvc:groovy-markup> MVC namespace element and register a
28+
* GroovyConfigurer bean
29+
*
30+
* @author Sebastien Deleuze
31+
* @since 4.1
32+
*/
33+
public class GroovyMarkupBeanDefinitionParser extends AbstractSimpleBeanDefinitionParser {
34+
35+
public static final String BEAN_NAME = "mvcGroovyMarkupConfigurer";
36+
37+
@Override
38+
protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) {
39+
return BEAN_NAME;
40+
}
41+
42+
@Override
43+
protected String getBeanClassName(Element element) {
44+
return "org.springframework.web.servlet.view.groovy.GroovyMarkupConfigurer";
45+
}
46+
47+
@Override
48+
protected boolean isEligibleAttribute(String attributeName) {
49+
return attributeName.equals("resource-loader-path");
50+
}
51+
52+
}

spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceHandler.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public void init() {
3939
registerBeanDefinitionParser("tiles", new TilesBeanDefinitionParser());
4040
registerBeanDefinitionParser("freemarker", new FreeMarkerBeanDefinitionParser());
4141
registerBeanDefinitionParser("velocity", new VelocityBeanDefinitionParser());
42+
registerBeanDefinitionParser("groovy-markup", new GroovyMarkupBeanDefinitionParser());
4243
}
4344

4445
}

spring-webmvc/src/main/java/org/springframework/web/servlet/config/ViewResolversBeanDefinitionParser.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@
1616

1717
package org.springframework.web.servlet.config;
1818

19-
import java.util.HashMap;
2019
import java.util.List;
21-
import java.util.Map;
2220

2321
import org.springframework.beans.MutablePropertyValues;
2422
import org.springframework.beans.factory.config.BeanDefinition;
@@ -37,6 +35,7 @@
3735

3836
import org.springframework.web.servlet.view.ViewResolverComposite;
3937
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;
38+
import org.springframework.web.servlet.view.groovy.GroovyMarkupViewResolver;
4039
import org.springframework.web.servlet.view.tiles3.TilesViewResolver;
4140
import org.springframework.web.servlet.view.velocity.VelocityViewResolver;
4241
import org.w3c.dom.Element;
@@ -77,7 +76,7 @@ public BeanDefinition parse(Element element, ParserContext context) {
7776

7877
ManagedList<Object> resolvers = new ManagedList<Object>(4);
7978
resolvers.setSource(context.extractSource(element));
80-
String[] names = new String[] {"jsp", "tiles", "bean-name", "freemarker", "velocity", "bean", "ref"};
79+
String[] names = new String[] {"jsp", "tiles", "bean-name", "freemarker", "velocity", "groovy-markup", "bean", "ref"};
8180

8281
for (Element resolverElement : DomUtils.getChildElementsByTagName(element, names)) {
8382
String name = resolverElement.getLocalName();
@@ -109,6 +108,11 @@ else if ("velocity".equals(name)) {
109108
else if ("bean-name".equals(name)) {
110109
resolverBeanDef = new RootBeanDefinition(BeanNameViewResolver.class);
111110
}
111+
else if ("groovy-markup".equals(name)) {
112+
resolverBeanDef = new RootBeanDefinition(GroovyMarkupViewResolver.class);
113+
resolverBeanDef.getPropertyValues().add("suffix", ".tpl");
114+
addUrlBasedViewResolverProperties(resolverElement, resolverBeanDef);
115+
}
112116
else {
113117
// Should never happen
114118
throw new IllegalStateException("Unexpected element name: " + name);

spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ViewResolverRegistry.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
import org.springframework.web.servlet.view.InternalResourceViewResolver;
3131
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
3232
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;
33+
import org.springframework.web.servlet.view.groovy.GroovyMarkupConfigurer;
34+
import org.springframework.web.servlet.view.groovy.GroovyMarkupViewResolver;
3335
import org.springframework.web.servlet.view.tiles3.TilesConfigurer;
3436
import org.springframework.web.servlet.view.tiles3.TilesViewResolver;
3537
import org.springframework.web.servlet.view.velocity.VelocityConfigurer;
@@ -198,7 +200,7 @@ public UrlBasedViewResolverRegistration freeMarker() {
198200

199201
/**
200202
* Register Velocity view resolver with an empty default view name
201-
* prefix, a default suffix of ".vm".
203+
* prefix and a default suffix of ".vm".
202204
*
203205
* <p><strong>Note</strong> that you must also configure Velocity by adding a
204206
* {@link org.springframework.web.servlet.view.velocity.VelocityConfigurer} bean.
@@ -224,6 +226,22 @@ public void beanName() {
224226
this.viewResolvers.add(resolver);
225227
}
226228

229+
/**
230+
* Register a Groovy Markup Template view resolver with an empty default view name
231+
* prefix and a default suffix of ".tpl".
232+
*/
233+
public UrlBasedViewResolverRegistration groovyMarkup() {
234+
if (this.applicationContext != null && !hasBeanOfType(GroovyMarkupConfigurer.class)) {
235+
throw new BeanInitializationException("In addition to a Groovy Markup Template view resolver " +
236+
"there must also be a single GroovyMarkupConfig bean in this web application context " +
237+
"(or its parent): GroovyMarkupConfigurer is the usual implementation. " +
238+
"This bean may be given any name.");
239+
}
240+
GroovyMarkupRegistration registration = new GroovyMarkupRegistration();
241+
this.viewResolvers.add(registration.getViewResolver());
242+
return registration;
243+
}
244+
227245
/**
228246
* Register a {@link ViewResolver} bean instance. This may be useful to
229247
* configure a custom (or 3rd party) resolver implementation. It may also be
@@ -282,4 +300,12 @@ private FreeMarkerRegistration() {
282300
}
283301
}
284302

303+
private static class GroovyMarkupRegistration extends UrlBasedViewResolverRegistration {
304+
305+
private GroovyMarkupRegistration() {
306+
super(new GroovyMarkupViewResolver());
307+
getViewResolver().setSuffix(".tpl");
308+
}
309+
}
310+
285311
}

spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.1.xsd

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -753,6 +753,16 @@
753753
]]></xsd:documentation>
754754
</xsd:annotation>
755755
</xsd:element>
756+
<xsd:element name="groovy-markup" type="urlViewResolverType">
757+
<xsd:annotation>
758+
<xsd:documentation><![CDATA[
759+
Register a GroovyMarkupViewResolver.
760+
By default ".tpl" is configured as a view name suffix.
761+
To configure Groovy Markup Template you must also add a top-level <mvc:groovy-markup> element.
762+
or declare a GroovyMarkupConfigurer bean.
763+
]]></xsd:documentation>
764+
</xsd:annotation>
765+
</xsd:element>
756766
<xsd:element ref="beans:bean">
757767
<xsd:annotation>
758768
<xsd:documentation><![CDATA[
@@ -853,4 +863,22 @@
853863
</xsd:complexType>
854864
</xsd:element>
855865

866+
<xsd:element name="groovy-markup">
867+
<xsd:annotation>
868+
<xsd:documentation><![CDATA[
869+
Configure Groovy Markup Template for view resolution by registering a GroovyMarkupConfigurer bean.
870+
This is a shortcut alternative to declaring a GroovyMarkupConfigurer bean directly.
871+
]]></xsd:documentation>
872+
</xsd:annotation>
873+
<xsd:complexType>
874+
<xsd:attribute name="resource-loader-path" type="xsd:string">
875+
<xsd:annotation>
876+
<xsd:documentation><![CDATA[
877+
The Groovy Markup Template resource loader path via a Spring resource location.
878+
]]></xsd:documentation>
879+
</xsd:annotation>
880+
</xsd:attribute>
881+
</xsd:complexType>
882+
</xsd:element>
883+
856884
</xsd:schema>

spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@
8484
import org.springframework.web.servlet.resource.ResourceResolver;
8585
import org.springframework.web.servlet.resource.ResourceTransformer;
8686
import org.springframework.web.servlet.theme.ThemeChangeInterceptor;
87+
import org.springframework.web.servlet.view.groovy.GroovyMarkupConfigurer;
88+
import org.springframework.web.servlet.view.groovy.GroovyMarkupViewResolver;
8789
import org.springframework.web.util.UrlPathHelper;
8890
import org.springframework.web.servlet.view.*;
8991
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
@@ -561,11 +563,11 @@ public void testAsyncSupportOptions() throws Exception {
561563

562564
@Test
563565
public void testViewResolution() throws Exception {
564-
loadBeanDefinitions("mvc-config-view-resolution.xml", 5);
566+
loadBeanDefinitions("mvc-config-view-resolution.xml", 6);
565567

566568
ViewResolverComposite compositeResolver = this.appContext.getBean(ViewResolverComposite.class);
567569
assertNotNull(compositeResolver);
568-
assertEquals(7, compositeResolver.getViewResolvers().size());
570+
assertEquals(8, compositeResolver.getViewResolvers().size());
569571
assertEquals(0, compositeResolver.getOrder());
570572

571573
List<ViewResolver> resolvers = compositeResolver.getViewResolvers();
@@ -593,8 +595,15 @@ public void testViewResolution() throws Exception {
593595
assertEquals(".vm", accessor.getPropertyValue("suffix"));
594596
assertEquals(0, accessor.getPropertyValue("cacheLimit"));
595597

596-
assertEquals(InternalResourceViewResolver.class, resolvers.get(5).getClass());
598+
resolver = resolvers.get(5);
599+
GroovyMarkupViewResolver groovyMarkupViewResolver = (GroovyMarkupViewResolver) resolver;
600+
accessor = new DirectFieldAccessor(resolver);
601+
assertEquals("", accessor.getPropertyValue("prefix"));
602+
assertEquals(".tpl", accessor.getPropertyValue("suffix"));
603+
assertEquals(1024, accessor.getPropertyValue("cacheLimit"));
604+
597605
assertEquals(InternalResourceViewResolver.class, resolvers.get(6).getClass());
606+
assertEquals(InternalResourceViewResolver.class, resolvers.get(7).getClass());
598607

599608

600609
TilesConfigurer tilesConfigurer = appContext.getBean(TilesConfigurer.class);
@@ -616,11 +625,16 @@ public void testViewResolution() throws Exception {
616625
assertNotNull(velocityConfigurer);
617626
accessor = new DirectFieldAccessor(velocityConfigurer);
618627
assertEquals("/test", accessor.getPropertyValue("resourceLoaderPath"));
628+
629+
GroovyMarkupConfigurer groovyMarkupConfigurer = appContext.getBean(GroovyMarkupConfigurer.class);
630+
assertNotNull(groovyMarkupConfigurer);
631+
accessor = new DirectFieldAccessor(groovyMarkupConfigurer);
632+
assertEquals("/test", accessor.getPropertyValue("resourceLoaderPath"));
619633
}
620634

621635
@Test
622636
public void testViewResolutionWithContentNegotiation() throws Exception {
623-
loadBeanDefinitions("mvc-config-view-resolution-content-negotiation.xml", 5);
637+
loadBeanDefinitions("mvc-config-view-resolution-content-negotiation.xml", 6);
624638

625639
ViewResolverComposite compositeResolver = this.appContext.getBean(ViewResolverComposite.class);
626640
assertNotNull(compositeResolver);
@@ -630,7 +644,7 @@ public void testViewResolutionWithContentNegotiation() throws Exception {
630644
List<ViewResolver> resolvers = compositeResolver.getViewResolvers();
631645
assertEquals(ContentNegotiatingViewResolver.class, resolvers.get(0).getClass());
632646
ContentNegotiatingViewResolver cnvr = (ContentNegotiatingViewResolver) resolvers.get(0);
633-
assertEquals(5, cnvr.getViewResolvers().size());
647+
assertEquals(6, cnvr.getViewResolvers().size());
634648
assertEquals(1, cnvr.getDefaultViews().size());
635649
assertTrue(cnvr.isUseNotAcceptableStatusCode());
636650

spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ViewResolutionIntegrationTests.java

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@
2727
import org.springframework.mock.web.test.MockServletContext;
2828
import org.springframework.stereotype.Controller;
2929
import org.springframework.ui.ModelMap;
30-
import org.springframework.web.bind.annotation.ModelAttribute;
3130
import org.springframework.web.bind.annotation.RequestMapping;
3231
import org.springframework.web.bind.annotation.RequestMethod;
3332
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
3433
import org.springframework.web.servlet.DispatcherServlet;
3534
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
35+
import org.springframework.web.servlet.view.groovy.GroovyMarkupConfigurer;
3636
import org.springframework.web.servlet.view.tiles3.TilesConfigurer;
3737
import org.springframework.web.servlet.view.velocity.VelocityConfigurer;
3838

@@ -71,6 +71,12 @@ public void tiles() throws Exception {
7171
assertEquals("/WEB-INF/index.jsp", response.getForwardedUrl());
7272
}
7373

74+
@Test
75+
public void groovyMarkup() throws Exception {
76+
MockHttpServletResponse response = runTest(GroovyMarkupWebConfig.class);
77+
assertEquals("<html><body>Hello World!</body></html>", response.getContentAsString());
78+
}
79+
7480
@Test
7581
public void freemarkerInvalidConfig() throws Exception {
7682
this.thrown.expectMessage("In addition to a FreeMarker view resolver ");
@@ -89,6 +95,12 @@ public void tilesInvalidConfig() throws Exception {
8995
runTest(InvalidTilesWebConfig.class);
9096
}
9197

98+
@Test
99+
public void groovyMarkupInvalidConfig() throws Exception {
100+
this.thrown.expectMessage("In addition to a Groovy Markup Template view resolver ");
101+
runTest(InvalidGroovyMarkupWebConfig.class);
102+
}
103+
92104

93105
private MockHttpServletResponse runTest(Class<?> configClass) throws ServletException, IOException {
94106
String basePath = "org/springframework/web/servlet/config/annotation";
@@ -112,7 +124,7 @@ private MockHttpServletResponse runTest(Class<?> configClass) throws ServletExce
112124
static class SampleController {
113125

114126
@RequestMapping(value = "/", method = RequestMethod.GET)
115-
public String tiles(@ModelAttribute("model") ModelMap model) {
127+
public String sample(ModelMap model) {
116128
model.addAttribute("hello", "Hello World!");
117129
return "index";
118130
}
@@ -175,6 +187,22 @@ public TilesConfigurer tilesConfigurer() {
175187
}
176188
}
177189

190+
@Configuration
191+
static class GroovyMarkupWebConfig extends AbstractWebConfig {
192+
193+
@Override
194+
public void configureViewResolvers(ViewResolverRegistry registry) {
195+
registry.groovyMarkup();
196+
}
197+
198+
@Bean
199+
public GroovyMarkupConfigurer groovyMarkupConfigurer() {
200+
GroovyMarkupConfigurer configurer = new GroovyMarkupConfigurer();
201+
configurer.setResourceLoaderPath("/WEB-INF/");
202+
return configurer;
203+
}
204+
}
205+
178206
@Configuration
179207
static class InvalidFreeMarkerWebConfig extends WebMvcConfigurationSupport {
180208

@@ -202,4 +230,13 @@ public void configureViewResolvers(ViewResolverRegistry registry) {
202230
}
203231
}
204232

233+
@Configuration
234+
static class InvalidGroovyMarkupWebConfig extends WebMvcConfigurationSupport {
235+
236+
@Override
237+
public void configureViewResolvers(ViewResolverRegistry registry) {
238+
registry.groovyMarkup();
239+
}
240+
}
241+
205242
}

spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ViewResolverRegistryTests.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import org.springframework.web.servlet.view.InternalResourceViewResolver;
2929
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
3030
import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;
31+
import org.springframework.web.servlet.view.groovy.GroovyMarkupConfigurer;
32+
import org.springframework.web.servlet.view.groovy.GroovyMarkupViewResolver;
3133
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
3234
import org.springframework.web.servlet.view.tiles3.TilesConfigurer;
3335
import org.springframework.web.servlet.view.tiles3.TilesViewResolver;
@@ -57,6 +59,7 @@ public void setUp() {
5759
context.registerSingleton("freeMarkerConfigurer", FreeMarkerConfigurer.class);
5860
context.registerSingleton("velocityConfigurer", VelocityConfigurer.class);
5961
context.registerSingleton("tilesConfigurer", TilesConfigurer.class);
62+
context.registerSingleton("groovyMarkupConfigurer", GroovyMarkupConfigurer.class);
6063
this.registry = new ViewResolverRegistry();
6164
this.registry.setApplicationContext(context);
6265
this.registry.setContentNegotiationManager(new ContentNegotiationManager());
@@ -165,6 +168,20 @@ public void freeMarkerDefaultValues() {
165168
checkPropertyValues(resolver, "prefix", "", "suffix", ".ftl");
166169
}
167170

171+
@Test
172+
public void groovyMarkup() {
173+
this.registry.groovyMarkup().prefix("/").suffix(".groovy").cache(true);
174+
GroovyMarkupViewResolver resolver = checkAndGetResolver(GroovyMarkupViewResolver.class);
175+
checkPropertyValues(resolver, "prefix", "/", "suffix", ".groovy", "cacheLimit", 1024);
176+
}
177+
178+
@Test
179+
public void groovyMarkupDefaultValues() {
180+
this.registry.groovyMarkup();
181+
GroovyMarkupViewResolver resolver = checkAndGetResolver(GroovyMarkupViewResolver.class);
182+
checkPropertyValues(resolver, "prefix", "", "suffix", ".tpl");
183+
}
184+
168185
@Test
169186
public void contentNegotiation() {
170187
MappingJackson2JsonView view = new MappingJackson2JsonView();
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<html><body>${model.hello}</body></html>
1+
<html><body>${hello}</body></html>

spring-webmvc/src/test/resources/org/springframework/web/servlet/config/annotation/WEB-INF/index.jsp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<title>My First Web Application Using Spring MVC</title>
77
</head>
88
<body>
9-
${model.hello}
9+
${hello}
1010
</body>
1111
</html>
1212

0 commit comments

Comments
 (0)