diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/GroovyMarkupBeanDefinitionParser.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/GroovyMarkupBeanDefinitionParser.java new file mode 100644 index 00000000000..3fdc4b18a80 --- /dev/null +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/GroovyMarkupBeanDefinitionParser.java @@ -0,0 +1,52 @@ +/* + * Copyright 2002-2014 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.web.servlet.config; + +import org.w3c.dom.Element; + +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; + +/** + * Parse the MVC namespace element and register a + * GroovyConfigurer bean + * + * @author Sebastien Deleuze + * @since 4.1 + */ +public class GroovyMarkupBeanDefinitionParser extends AbstractSimpleBeanDefinitionParser { + + public static final String BEAN_NAME = "mvcGroovyMarkupConfigurer"; + + @Override + protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) { + return BEAN_NAME; + } + + @Override + protected String getBeanClassName(Element element) { + return "org.springframework.web.servlet.view.groovy.GroovyMarkupConfigurer"; + } + + @Override + protected boolean isEligibleAttribute(String attributeName) { + return attributeName.equals("resource-loader-path"); + } + +} diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceHandler.java index b78951fbd78..017603a91a1 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceHandler.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceHandler.java @@ -39,6 +39,7 @@ public class MvcNamespaceHandler extends NamespaceHandlerSupport { registerBeanDefinitionParser("tiles", new TilesBeanDefinitionParser()); registerBeanDefinitionParser("freemarker", new FreeMarkerBeanDefinitionParser()); registerBeanDefinitionParser("velocity", new VelocityBeanDefinitionParser()); + registerBeanDefinitionParser("groovy-markup", new GroovyMarkupBeanDefinitionParser()); } } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ViewResolversBeanDefinitionParser.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ViewResolversBeanDefinitionParser.java index 5cf24777070..9c69d538027 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ViewResolversBeanDefinitionParser.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ViewResolversBeanDefinitionParser.java @@ -16,9 +16,7 @@ package org.springframework.web.servlet.config; -import java.util.HashMap; import java.util.List; -import java.util.Map; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.factory.config.BeanDefinition; @@ -37,6 +35,7 @@ import org.springframework.web.servlet.view.InternalResourceViewResolver; import org.springframework.web.servlet.view.ViewResolverComposite; import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver; +import org.springframework.web.servlet.view.groovy.GroovyMarkupViewResolver; import org.springframework.web.servlet.view.tiles3.TilesViewResolver; import org.springframework.web.servlet.view.velocity.VelocityViewResolver; import org.w3c.dom.Element; @@ -77,7 +76,7 @@ public class ViewResolversBeanDefinitionParser implements BeanDefinitionParser { ManagedList resolvers = new ManagedList(4); resolvers.setSource(context.extractSource(element)); - String[] names = new String[] {"jsp", "tiles", "bean-name", "freemarker", "velocity", "bean", "ref"}; + String[] names = new String[] {"jsp", "tiles", "bean-name", "freemarker", "velocity", "groovy-markup", "bean", "ref"}; for (Element resolverElement : DomUtils.getChildElementsByTagName(element, names)) { String name = resolverElement.getLocalName(); @@ -109,6 +108,11 @@ public class ViewResolversBeanDefinitionParser implements BeanDefinitionParser { else if ("bean-name".equals(name)) { resolverBeanDef = new RootBeanDefinition(BeanNameViewResolver.class); } + else if ("groovy-markup".equals(name)) { + resolverBeanDef = new RootBeanDefinition(GroovyMarkupViewResolver.class); + resolverBeanDef.getPropertyValues().add("suffix", ".tpl"); + addUrlBasedViewResolverProperties(resolverElement, resolverBeanDef); + } else { // Should never happen throw new IllegalStateException("Unexpected element name: " + name); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ViewResolverRegistry.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ViewResolverRegistry.java index 389c60c8832..f6b0091ece4 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ViewResolverRegistry.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ViewResolverRegistry.java @@ -30,6 +30,8 @@ import org.springframework.web.servlet.view.ContentNegotiatingViewResolver; import org.springframework.web.servlet.view.InternalResourceViewResolver; import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer; import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver; +import org.springframework.web.servlet.view.groovy.GroovyMarkupConfigurer; +import org.springframework.web.servlet.view.groovy.GroovyMarkupViewResolver; import org.springframework.web.servlet.view.tiles3.TilesConfigurer; import org.springframework.web.servlet.view.tiles3.TilesViewResolver; import org.springframework.web.servlet.view.velocity.VelocityConfigurer; @@ -198,7 +200,7 @@ public class ViewResolverRegistry { /** * Register Velocity view resolver with an empty default view name - * prefix, a default suffix of ".vm". + * prefix and a default suffix of ".vm". * *

Note that you must also configure Velocity by adding a * {@link org.springframework.web.servlet.view.velocity.VelocityConfigurer} bean. @@ -224,6 +226,22 @@ public class ViewResolverRegistry { this.viewResolvers.add(resolver); } + /** + * Register a Groovy Markup Template view resolver with an empty default view name + * prefix and a default suffix of ".tpl". + */ + public UrlBasedViewResolverRegistration groovyMarkup() { + if (this.applicationContext != null && !hasBeanOfType(GroovyMarkupConfigurer.class)) { + throw new BeanInitializationException("In addition to a Groovy Markup Template view resolver " + + "there must also be a single GroovyMarkupConfig bean in this web application context " + + "(or its parent): GroovyMarkupConfigurer is the usual implementation. " + + "This bean may be given any name."); + } + GroovyMarkupRegistration registration = new GroovyMarkupRegistration(); + this.viewResolvers.add(registration.getViewResolver()); + return registration; + } + /** * Register a {@link ViewResolver} bean instance. This may be useful to * configure a custom (or 3rd party) resolver implementation. It may also be @@ -282,4 +300,12 @@ public class ViewResolverRegistry { } } + private static class GroovyMarkupRegistration extends UrlBasedViewResolverRegistration { + + private GroovyMarkupRegistration() { + super(new GroovyMarkupViewResolver()); + getViewResolver().setSuffix(".tpl"); + } + } + } diff --git a/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.1.xsd b/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.1.xsd index 1d674d983a4..7a4cc369dc4 100644 --- a/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.1.xsd +++ b/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.1.xsd @@ -753,6 +753,16 @@ ]]> + + + element. + or declare a GroovyMarkupConfigurer bean. + ]]> + + + + + + + + + + + + + + + diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java index eda0a2ccd54..e5252e0b521 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java @@ -84,6 +84,8 @@ import org.springframework.web.servlet.resource.ResourceHttpRequestHandler; import org.springframework.web.servlet.resource.ResourceResolver; import org.springframework.web.servlet.resource.ResourceTransformer; import org.springframework.web.servlet.theme.ThemeChangeInterceptor; +import org.springframework.web.servlet.view.groovy.GroovyMarkupConfigurer; +import org.springframework.web.servlet.view.groovy.GroovyMarkupViewResolver; import org.springframework.web.util.UrlPathHelper; import org.springframework.web.servlet.view.*; import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer; @@ -561,11 +563,11 @@ public class MvcNamespaceTests { @Test public void testViewResolution() throws Exception { - loadBeanDefinitions("mvc-config-view-resolution.xml", 5); + loadBeanDefinitions("mvc-config-view-resolution.xml", 6); ViewResolverComposite compositeResolver = this.appContext.getBean(ViewResolverComposite.class); assertNotNull(compositeResolver); - assertEquals(7, compositeResolver.getViewResolvers().size()); + assertEquals(8, compositeResolver.getViewResolvers().size()); assertEquals(0, compositeResolver.getOrder()); List resolvers = compositeResolver.getViewResolvers(); @@ -593,8 +595,15 @@ public class MvcNamespaceTests { assertEquals(".vm", accessor.getPropertyValue("suffix")); assertEquals(0, accessor.getPropertyValue("cacheLimit")); - assertEquals(InternalResourceViewResolver.class, resolvers.get(5).getClass()); + resolver = resolvers.get(5); + GroovyMarkupViewResolver groovyMarkupViewResolver = (GroovyMarkupViewResolver) resolver; + accessor = new DirectFieldAccessor(resolver); + assertEquals("", accessor.getPropertyValue("prefix")); + assertEquals(".tpl", accessor.getPropertyValue("suffix")); + assertEquals(1024, accessor.getPropertyValue("cacheLimit")); + assertEquals(InternalResourceViewResolver.class, resolvers.get(6).getClass()); + assertEquals(InternalResourceViewResolver.class, resolvers.get(7).getClass()); TilesConfigurer tilesConfigurer = appContext.getBean(TilesConfigurer.class); @@ -616,11 +625,16 @@ public class MvcNamespaceTests { assertNotNull(velocityConfigurer); accessor = new DirectFieldAccessor(velocityConfigurer); assertEquals("/test", accessor.getPropertyValue("resourceLoaderPath")); + + GroovyMarkupConfigurer groovyMarkupConfigurer = appContext.getBean(GroovyMarkupConfigurer.class); + assertNotNull(groovyMarkupConfigurer); + accessor = new DirectFieldAccessor(groovyMarkupConfigurer); + assertEquals("/test", accessor.getPropertyValue("resourceLoaderPath")); } @Test public void testViewResolutionWithContentNegotiation() throws Exception { - loadBeanDefinitions("mvc-config-view-resolution-content-negotiation.xml", 5); + loadBeanDefinitions("mvc-config-view-resolution-content-negotiation.xml", 6); ViewResolverComposite compositeResolver = this.appContext.getBean(ViewResolverComposite.class); assertNotNull(compositeResolver); @@ -630,7 +644,7 @@ public class MvcNamespaceTests { List resolvers = compositeResolver.getViewResolvers(); assertEquals(ContentNegotiatingViewResolver.class, resolvers.get(0).getClass()); ContentNegotiatingViewResolver cnvr = (ContentNegotiatingViewResolver) resolvers.get(0); - assertEquals(5, cnvr.getViewResolvers().size()); + assertEquals(6, cnvr.getViewResolvers().size()); assertEquals(1, cnvr.getDefaultViews().size()); assertTrue(cnvr.isUseNotAcceptableStatusCode()); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ViewResolutionIntegrationTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ViewResolutionIntegrationTests.java index 0e89c2f55f6..46c55a20bb4 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ViewResolutionIntegrationTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ViewResolutionIntegrationTests.java @@ -27,12 +27,12 @@ import org.springframework.mock.web.test.MockServletConfig; import org.springframework.mock.web.test.MockServletContext; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; -import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer; +import org.springframework.web.servlet.view.groovy.GroovyMarkupConfigurer; import org.springframework.web.servlet.view.tiles3.TilesConfigurer; import org.springframework.web.servlet.view.velocity.VelocityConfigurer; @@ -71,6 +71,12 @@ public class ViewResolutionIntegrationTests { assertEquals("/WEB-INF/index.jsp", response.getForwardedUrl()); } + @Test + public void groovyMarkup() throws Exception { + MockHttpServletResponse response = runTest(GroovyMarkupWebConfig.class); + assertEquals("Hello World!", response.getContentAsString()); + } + @Test public void freemarkerInvalidConfig() throws Exception { this.thrown.expectMessage("In addition to a FreeMarker view resolver "); @@ -89,6 +95,12 @@ public class ViewResolutionIntegrationTests { runTest(InvalidTilesWebConfig.class); } + @Test + public void groovyMarkupInvalidConfig() throws Exception { + this.thrown.expectMessage("In addition to a Groovy Markup Template view resolver "); + runTest(InvalidGroovyMarkupWebConfig.class); + } + private MockHttpServletResponse runTest(Class configClass) throws ServletException, IOException { String basePath = "org/springframework/web/servlet/config/annotation"; @@ -112,7 +124,7 @@ public class ViewResolutionIntegrationTests { static class SampleController { @RequestMapping(value = "/", method = RequestMethod.GET) - public String tiles(@ModelAttribute("model") ModelMap model) { + public String sample(ModelMap model) { model.addAttribute("hello", "Hello World!"); return "index"; } @@ -175,6 +187,22 @@ public class ViewResolutionIntegrationTests { } } + @Configuration + static class GroovyMarkupWebConfig extends AbstractWebConfig { + + @Override + public void configureViewResolvers(ViewResolverRegistry registry) { + registry.groovyMarkup(); + } + + @Bean + public GroovyMarkupConfigurer groovyMarkupConfigurer() { + GroovyMarkupConfigurer configurer = new GroovyMarkupConfigurer(); + configurer.setResourceLoaderPath("/WEB-INF/"); + return configurer; + } + } + @Configuration static class InvalidFreeMarkerWebConfig extends WebMvcConfigurationSupport { @@ -202,4 +230,13 @@ public class ViewResolutionIntegrationTests { } } + @Configuration + static class InvalidGroovyMarkupWebConfig extends WebMvcConfigurationSupport { + + @Override + public void configureViewResolvers(ViewResolverRegistry registry) { + registry.groovyMarkup(); + } + } + } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ViewResolverRegistryTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ViewResolverRegistryTests.java index 622a3e68c33..e70cbbca178 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ViewResolverRegistryTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/ViewResolverRegistryTests.java @@ -28,6 +28,8 @@ import org.springframework.web.servlet.view.ContentNegotiatingViewResolver; import org.springframework.web.servlet.view.InternalResourceViewResolver; import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer; import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver; +import org.springframework.web.servlet.view.groovy.GroovyMarkupConfigurer; +import org.springframework.web.servlet.view.groovy.GroovyMarkupViewResolver; import org.springframework.web.servlet.view.json.MappingJackson2JsonView; import org.springframework.web.servlet.view.tiles3.TilesConfigurer; import org.springframework.web.servlet.view.tiles3.TilesViewResolver; @@ -57,6 +59,7 @@ public class ViewResolverRegistryTests { context.registerSingleton("freeMarkerConfigurer", FreeMarkerConfigurer.class); context.registerSingleton("velocityConfigurer", VelocityConfigurer.class); context.registerSingleton("tilesConfigurer", TilesConfigurer.class); + context.registerSingleton("groovyMarkupConfigurer", GroovyMarkupConfigurer.class); this.registry = new ViewResolverRegistry(); this.registry.setApplicationContext(context); this.registry.setContentNegotiationManager(new ContentNegotiationManager()); @@ -165,6 +168,20 @@ public class ViewResolverRegistryTests { checkPropertyValues(resolver, "prefix", "", "suffix", ".ftl"); } + @Test + public void groovyMarkup() { + this.registry.groovyMarkup().prefix("/").suffix(".groovy").cache(true); + GroovyMarkupViewResolver resolver = checkAndGetResolver(GroovyMarkupViewResolver.class); + checkPropertyValues(resolver, "prefix", "/", "suffix", ".groovy", "cacheLimit", 1024); + } + + @Test + public void groovyMarkupDefaultValues() { + this.registry.groovyMarkup(); + GroovyMarkupViewResolver resolver = checkAndGetResolver(GroovyMarkupViewResolver.class); + checkPropertyValues(resolver, "prefix", "", "suffix", ".tpl"); + } + @Test public void contentNegotiation() { MappingJackson2JsonView view = new MappingJackson2JsonView(); diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/annotation/WEB-INF/index.ftl b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/annotation/WEB-INF/index.ftl index fe9a8f9b85a..f9ad1fdc6ec 100644 --- a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/annotation/WEB-INF/index.ftl +++ b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/annotation/WEB-INF/index.ftl @@ -1 +1 @@ -${model.hello} \ No newline at end of file +${hello} \ No newline at end of file diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/annotation/WEB-INF/index.jsp b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/annotation/WEB-INF/index.jsp index c76e72290a3..46df8a3f54b 100644 --- a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/annotation/WEB-INF/index.jsp +++ b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/annotation/WEB-INF/index.jsp @@ -6,7 +6,7 @@ My First Web Application Using Spring MVC -${model.hello} +${hello} diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/annotation/WEB-INF/index.tpl b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/annotation/WEB-INF/index.tpl new file mode 100644 index 00000000000..70708a00bc4 --- /dev/null +++ b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/annotation/WEB-INF/index.tpl @@ -0,0 +1 @@ +html { body(hello) } \ No newline at end of file diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/annotation/WEB-INF/index.vm b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/annotation/WEB-INF/index.vm index fe9a8f9b85a..f9ad1fdc6ec 100644 --- a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/annotation/WEB-INF/index.vm +++ b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/annotation/WEB-INF/index.vm @@ -1 +1 @@ -${model.hello} \ No newline at end of file +${hello} \ No newline at end of file diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-resolution-content-negotiation.xml b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-resolution-content-negotiation.xml index d5a5208d92b..6b6c2c8c492 100644 --- a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-resolution-content-negotiation.xml +++ b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-resolution-content-negotiation.xml @@ -25,6 +25,7 @@ + @@ -37,5 +38,7 @@ + + \ No newline at end of file diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-resolution.xml b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-resolution.xml index afca9d64072..fdff4f55a2e 100644 --- a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-resolution.xml +++ b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-view-resolution.xml @@ -12,6 +12,7 @@ + @@ -30,5 +31,7 @@ + + \ No newline at end of file