diff --git a/build.gradle b/build.gradle index 1af7e61e76..57d43d9d66 100644 --- a/build.gradle +++ b/build.gradle @@ -768,6 +768,7 @@ project("spring-webmvc") { optional("org.apache.velocity:velocity:1.7") optional("velocity-tools:velocity-tools-view:1.4") optional("org.freemarker:freemarker:2.3.20") + optional("org.codehaus.groovy:groovy-all:${groovyVersion}") optional("com.lowagie:itext:2.1.7") optional("net.sf.jasperreports:jasperreports:$jasperReportsVersion") { exclude group: "com.fasterxml.jackson.core", module: "jackson-annotations" diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupConfig.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupConfig.java new file mode 100644 index 0000000000..596dfaf801 --- /dev/null +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupConfig.java @@ -0,0 +1,37 @@ +/* + * 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.view.groovy; + +import groovy.text.markup.MarkupTemplateEngine; + +/** + * Interface to be implemented by objects that configure and manage a + * Groovy MarkupTemplateEngine for automatic lookup in a web environment. + * Detected and used by GroovyMarkupView. + * + * @author Brian Clozel + * @see GroovyMarkupConfigurer + * @since 4.1 + */ +public interface GroovyMarkupConfig { + + /** + * Return the Groovy MarkupTemplateEngine for the current web application context. + * May be unique to one servlet, or shared in the root context. + * @return the Groovy Template engine + */ + public MarkupTemplateEngine getTemplateEngine(); +} diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupConfigurer.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupConfigurer.java new file mode 100644 index 0000000000..c7daf047af --- /dev/null +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupConfigurer.java @@ -0,0 +1,234 @@ +/* + * 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.view.groovy; + +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import groovy.text.markup.MarkupTemplateEngine; +import groovy.text.markup.TemplateConfiguration; +import groovy.text.markup.TemplateResolver; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.core.io.Resource; +import org.springframework.util.StringUtils; + +/** + * An extension of Groovy's {@link groovy.text.markup.TemplateConfiguration} and + * an implementation of Spring MVC's {@link GroovyMarkupConfig} for creating + * a {@code MarkupTemplateEngine} for use in a web application. The most basic + * way to configure this class is to set the "resourceLoaderPath". For example: + * + *
+ *
+ * // Add the following to an @Configuration class
+ *
+ * @Bean
+ * public GroovyMarkupConfig groovyMarkupConfigurer() {
+ *
+ *     GroovyMarkupConfigurer configurer = new GroovyMarkupConfigurer();
+ *     configurer.setResourceLoaderPath("classpath:/WEB-INF/groovymarkup/");
+ *     return configurer;
+ * }
+ * 
+ * + * By default this bean will create a {@link MarkupTemplateEngine} with: + * + * + * You can provide the {@link MarkupTemplateEngine} instance directly to this bean + * in which case all other properties will not be effectively ignored. + * + *

This bean must be included in the application context of any application + * using the Spring MVC {@link GroovyMarkupView} for rendering. It exists purely + * for the purpose of configuring Groovy's Markup templates. It is not meant to be + * referenced by application components directly. It implements GroovyMarkupConfig + * to be found by GroovyMarkupView without depending on a bean name. Each + * DispatcherServlet can define its own GroovyMarkupConfigurer if desired. + * + *

Note that resource caching is enabled by default in {@link MarkupTemplateEngine}. + * Use the {@link #setCacheTemplates(boolean)} to configure that as necessary. + + *

Spring's Groovy Markup template support requires Groovy 2.3.1 or higher. + * + * @author Brian Clozel + * @author Rossen Stoyanchev + * @since 4.1 + * + * @see GroovyMarkupView + * @see + * Groovy Markup Template engine documentation + */ +public class GroovyMarkupConfigurer extends TemplateConfiguration + implements GroovyMarkupConfig, ApplicationContextAware, InitializingBean { + + private String resourceLoaderPath = "classpath:"; + + private MarkupTemplateEngine templateEngine; + + private ApplicationContext applicationContext; + + + /** + * Set the Groovy Markup Template resource loader path(s) via a Spring resource + * location. Accepts multiple locations as a comma-separated list of paths. + * Standard URLs like "file:" and "classpath:" and pseudo URLs are supported + * as understood by Spring's {@link org.springframework.core.io.ResourceLoader}. + * Relative paths are allowed when running in an ApplicationContext. + * + */ + public void setResourceLoaderPath(String resourceLoaderPath) { + this.resourceLoaderPath = resourceLoaderPath; + } + + public String getResourceLoaderPath() { + return this.resourceLoaderPath; + } + + /** + * Set a pre-configured MarkupTemplateEngine to use for the Groovy Markup + * Template web configuration. + *

Note that this engine instance has to be manually configured, since all + * other bean properties of this configurer will be ignored. + */ + public void setTemplateEngine(MarkupTemplateEngine templateEngine) { + this.templateEngine = templateEngine; + } + + /** + * {@inheritDoc} + */ + public MarkupTemplateEngine getTemplateEngine() { + return templateEngine; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + protected ApplicationContext getApplicationContext() { + return this.applicationContext; + } + + /** + * This method should not be used, since the considered Locale for resolving + * templates is the Locale for the current HTTP request. + */ + @Override + public void setLocale(Locale locale) { + super.setLocale(locale); + } + + + @Override + public void afterPropertiesSet() throws Exception { + if (this.templateEngine == null) { + this.templateEngine = createTemplateEngine(); + } + } + + protected MarkupTemplateEngine createTemplateEngine() throws IOException { + if (this.templateEngine == null) { + ClassLoader templateClassLoader = createTemplateClassLoader(); + this.templateEngine = new MarkupTemplateEngine(templateClassLoader, this, createTemplateResolver()); + } + return this.templateEngine; + } + + /** + * Create a parent classloader for Groovy to use as parent classloader when + * loading and compiling templates. + */ + protected ClassLoader createTemplateClassLoader() throws IOException { + String[] paths = StringUtils.commaDelimitedListToStringArray(getResourceLoaderPath()); + List urls = new ArrayList(); + for (String path : paths) { + Resource[] resources = getApplicationContext().getResources(path); + if (resources.length > 0) { + for (Resource resource : resources) { + if (resource.exists()) { + urls.add(resource.getURL()); + } + } + } + } + ClassLoader classLoader = getApplicationContext().getClassLoader(); + return (urls.size() > 0 ? new URLClassLoader(urls.toArray(new URL[urls.size()]), classLoader) : classLoader); + } + + /** + * Return a {@link TemplateResolver} to be used by the + * {@link MarkupTemplateEngine} to resolve template files. + * + *

The default implementation returns a resolver that uses the Locale + * associated with the current request to resolve the relevant template file. + * Effectively the locale configured at the engine level is ignored. + * + * @see LocaleContextHolder + * @see #setLocale(java.util.Locale) + */ + protected TemplateResolver createTemplateResolver() { + return new MarkupTemplateResolver(); + } + + + /** + * A custom {@link TemplateResolver template resolver} that resolves + * templates using the locale found with LocaleContextHolder. + * + * @author Cedric Champeau + * @author Brian Clozel + * @see LocaleContextHolder + */ + private static class MarkupTemplateResolver implements TemplateResolver { + + private ClassLoader classLoader; + + @Override + public void configure(ClassLoader templateClassLoader, TemplateConfiguration configuration) { + this.classLoader = templateClassLoader; + } + + @Override + public URL resolveTemplate(final String templatePath) throws IOException { + MarkupTemplateEngine.TemplateResource resource = MarkupTemplateEngine.TemplateResource.parse(templatePath); + Locale locale = LocaleContextHolder.getLocale(); + URL url = this.classLoader.getResource(resource.withLocale(locale.toString().replace("-", "_")).toString()); + if (url == null) { + url = this.classLoader.getResource(resource.withLocale(locale.getLanguage()).toString()); + } + if (url == null) { + url = this.classLoader.getResource(resource.withLocale(null).toString()); + } + if (url == null) { + throw new IOException("Unable to load template:" + templatePath); + } + return url; + } + } +} diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupView.java new file mode 100644 index 0000000000..05859b99cf --- /dev/null +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupView.java @@ -0,0 +1,142 @@ +/* + * 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.view.groovy; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.util.Locale; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import groovy.text.Template; +import groovy.text.markup.MarkupTemplateEngine; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactoryUtils; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextException; +import org.springframework.web.servlet.view.AbstractTemplateView; +import org.springframework.web.util.NestedServletException; + +/** + * An {@link org.springframework.web.servlet.view.AbstractTemplateView AbstractTemplateView} + * based on Groovy XML/XHTML markup templates. + * + *

Spring's Groovy Markup Template support requires Groovy 2.3.1 and higher. + * + * @author Brian Clozel + * @author Rossen Stoyanchev + * @since 4.1 + * + * @see GroovyMarkupViewResolver + * @see GroovyMarkupConfigurer + * @see + * Groovy Markup Template engine documentation + */ +public class GroovyMarkupView extends AbstractTemplateView { + + private MarkupTemplateEngine engine; + + + /** + * Set the MarkupTemplateEngine to use in this view. + * + *

If not set, the engine is auto-detected by looking up up a single + * {@link GroovyMarkupConfig} bean in the web application context and using + * it to obtain the configured {@code MarkupTemplateEngine} instance. + * + * @see GroovyMarkupConfig + */ + public void setTemplateEngine(MarkupTemplateEngine engine) { + this.engine = engine; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean checkResource(Locale locale) throws Exception { + try { + this.engine.resolveTemplate(getUrl()); + } + catch (IOException exception) { + return false; + } + return true; + } + + /** + * Invoked at startup. + * If no {@link #setTemplateEngine(MarkupTemplateEngine) templateEngine} has + * been manually set, this method looks up a {@link GroovyMarkupConfig} bean + * by type and uses it to obtain the Groovy Markup template engine. + * + * @see GroovyMarkupConfig + * @see #setTemplateEngine(groovy.text.markup.MarkupTemplateEngine) + */ + @Override + protected void initApplicationContext(ApplicationContext context) { + super.initApplicationContext(); + if (this.engine == null) { + setTemplateEngine(autodetectMarkupTemplateEngine()); + } + } + + /** + * Auto-detect a MarkupTemplateEngine via the ApplicationContext. + * Called if a MarkupTemplateEngine has not been manually configured. + */ + protected MarkupTemplateEngine autodetectMarkupTemplateEngine() throws BeansException { + try { + return BeanFactoryUtils.beanOfTypeIncludingAncestors(getApplicationContext(), + GroovyMarkupConfig.class, true, false).getTemplateEngine(); + } + catch (NoSuchBeanDefinitionException ex) { + throw new ApplicationContextException( + "Expected a single GroovyMarkupConfig bean in the current Servlet web application context " + + "or the parent root context: GroovyMarkupConfigurer is the usual implementation. " + + "This bean may have any name.", ex); + } + } + + @Override + protected void renderMergedTemplateModel(Map model, + HttpServletRequest request, HttpServletResponse response) throws Exception { + + Template template = getTemplate(getUrl()); + template.make(model).writeTo(new BufferedWriter(response.getWriter())); + } + + /** + * Return a template compiled by the configured Groovy Markup template engine + * for the given view URL. + */ + protected Template getTemplate(String viewUrl) throws Exception { + try { + return this.engine.createTemplateByPath(viewUrl); + } + catch (ClassNotFoundException ex) { + Throwable cause = ex.getCause() != null ? ex.getCause() : ex; + throw new NestedServletException( + "Could not find class while rendering Groovy Markup view with name '" + + getUrl() + "': " + ex.getMessage() + "'", cause); + } + } + +} diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupViewResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupViewResolver.java new file mode 100644 index 0000000000..b37396a7ca --- /dev/null +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/GroovyMarkupViewResolver.java @@ -0,0 +1,61 @@ +/* + * 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.view.groovy; + +import java.util.Locale; + +import org.springframework.web.servlet.view.AbstractTemplateViewResolver; + +/** + * Convenience subclass of + * {@link org.springframework.web.servlet.view.AbstractTemplateViewResolver} + * that supports {@link GroovyMarkupView} (i.e. Groovy XML/XHTML markup templates) + * and custom subclasses of it. + * + *

The view class for all views created by this resolver can be specified + * via the {@link #setViewClass(Class)} property. + * + *

Note: When chaining ViewResolvers this resolver will check for the + * existence of the specified template resources and only return a non-null + * View object if a template is actually found. + * + * @author Brian Clozel + * @see GroovyMarkupConfigurer + * @since 4.1 + */ +public class GroovyMarkupViewResolver extends AbstractTemplateViewResolver { + + + public GroovyMarkupViewResolver() { + this.setViewClass(requiredViewClass()); + } + + + @Override + protected Class requiredViewClass() { + return GroovyMarkupView.class; + } + + /** + * This resolver supports i18n, so cache keys should contain the locale. + */ + @Override + protected Object getCacheKey(String viewName, Locale locale) { + return viewName + "_" + locale; + } + +} \ No newline at end of file diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/package-info.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/package-info.java new file mode 100644 index 0000000000..5323cc0ce3 --- /dev/null +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/groovy/package-info.java @@ -0,0 +1,9 @@ + +/** + * Support classes for the integration of + * + * Groovy Templates as Spring web view technology. + * Contains a View implementation for Groovy templates. + */ +package org.springframework.web.servlet.view.groovy; + diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/groovy/GroovyMarkupConfigurerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/groovy/GroovyMarkupConfigurerTests.java new file mode 100644 index 0000000000..081d437d01 --- /dev/null +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/groovy/GroovyMarkupConfigurerTests.java @@ -0,0 +1,173 @@ +/* + * 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.view.groovy; + +import static org.junit.Assert.*; + +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Arrays; +import java.util.Locale; + +import groovy.text.TemplateEngine; +import groovy.text.markup.MarkupTemplateEngine; +import groovy.text.markup.TemplateConfiguration; +import groovy.text.markup.TemplateResolver; +import org.hamcrest.Matchers; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.context.support.StaticApplicationContext; + +/** + * Unit tests for + * {@link org.springframework.web.servlet.view.groovy.GroovyMarkupConfigurer}. + * + * @author Brian Clozel + */ +public class GroovyMarkupConfigurerTests { + + private static final String RESOURCE_LOADER_PATH = "classpath:org/springframework/web/servlet/view/groovy/"; + + private StaticApplicationContext applicationContext; + + private static final String TEMPLATE_PREFIX = "org/springframework/web/servlet/view/groovy/"; + + private GroovyMarkupConfigurer configurer; + + private TemplateResolver resolver; + + @Before + public void setup() throws Exception { + this.applicationContext = new StaticApplicationContext(); + this.configurer = new GroovyMarkupConfigurer(); + this.configurer.setResourceLoaderPath(RESOURCE_LOADER_PATH); + this.resolver = this.configurer.createTemplateResolver(); + this.resolver.configure(this.getClass().getClassLoader(), null); + } + + @Test + public void defaultTemplateEngine() throws Exception { + this.configurer.setApplicationContext(this.applicationContext); + this.configurer.afterPropertiesSet(); + + TemplateEngine engine = this.configurer.getTemplateEngine(); + assertNotNull(engine); + assertEquals(MarkupTemplateEngine.class, engine.getClass()); + + MarkupTemplateEngine markupEngine = (MarkupTemplateEngine) engine; + TemplateConfiguration configuration = markupEngine.getTemplateConfiguration(); + assertNotNull(configuration); + assertEquals(GroovyMarkupConfigurer.class, configuration.getClass()); + } + + @Test + public void customTemplateEngine() throws Exception { + this.configurer.setApplicationContext(this.applicationContext); + this.configurer.setTemplateEngine(new TestTemplateEngine()); + this.configurer.afterPropertiesSet(); + + TemplateEngine engine = this.configurer.getTemplateEngine(); + assertNotNull(engine); + assertEquals(TestTemplateEngine.class, engine.getClass()); + } + + @Test + public void customTemplateConfiguration() throws Exception { + this.configurer.setApplicationContext(this.applicationContext); + this.configurer.setCacheTemplates(false); + this.configurer.afterPropertiesSet(); + + TemplateEngine engine = this.configurer.getTemplateEngine(); + assertNotNull(engine); + assertEquals(MarkupTemplateEngine.class, engine.getClass()); + + MarkupTemplateEngine markupEngine = (MarkupTemplateEngine) engine; + TemplateConfiguration configuration = markupEngine.getTemplateConfiguration(); + assertNotNull(configuration); + assertFalse(configuration.isCacheTemplates()); + } + + @Test + public void parentLoader() throws Exception { + + this.configurer.setApplicationContext(this.applicationContext); + + ClassLoader classLoader = this.configurer.createTemplateClassLoader(); + assertNotNull(classLoader); + URLClassLoader urlClassLoader = (URLClassLoader) classLoader; + assertThat(Arrays.asList(urlClassLoader.getURLs()), Matchers.hasSize(1)); + assertThat(Arrays.asList(urlClassLoader.getURLs()).get(0).toString(), + Matchers.endsWith("org/springframework/web/servlet/view/groovy/")); + + this.configurer.setResourceLoaderPath(RESOURCE_LOADER_PATH + ",classpath:org/springframework/web/servlet/view/"); + classLoader = this.configurer.createTemplateClassLoader(); + assertNotNull(classLoader); + urlClassLoader = (URLClassLoader) classLoader; + assertThat(Arrays.asList(urlClassLoader.getURLs()), Matchers.hasSize(2)); + assertThat(Arrays.asList(urlClassLoader.getURLs()).get(0).toString(), + Matchers.endsWith("org/springframework/web/servlet/view/groovy/")); + assertThat(Arrays.asList(urlClassLoader.getURLs()).get(1).toString(), + Matchers.endsWith("org/springframework/web/servlet/view/")); + } + + private class TestTemplateEngine extends MarkupTemplateEngine { + + public TestTemplateEngine() { + super(new TemplateConfiguration()); + } + } + + @Test + public void resolveSampleTemplate() throws Exception { + URL url = this.resolver.resolveTemplate(TEMPLATE_PREFIX + "test.tpl"); + Assert.assertNotNull(url); + } + + @Test + public void resolveI18nFullLocale() throws Exception { + LocaleContextHolder.setLocale(Locale.GERMANY); + URL url = this.resolver.resolveTemplate(TEMPLATE_PREFIX + "i18n.tpl"); + Assert.assertNotNull(url); + Assert.assertThat(url.getPath(), Matchers.containsString("i18n_de_DE.tpl")); + } + + @Test + public void resolveI18nPartialLocale() throws Exception { + LocaleContextHolder.setLocale(Locale.FRANCE); + URL url = this.resolver.resolveTemplate(TEMPLATE_PREFIX + "i18n.tpl"); + Assert.assertNotNull(url); + Assert.assertThat(url.getPath(), Matchers.containsString("i18n_fr.tpl")); + } + + @Test + public void resolveI18nDefaultLocale() throws Exception { + LocaleContextHolder.setLocale(Locale.US); + URL url = this.resolver.resolveTemplate(TEMPLATE_PREFIX + "i18n.tpl"); + Assert.assertNotNull(url); + Assert.assertThat(url.getPath(), Matchers.containsString("i18n.tpl")); + } + + @Test(expected = IOException.class) + public void failMissingTemplate() throws Exception { + LocaleContextHolder.setLocale(Locale.US); + this.resolver.resolveTemplate(TEMPLATE_PREFIX + "missing.tpl"); + Assert.fail(); + } +} diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/groovy/GroovyMarkupViewResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/groovy/GroovyMarkupViewResolverTests.java new file mode 100644 index 0000000000..2190e3689d --- /dev/null +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/groovy/GroovyMarkupViewResolverTests.java @@ -0,0 +1,50 @@ +/* + * 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.view.groovy; + +import java.util.Locale; + +import org.junit.Assert; +import org.junit.Test; + +import org.springframework.beans.DirectFieldAccessor; + +/** + * Unit tests for + * {@link org.springframework.web.servlet.view.groovy.GroovyMarkupViewResolver}. + * + * @author Brian Clozel + */ +public class GroovyMarkupViewResolverTests { + + @Test + public void viewClass() throws Exception { + GroovyMarkupViewResolver resolver = new GroovyMarkupViewResolver(); + Assert.assertEquals(GroovyMarkupView.class, resolver.requiredViewClass()); + DirectFieldAccessor viewAccessor = new DirectFieldAccessor(resolver); + Class viewClass = (Class) viewAccessor.getPropertyValue("viewClass"); + Assert.assertEquals(GroovyMarkupView.class, viewClass); + } + + @Test + public void cacheKey() throws Exception { + GroovyMarkupViewResolver resolver = new GroovyMarkupViewResolver(); + String cacheKey = (String) resolver.getCacheKey("test", Locale.US); + Assert.assertNotNull(cacheKey); + Assert.assertEquals("test_en_US", cacheKey); + } + +} diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/view/groovy/GroovyMarkupViewTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/groovy/GroovyMarkupViewTests.java new file mode 100644 index 0000000000..dafeae4a67 --- /dev/null +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/view/groovy/GroovyMarkupViewTests.java @@ -0,0 +1,217 @@ +/* + * 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.view.groovy; + +import static org.junit.Assert.*; +import static org.mockito.BDDMockito.*; +import static org.mockito.Mockito.mock; + +import java.io.IOException; +import java.io.Reader; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import javax.servlet.ServletContext; + +import groovy.text.Template; +import groovy.text.TemplateEngine; +import groovy.text.markup.MarkupTemplateEngine; +import groovy.text.markup.TemplateConfiguration; +import org.codehaus.groovy.control.CompilationFailedException; +import org.hamcrest.Matchers; +import org.junit.Before; +import org.junit.Test; + +import org.springframework.beans.DirectFieldAccessor; +import org.springframework.context.ApplicationContextException; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.mock.web.test.MockHttpServletRequest; +import org.springframework.mock.web.test.MockHttpServletResponse; +import org.springframework.mock.web.test.MockServletContext; +import org.springframework.web.context.WebApplicationContext; + +/** + * @author Brian Clozel + */ +public class GroovyMarkupViewTests { + + private static final String RESOURCE_LOADER_PATH = "classpath*:org/springframework/web/servlet/view/groovy/"; + + private WebApplicationContext webAppContext; + + private ServletContext servletContext; + + @Before + public void setup() { + this.webAppContext = mock(WebApplicationContext.class); + this.servletContext = new MockServletContext(); + this.servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.webAppContext); + } + + @Test + public void missingGroovyMarkupConfig() throws Exception { + GroovyMarkupView view = new GroovyMarkupView(); + given(this.webAppContext.getBeansOfType(GroovyMarkupConfig.class, true, false)) + .willReturn(new HashMap()); + + view.setUrl("sampleView"); + try { + view.setApplicationContext(this.webAppContext); + fail(); + } + catch (ApplicationContextException ex) { + assertTrue(ex.getMessage().contains("GroovyMarkupConfig")); + } + } + + @Test + public void customTemplateEngine() throws Exception { + + GroovyMarkupView view = new GroovyMarkupView(); + view.setTemplateEngine(new TestTemplateEngine()); + view.setApplicationContext(this.webAppContext); + + DirectFieldAccessor accessor = new DirectFieldAccessor(view); + TemplateEngine engine = (TemplateEngine) accessor.getPropertyValue("engine"); + assertNotNull(engine); + assertEquals(TestTemplateEngine.class, engine.getClass()); + } + + @Test + public void detectTemplateEngine() throws Exception { + + GroovyMarkupView view = new GroovyMarkupView(); + + view.setTemplateEngine(new TestTemplateEngine()); + view.setApplicationContext(this.webAppContext); + + DirectFieldAccessor accessor = new DirectFieldAccessor(view); + TemplateEngine engine = (TemplateEngine) accessor.getPropertyValue("engine"); + assertNotNull(engine); + assertEquals(TestTemplateEngine.class, engine.getClass()); + } + + @Test + public void checkResource() throws Exception { + + GroovyMarkupView view = createViewWithUrl("test.tpl"); + assertTrue(view.checkResource(Locale.US)); + } + + @Test + public void checkMissingResource() throws Exception { + + GroovyMarkupView view = createViewWithUrl("missing.tpl"); + assertFalse(view.checkResource(Locale.US)); + } + + @Test + public void checkI18nResource() throws Exception { + + GroovyMarkupView view = createViewWithUrl("i18n.tpl"); + assertTrue(view.checkResource(Locale.FRENCH)); + } + + @Test + public void checkI18nResourceMissingLocale() throws Exception { + + GroovyMarkupView view = createViewWithUrl("i18n.tpl"); + assertTrue(view.checkResource(Locale.CHINESE)); + } + + @Test + public void renderMarkupTemplate() throws Exception { + + Map model = new HashMap<>(); + model.put("name", "Spring"); + MockHttpServletResponse response = renderViewWithModel("test.tpl", model, Locale.US); + assertThat(response.getContentAsString(), Matchers.containsString("

Hello Spring

")); + } + + @Test + public void renderI18nTemplate() throws Exception { + Map model = new HashMap<>(); + model.put("name", "Spring"); + MockHttpServletResponse response = renderViewWithModel("i18n.tpl", model, Locale.FRANCE); + assertEquals("

Bonjour Spring

", response.getContentAsString()); + + response = renderViewWithModel("i18n.tpl", model, Locale.GERMANY); + assertEquals("

Include German

Hallo Spring

", response.getContentAsString()); + + response = renderViewWithModel("i18n.tpl", model, new Locale("es")); + assertEquals("

Include Default

¡hola Spring

", response.getContentAsString()); + } + + @Test + public void renderLayoutTemplate() throws Exception { + Map model = new HashMap<>(); + MockHttpServletResponse response = renderViewWithModel("content.tpl", model, Locale.US); + assertEquals("Layout example

This is the body

", + response.getContentAsString()); + } + + private MockHttpServletResponse renderViewWithModel(String viewUrl, Map model, Locale locale) throws Exception { + + GroovyMarkupView view = createViewWithUrl(viewUrl); + MockHttpServletResponse response = new MockHttpServletResponse(); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setPreferredLocales(Arrays.asList(locale)); + LocaleContextHolder.setLocale(locale); + view.renderMergedTemplateModel(model, request, response); + return response; + } + + private GroovyMarkupView createViewWithUrl(String viewUrl) throws Exception { + + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + ctx.register(GroovyMarkupConfiguration.class); + ctx.refresh(); + + GroovyMarkupView view = new GroovyMarkupView(); + view.setApplicationContext(ctx); + view.setUrl(viewUrl); + view.afterPropertiesSet(); + return view; + } + + public class TestTemplateEngine extends MarkupTemplateEngine { + + public TestTemplateEngine() { + super(new TemplateConfiguration()); + } + + @Override + public Template createTemplate(Reader reader) throws CompilationFailedException, ClassNotFoundException, IOException { + return null; + } + } + + @Configuration + static class GroovyMarkupConfiguration { + + @Bean + public GroovyMarkupConfig groovyMarkupConfigurer() { + GroovyMarkupConfigurer configurer = new GroovyMarkupConfigurer(); + configurer.setResourceLoaderPath(RESOURCE_LOADER_PATH); + return configurer; + } + } +} diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/groovy/content.tpl b/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/groovy/content.tpl new file mode 100644 index 0000000000..ed7557d3b9 --- /dev/null +++ b/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/groovy/content.tpl @@ -0,0 +1,3 @@ +layout 'layout-main.tpl', + title: 'Layout example', + bodyContents: contents { p('This is the body') } \ No newline at end of file diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/groovy/i18n.tpl b/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/groovy/i18n.tpl new file mode 100644 index 0000000000..f368eb4543 --- /dev/null +++ b/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/groovy/i18n.tpl @@ -0,0 +1 @@ +p('Hello Spring') \ No newline at end of file diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/groovy/i18n_de_DE.tpl b/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/groovy/i18n_de_DE.tpl new file mode 100644 index 0000000000..ba664d29db --- /dev/null +++ b/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/groovy/i18n_de_DE.tpl @@ -0,0 +1,2 @@ +include template: 'includes/include.tpl' +p('Hallo Spring') \ No newline at end of file diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/groovy/i18n_es.tpl b/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/groovy/i18n_es.tpl new file mode 100644 index 0000000000..2d52884bde --- /dev/null +++ b/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/groovy/i18n_es.tpl @@ -0,0 +1,2 @@ +include template: 'includes/include.tpl' +p('¡hola Spring') \ No newline at end of file diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/groovy/i18n_fr.tpl b/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/groovy/i18n_fr.tpl new file mode 100644 index 0000000000..171f1a6a63 --- /dev/null +++ b/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/groovy/i18n_fr.tpl @@ -0,0 +1 @@ +p('Bonjour Spring') \ No newline at end of file diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/groovy/includes/include.tpl b/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/groovy/includes/include.tpl new file mode 100644 index 0000000000..e97ee30160 --- /dev/null +++ b/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/groovy/includes/include.tpl @@ -0,0 +1 @@ +p('Include Default') \ No newline at end of file diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/groovy/includes/include_de_DE.tpl b/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/groovy/includes/include_de_DE.tpl new file mode 100644 index 0000000000..7408223b0c --- /dev/null +++ b/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/groovy/includes/include_de_DE.tpl @@ -0,0 +1 @@ +p('Include German') \ No newline at end of file diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/groovy/layout-main.tpl b/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/groovy/layout-main.tpl new file mode 100644 index 0000000000..7248240b1a --- /dev/null +++ b/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/groovy/layout-main.tpl @@ -0,0 +1,8 @@ +html { + head { + title(title) + } + body { + bodyContents() + } +} \ No newline at end of file diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/groovy/test.tpl b/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/groovy/test.tpl new file mode 100644 index 0000000000..f19b43c04d --- /dev/null +++ b/spring-webmvc/src/test/resources/org/springframework/web/servlet/view/groovy/test.tpl @@ -0,0 +1,13 @@ +yieldUnescaped '' +html { + head { + meta('http-equiv':'"Content-Type", content="text/html; charset=utf-8"') + title('Spring Framework Groovy Template Support') + } + body { + div(class:'test') { + h1("Hello $name") + p("Groovy Templates") + } + } +} \ No newline at end of file