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:
committed by
Rossen Stoyanchev
parent
6b6b008c1f
commit
0ecfa8e404
@@ -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();
|
||||
}
|
||||
@@ -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 @Configuration class
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user