Refactor Mustache views support in Spring MVC

This commit simplifies the Mustache support for Spring MVC and removes
the included (view-based) i18n support in favor of more idiomatic
constructs like Mustache lambdas.

Fixes gh-8941
pull/9002/head
Brian Clozel 8 years ago
parent 7e77e648bf
commit ec25e51f1f

@ -114,10 +114,9 @@ public class MustacheAutoConfiguration {
@Bean
@ConditionalOnMissingBean(MustacheViewResolver.class)
public MustacheViewResolver mustacheViewResolver(Compiler mustacheCompiler) {
MustacheViewResolver resolver = new MustacheViewResolver();
MustacheViewResolver resolver = new MustacheViewResolver(mustacheCompiler);
this.mustache.applyToViewResolver(resolver);
resolver.setCharset(this.mustache.getCharsetName());
resolver.setCompiler(mustacheCompiler);
resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
return resolver;
}

@ -16,58 +16,84 @@
package org.springframework.boot.autoconfigure.mustache.servlet;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Locale;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Template;
import org.springframework.core.io.Resource;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.view.AbstractTemplateView;
/**
* Spring MVC {@link View} using the Mustache template engine.
*
* @author Brian Clozel
* @author Dave Syer
* @author Phillip Webb
* @since 1.2.2
* @since 2.0.0
*/
public class MustacheView extends AbstractTemplateView {
private Template template;
private Mustache.Compiler compiler;
private String charset;
/**
* Create a new {@link MustacheView} instance.
* @since 1.2.5
* @see #setTemplate(Template)
* Set the Mustache compiler to be used by this view.
* <p>Typically this property is not set directly. Instead a single
* {@link Mustache.Compiler} is expected in the Spring application context
* which is used to compile Mustache templates.
* @param compiler the Mustache compiler
*/
public MustacheView() {
public void setCompiler(Mustache.Compiler compiler) {
this.compiler = compiler;
}
/**
* Create a new {@link MustacheView} with the specified template.
* @param template the source template
* Set the charset used for reading Mustache template files.
* @param charset the charset to use for reading template files
*/
public MustacheView(Template template) {
this.template = template;
public void setCharset(String charset) {
this.charset = charset;
}
/**
* Set the Mustache template that should actually be rendered.
* @param template the mustache template
* @since 1.2.5
*/
public void setTemplate(Template template) {
this.template = template;
@Override
public boolean checkResource(Locale locale) throws Exception {
Resource resource = getApplicationContext().getResource(this.getUrl());
return (resource != null && resource.exists());
}
@Override
protected void renderMergedTemplateModel(Map<String, Object> model,
HttpServletRequest request, HttpServletResponse response) throws Exception {
if (this.template != null) {
this.template.execute(model, response.getWriter());
protected void renderMergedTemplateModel(Map<String, Object> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
Template template = createTemplate(getApplicationContext().getResource(this.getUrl()));
if (template != null) {
template.execute(model, response.getWriter());
}
}
private Template createTemplate(Resource resource) throws IOException {
Reader reader = getReader(resource);
try {
return this.compiler.compile(reader);
}
finally {
reader.close();
}
}
private Reader getReader(Resource resource) throws IOException {
if (this.charset != null) {
return new InputStreamReader(resource.getInputStream(), this.charset);
}
return new InputStreamReader(resource.getInputStream());
}
}

@ -16,50 +16,46 @@
package org.springframework.boot.autoconfigure.mustache.servlet;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Locale;
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Mustache.Compiler;
import com.samskivert.mustache.Template;
import org.springframework.beans.propertyeditors.LocaleEditor;
import org.springframework.core.io.Resource;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.view.AbstractTemplateViewResolver;
import org.springframework.web.servlet.view.AbstractUrlBasedView;
/**
* Spring MVC {@link ViewResolver} for Mustache.
*
* @author Dave Syer
* @author Andy Wilkinson
* @author Phillip Webb
* @since 1.2.2
* @author Brian Clozel
* @since 2.0.0
*/
public class MustacheViewResolver extends AbstractTemplateViewResolver {
private Compiler compiler = Mustache.compiler();
private final Mustache.Compiler compiler;
private String charset;
/**
* Create a {@code MustacheViewResolver} backed by a default
* instance of a {@link Mustache.Compiler}.
*/
public MustacheViewResolver() {
this.compiler = Mustache.compiler();
setViewClass(requiredViewClass());
}
@Override
protected Class<?> requiredViewClass() {
return MustacheView.class;
}
/**
* Set the compiler.
* @param compiler the compiler
* Create a {@code MustacheViewResolver} backed by a custom
* instance of a {@link Mustache.Compiler}.
* @param compiler the Mustache compiler used to compile templates
*/
public void setCompiler(Compiler compiler) {
public MustacheViewResolver(Mustache.Compiler compiler) {
this.compiler = compiler;
setViewClass(requiredViewClass());
}
@Override
protected Class<?> requiredViewClass() {
return MustacheView.class;
}
/**
@ -71,57 +67,11 @@ public class MustacheViewResolver extends AbstractTemplateViewResolver {
}
@Override
protected View loadView(String viewName, Locale locale) throws Exception {
Resource resource = resolveResource(viewName, locale);
if (resource == null) {
return null;
}
MustacheView mustacheView = (MustacheView) super.loadView(viewName, locale);
mustacheView.setTemplate(createTemplate(resource));
return mustacheView;
}
private Resource resolveResource(String viewName, Locale locale) {
return resolveFromLocale(viewName, getLocale(locale));
}
private Resource resolveFromLocale(String viewName, String locale) {
Resource resource = getApplicationContext()
.getResource(getPrefix() + viewName + locale + getSuffix());
if (resource == null || !resource.exists()) {
if (locale.isEmpty()) {
return null;
}
int index = locale.lastIndexOf("_");
return resolveFromLocale(viewName, locale.substring(0, index));
}
return resource;
}
private String getLocale(Locale locale) {
if (locale == null) {
return "";
}
LocaleEditor localeEditor = new LocaleEditor();
localeEditor.setValue(locale);
return "_" + localeEditor.getAsText();
}
private Template createTemplate(Resource resource) throws IOException {
Reader reader = getReader(resource);
try {
return this.compiler.compile(reader);
}
finally {
reader.close();
}
}
private Reader getReader(Resource resource) throws IOException {
if (this.charset != null) {
return new InputStreamReader(resource.getInputStream(), this.charset);
}
return new InputStreamReader(resource.getInputStream());
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
MustacheView view = (MustacheView) super.buildView(viewName);
view.setCompiler(this.compiler);
view.setCharset(this.charset);
return view;
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2016 the original author or authors.
* Copyright 2012-2017 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.
@ -16,23 +16,14 @@
package org.springframework.boot.autoconfigure.mustache.servlet;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.Locale;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.io.Resource;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.support.StaticWebApplicationContext;
import org.springframework.web.servlet.View;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link MustacheViewResolver}.
@ -46,7 +37,9 @@ public class MustacheViewResolverTests {
@Before
public void init() {
this.resolver.setApplicationContext(new StaticWebApplicationContext());
GenericApplicationContext applicationContext = new GenericApplicationContext();
applicationContext.refresh();
this.resolver.setApplicationContext(applicationContext);
this.resolver.setServletContext(new MockServletContext());
this.resolver.setPrefix("classpath:/mustache-templates/");
this.resolver.setSuffix(".html");
@ -58,32 +51,10 @@ public class MustacheViewResolverTests {
}
@Test
public void resolveNullLocale() throws Exception {
public void resolveExisting() throws Exception {
assertThat(this.resolver.resolveViewName("foo", null)).isNotNull();
}
@Test
public void resolveDefaultLocale() throws Exception {
assertThat(this.resolver.resolveViewName("foo", Locale.US)).isNotNull();
}
@Test
public void resolveDoubleLocale() throws Exception {
assertThat(this.resolver.resolveViewName("foo", Locale.CANADA_FRENCH))
.isNotNull();
}
@Test
public void resolveTripleLocale() throws Exception {
assertThat(this.resolver.resolveViewName("foo", new Locale("en", "GB", "cy")))
.isNotNull();
}
@Test
public void resolveSpecificLocale() throws Exception {
assertThat(this.resolver.resolveViewName("foo", new Locale("de"))).isNotNull();
}
@Test
public void setsContentType() throws Exception {
this.resolver.setContentType("application/octet-stream");
@ -92,24 +63,4 @@ public class MustacheViewResolverTests {
}
@Test
public void templateResourceInputStreamIsClosed() throws Exception {
final Resource resource = mock(Resource.class);
given(resource.exists()).willReturn(true);
InputStream inputStream = new ByteArrayInputStream(new byte[0]);
InputStream spyInputStream = spy(inputStream);
given(resource.getInputStream()).willReturn(spyInputStream);
this.resolver = new MustacheViewResolver();
this.resolver.setApplicationContext(new StaticWebApplicationContext() {
@Override
public Resource getResource(String location) {
return resource;
}
});
this.resolver.loadView("foo", null);
verify(spyInputStream).close();
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2016 the original author or authors.
* Copyright 2012-2017 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.
@ -55,12 +55,12 @@ public class MustacheViewTests {
@Test
public void viewResolvesHandlebars() throws Exception {
MustacheView view = new MustacheView(
Mustache.compiler().compile("Hello {{msg}}"));
MustacheView view = new MustacheView();
view.setCompiler(Mustache.compiler());
view.setUrl("classpath:/mustache-templates/foo.html");
view.setApplicationContext(this.context);
view.render(Collections.singletonMap("msg", "World"), this.request,
this.response);
assertThat(this.response.getContentAsString()).isEqualTo("Hello World");
view.render(Collections.singletonMap("World", "Spring"), this.request, this.response);
assertThat(this.response.getContentAsString()).isEqualTo("Hello Spring");
}
}

@ -120,12 +120,12 @@ public class MustacheWebIntegrationTests {
@Bean
public MustacheViewResolver viewResolver() {
MustacheViewResolver resolver = new MustacheViewResolver();
Mustache.Compiler compiler = Mustache.compiler()
.withLoader(new MustacheResourceTemplateLoader("classpath:/mustache-templates/",
".html"));
MustacheViewResolver resolver = new MustacheViewResolver(compiler);
resolver.setPrefix("classpath:/mustache-templates/");
resolver.setSuffix(".html");
resolver.setCompiler(
Mustache.compiler().withLoader(new MustacheResourceTemplateLoader(
"classpath:/mustache-templates/", ".html")));
return resolver;
}

Loading…
Cancel
Save