Add Spring View support for Groovy Markup Templates

This commit adds support for Groovy Markup templates.
Spring's support requires Groovy 2.3.1+.

To use it, simply create a GroovyMarkupConfigurer and a
GroovyMarkupViewResolver beans in the web application context.

Issue: SPR-11789
This commit is contained in:
Brian Clozel 2014-06-03 15:50:50 +02:00 committed by Rossen Stoyanchev
parent 6b6b008c1f
commit 0ecfa8e404
18 changed files with 956 additions and 0 deletions

View File

@ -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"

View File

@ -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();
}

View File

@ -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:
*
* <pre class="code">
*
* // Add the following to an &#64;Configuration class
*
* &#64;Bean
* public GroovyMarkupConfig groovyMarkupConfigurer() {
*
* GroovyMarkupConfigurer configurer = new GroovyMarkupConfigurer();
* configurer.setResourceLoaderPath("classpath:/WEB-INF/groovymarkup/");
* return configurer;
* }
* </pre>
*
* By default this bean will create a {@link MarkupTemplateEngine} with:
* <ul>
* <li>a parent ClassLoader for loading Groovy templates with their references
* <li>the default configuration in the base class {@link TemplateConfiguration}
* <li>a {@link groovy.text.markup.TemplateResolver} for resolving template files
* </ul>
*
* You can provide the {@link MarkupTemplateEngine} instance directly to this bean
* in which case all other properties will not be effectively ignored.
*
* <p>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.
*
* <p>Note that resource caching is enabled by default in {@link MarkupTemplateEngine}.
* Use the {@link #setCacheTemplates(boolean)} to configure that as necessary.
* <p>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 <a href="http://beta.groovy-lang.org/docs/groovy-2.3.2/html/documentation/markup-template-engine.html">
* Groovy Markup Template engine documentation</a>
*/
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.
* <p>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<URL> urls = new ArrayList<URL>();
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.
*
* <p>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;
}
}
}

View File

@ -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.
*
* <p>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 <a href="http://beta.groovy-lang.org/docs/groovy-2.3.2/html/documentation/markup-template-engine.html">
* Groovy Markup Template engine documentation</a>
*/
public class GroovyMarkupView extends AbstractTemplateView {
private MarkupTemplateEngine engine;
/**
* Set the MarkupTemplateEngine to use in this view.
*
* <p>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<String, Object> 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);
}
}
}

View File

@ -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.
*
* <p>The view class for all views created by this resolver can be specified
* via the {@link #setViewClass(Class)} property.
*
* <p><b>Note:</b> 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;
}
}

View File

@ -0,0 +1,9 @@
/**
* Support classes for the integration of
* <a href="http://beta.groovy-lang.org/docs/groovy-2.3.0/html/documentation/markup-template-engine.html">
* Groovy Templates</a> as Spring web view technology.
* Contains a View implementation for Groovy templates.
*/
package org.springframework.web.servlet.view.groovy;

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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<String, GroovyMarkupConfig>());
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<String, Object> model = new HashMap<>();
model.put("name", "Spring");
MockHttpServletResponse response = renderViewWithModel("test.tpl", model, Locale.US);
assertThat(response.getContentAsString(), Matchers.containsString("<h1>Hello Spring</h1>"));
}
@Test
public void renderI18nTemplate() throws Exception {
Map<String, Object> model = new HashMap<>();
model.put("name", "Spring");
MockHttpServletResponse response = renderViewWithModel("i18n.tpl", model, Locale.FRANCE);
assertEquals("<p>Bonjour Spring</p>", response.getContentAsString());
response = renderViewWithModel("i18n.tpl", model, Locale.GERMANY);
assertEquals("<p>Include German</p><p>Hallo Spring</p>", response.getContentAsString());
response = renderViewWithModel("i18n.tpl", model, new Locale("es"));
assertEquals("<p>Include Default</p><p>¡hola Spring</p>", response.getContentAsString());
}
@Test
public void renderLayoutTemplate() throws Exception {
Map<String, Object> model = new HashMap<>();
MockHttpServletResponse response = renderViewWithModel("content.tpl", model, Locale.US);
assertEquals("<html><head><title>Layout example</title></head><body><p>This is the body</p></body></html>",
response.getContentAsString());
}
private MockHttpServletResponse renderViewWithModel(String viewUrl, Map<String, Object> 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;
}
}
}

View File

@ -0,0 +1,3 @@
layout 'layout-main.tpl',
title: 'Layout example',
bodyContents: contents { p('This is the body') }

View File

@ -0,0 +1 @@
p('Hello Spring')

View File

@ -0,0 +1,2 @@
include template: 'includes/include.tpl'
p('Hallo Spring')

View File

@ -0,0 +1,2 @@
include template: 'includes/include.tpl'
p('¡hola Spring')

View File

@ -0,0 +1 @@
p('Bonjour Spring')

View File

@ -0,0 +1,8 @@
html {
head {
title(title)
}
body {
bodyContents()
}
}

View File

@ -0,0 +1,13 @@
yieldUnescaped '<!DOCTYPE html>'
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")
}
}
}