Skip to content

Commit

Permalink
Support Groovy Markup templating in Spring MVC
Browse files Browse the repository at this point in the history
Solution #1

This commit adds support for Groovy templating, available as of
Groovy 2.3.0 (and 2.3.1 for advanced use cases, such as layouts and
fragments).

To enable this, one should create a new GroovyMarkupViewResolver
Bean in their application context.

Issue: SPR-11789
  • Loading branch information
bclozel committed Jun 5, 2014
1 parent 5a8e470 commit 2f95de4
Show file tree
Hide file tree
Showing 17 changed files with 726 additions and 0 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* 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.Template;
import org.springframework.web.servlet.view.AbstractTemplateView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.io.BufferedWriter;
import java.util.Locale;
import java.util.Map;

/**
* View using Groovy Markup Templates.
*
* @author Brian Clozel
* @since 4.1
* @see GroovyMarkupViewResolver
*/
public class GroovyMarkupView extends AbstractTemplateView {

private final Template template;

public GroovyMarkupView(Template template) {
this.template = template;
}

@Override
protected void renderMergedTemplateModel(Map<String, Object> model,
HttpServletRequest request, HttpServletResponse response) throws Exception {
this.template.make(model).writeTo(new BufferedWriter(response.getWriter()));
}

/**
* This class is using a pre-compiled groovy template,
* so the template file URL is of no use here.
*/
@Override
protected boolean isUrlRequired() {
return false;
}

@Override
public boolean checkResource(Locale locale) throws Exception {
return this.template != null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/*
* 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.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import groovy.text.Template;
import groovy.text.markup.MarkupTemplateEngine;
import groovy.text.markup.TemplateConfiguration;

import org.springframework.context.ApplicationContextException;
import org.springframework.core.io.Resource;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.view.AbstractTemplateViewResolver;
import org.springframework.web.servlet.view.AbstractView;

/**
* 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.
*
* This ViewResolver requires Groovy 2.3.0 and higher and uses by default an instance of a
* {@link groovy.text.markup.MarkupTemplateEngine}.
*
* @author Brian Clozel
* @author Dave Syer
* @since 4.1
* @see groovy.text.markup.MarkupTemplateEngine
*/
public class GroovyMarkupViewResolver extends AbstractTemplateViewResolver {

private MarkupTemplateEngine engine;

private final TemplateConfiguration templateConfiguration;

/**
* Create a GroovyTemplateViewResolver initialized with a {@link MarkupTemplateEngine}
* that's configured with:
* <ul>
* <li>a default {@link TemplateConfiguration}
* <li>a {@link MarkupTemplateResolver} for resolving templates
* <li>a parent classLoader to be used by Groovy to load code and resources within templates
* </ul>
*
* @see #createParentLoaderForTemplates()
*/
public GroovyMarkupViewResolver() throws Exception {
this(null);
}

/**
* Create a GroovyTemplateViewResolver using a {@link MarkupTemplateEngine}
* configured with the provided {@link TemplateConfiguration}.
*/
public GroovyMarkupViewResolver(TemplateConfiguration templateConfiguration) throws Exception {
this.templateConfiguration =
(templateConfiguration != null) ? templateConfiguration : new TemplateConfiguration();
this.setViewClass(requiredViewClass());
}

@Override
protected Class<?> requiredViewClass() {
return GroovyMarkupView.class;
}

/**
* Set the @{link MarkupTemplateEngine} to use for compiling {@link Template}s.
* This will override the TemplateConfiguration provided in the constructor,
* as well as the default engine configuration.
*/
public void setEngine(MarkupTemplateEngine engine) {
this.engine = engine;
}

@Override
protected void initApplicationContext() {
try {
super.initApplicationContext();
if (this.engine == null) {
this.engine = new MarkupTemplateEngine(this.createParentLoaderForTemplates(),
this.templateConfiguration, new MarkupTemplateResolver());
}
} catch (Exception exc) {
throw new ApplicationContextException("Could not initialize Groovy Template engine", exc);
}
}

/**
* Create a parent class loader to be used by the Groovy class loader when
* loading and compiling templates.
*/
protected ClassLoader createParentLoaderForTemplates() throws Exception {
Resource[] resources = this.getApplicationContext().getResources(this.getPrefix());
if (resources.length > 0) {
List<URL> urls = new ArrayList<URL>();
for (Resource resource : resources) {
if (resource.exists()) {
urls.add(resource.getURL());
}
}
return new URLClassLoader(urls.toArray(new URL[urls.size()]),
getApplicationContext().getClassLoader());
}
else {
return getApplicationContext().getClassLoader();
}
}

/**
* This resolver supports i18n, so cache keys should contain the locale.
*/
@Override
protected Object getCacheKey(String viewName, Locale locale) {
return viewName + "_" + locale;
}

@Override
protected View loadView(String viewName, Locale locale) throws Exception {

URL viewURL = getViewURL(viewName, locale);
if(viewURL == null) {
return null;
}
else {
Template template = this.engine.createTemplate(viewURL);
GroovyMarkupView view = new GroovyMarkupView(template);

view.setUrl(viewURL.getPath());
String contentType = getContentType();
if (contentType != null) {
view.setContentType(contentType);
}
view.setServletContext(getServletContext());
view.setRequestContextAttribute(getRequestContextAttribute());
view.setAttributesMap(getAttributesMap());

return (View)getApplicationContext().getAutowireCapableBeanFactory().initializeBean(view, viewName);
}
}

private URL getViewURL(String viewName, Locale locale) throws Exception{

Resource resource = getApplicationContext().getResource(getPrefix() + viewName
+ "_" + locale.toString().replace("-", "_") + getSuffix());

if (!resource.exists()) {
resource = getApplicationContext().getResource(getPrefix() + viewName
+ "_" + locale.getLanguage() + getSuffix());
}
if (!resource.exists()) {
resource = getApplicationContext().getResource(getPrefix() + viewName + getSuffix());
}

return resource.exists() ? resource.getURL() : null;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright 2012-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;
import groovy.text.markup.TemplateConfiguration;
import groovy.text.markup.TemplateResolver;

import java.io.IOException;
import java.net.URL;
import java.util.Locale;

import org.springframework.context.i18n.LocaleContextHolder;

/**
* A custom {@link groovy.text.markup.TemplateResolver template resolver} which resolves
* templates using the locale found in the thread locale. This resolver ignores the
* template engine configuration locale.
*
* @author Cedric Champeau
* @author Brian Clozel
* @since 4.1
* @see LocaleContextHolder
*/
public class MarkupTemplateResolver implements TemplateResolver {

private ClassLoader templateClassLoader;

@Override
public void configure(final ClassLoader templateClassLoader, final TemplateConfiguration configuration) {
this.templateClassLoader = templateClassLoader;
}

@Override
public URL resolveTemplate(final String templatePath) throws IOException {
MarkupTemplateEngine.TemplateResource resource = MarkupTemplateEngine.TemplateResource.parse(templatePath);
Locale locale = LocaleContextHolder.getLocale();
URL url = this.templateClassLoader
.getResource(resource.withLocale(locale.toString().replace("-", "_")).toString());
if (url == null) {
url = this.templateClassLoader
.getResource(resource.withLocale(locale.getLanguage()).toString());
}
if (url == null) {
url = this.templateClassLoader.getResource(resource.withLocale(null).toString());
}
if (url == null) {
throw new IOException("Unable to load template:" + templatePath);
}
return url;
}

}
Original file line number Diff line number Diff line change
@@ -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;

0 comments on commit 2f95de4

Please sign in to comment.