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
This commit is contained in:
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
public void setCharset(String charset) {
|
||||
this.charset = charset;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderMergedTemplateModel(Map<String, Object> model,
|
||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
if (this.template != null) {
|
||||
this.template.execute(model, response.getWriter());
|
||||
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 {
|
||||
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,36 +16,40 @@
|
|||
|
||||
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());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@code MustacheViewResolver} backed by a custom
|
||||
* instance of a {@link Mustache.Compiler}.
|
||||
* @param compiler the Mustache compiler used to compile templates
|
||||
*/
|
||||
public MustacheViewResolver(Mustache.Compiler compiler) {
|
||||
this.compiler = compiler;
|
||||
setViewClass(requiredViewClass());
|
||||
}
|
||||
|
||||
|
|
@ -54,14 +58,6 @@ public class MustacheViewResolver extends AbstractTemplateViewResolver {
|
|||
return MustacheView.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the compiler.
|
||||
* @param compiler the compiler
|
||||
*/
|
||||
public void setCompiler(Compiler compiler) {
|
||||
this.compiler = compiler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the charset.
|
||||
* @param charset the charset
|
||||
|
|
@ -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…
Reference in New Issue