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
|
@Bean
|
||||||
@ConditionalOnMissingBean(MustacheViewResolver.class)
|
@ConditionalOnMissingBean(MustacheViewResolver.class)
|
||||||
public MustacheViewResolver mustacheViewResolver(Compiler mustacheCompiler) {
|
public MustacheViewResolver mustacheViewResolver(Compiler mustacheCompiler) {
|
||||||
MustacheViewResolver resolver = new MustacheViewResolver();
|
MustacheViewResolver resolver = new MustacheViewResolver(mustacheCompiler);
|
||||||
this.mustache.applyToViewResolver(resolver);
|
this.mustache.applyToViewResolver(resolver);
|
||||||
resolver.setCharset(this.mustache.getCharsetName());
|
resolver.setCharset(this.mustache.getCharsetName());
|
||||||
resolver.setCompiler(mustacheCompiler);
|
|
||||||
resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
|
resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
|
||||||
return resolver;
|
return resolver;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,58 +16,84 @@
|
||||||
|
|
||||||
package org.springframework.boot.autoconfigure.mustache.servlet;
|
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 java.util.Map;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import com.samskivert.mustache.Mustache;
|
||||||
import com.samskivert.mustache.Template;
|
import com.samskivert.mustache.Template;
|
||||||
|
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.web.servlet.View;
|
import org.springframework.web.servlet.View;
|
||||||
import org.springframework.web.servlet.view.AbstractTemplateView;
|
import org.springframework.web.servlet.view.AbstractTemplateView;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spring MVC {@link View} using the Mustache template engine.
|
* Spring MVC {@link View} using the Mustache template engine.
|
||||||
*
|
*
|
||||||
|
* @author Brian Clozel
|
||||||
* @author Dave Syer
|
* @author Dave Syer
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
* @since 1.2.2
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public class MustacheView extends AbstractTemplateView {
|
public class MustacheView extends AbstractTemplateView {
|
||||||
|
|
||||||
private Template template;
|
private Mustache.Compiler compiler;
|
||||||
|
|
||||||
|
private String charset;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new {@link MustacheView} instance.
|
* Set the Mustache compiler to be used by this view.
|
||||||
* @since 1.2.5
|
* <p>Typically this property is not set directly. Instead a single
|
||||||
* @see #setTemplate(Template)
|
* {@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.
|
* Set the charset used for reading Mustache template files.
|
||||||
* @param template the source template
|
* @param charset the charset to use for reading template files
|
||||||
*/
|
*/
|
||||||
public MustacheView(Template template) {
|
public void setCharset(String charset) {
|
||||||
this.template = template;
|
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
|
@Override
|
||||||
protected void renderMergedTemplateModel(Map<String, Object> model,
|
public boolean checkResource(Locale locale) throws Exception {
|
||||||
HttpServletRequest request, HttpServletResponse response) throws Exception {
|
Resource resource = getApplicationContext().getResource(this.getUrl());
|
||||||
if (this.template != null) {
|
return (resource != null && resource.exists());
|
||||||
this.template.execute(model, response.getWriter());
|
}
|
||||||
|
|
||||||
|
@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;
|
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;
|
||||||
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.ViewResolver;
|
||||||
import org.springframework.web.servlet.view.AbstractTemplateViewResolver;
|
import org.springframework.web.servlet.view.AbstractTemplateViewResolver;
|
||||||
|
import org.springframework.web.servlet.view.AbstractUrlBasedView;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spring MVC {@link ViewResolver} for Mustache.
|
* Spring MVC {@link ViewResolver} for Mustache.
|
||||||
*
|
*
|
||||||
* @author Dave Syer
|
* @author Brian Clozel
|
||||||
* @author Andy Wilkinson
|
* @since 2.0.0
|
||||||
* @author Phillip Webb
|
|
||||||
* @since 1.2.2
|
|
||||||
*/
|
*/
|
||||||
public class MustacheViewResolver extends AbstractTemplateViewResolver {
|
public class MustacheViewResolver extends AbstractTemplateViewResolver {
|
||||||
|
|
||||||
private Compiler compiler = Mustache.compiler();
|
private final Mustache.Compiler compiler;
|
||||||
|
|
||||||
private String charset;
|
private String charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@code MustacheViewResolver} backed by a default
|
||||||
|
* instance of a {@link Mustache.Compiler}.
|
||||||
|
*/
|
||||||
public MustacheViewResolver() {
|
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());
|
setViewClass(requiredViewClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,14 +58,6 @@ public class MustacheViewResolver extends AbstractTemplateViewResolver {
|
||||||
return MustacheView.class;
|
return MustacheView.class;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the compiler.
|
|
||||||
* @param compiler the compiler
|
|
||||||
*/
|
|
||||||
public void setCompiler(Compiler compiler) {
|
|
||||||
this.compiler = compiler;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the charset.
|
* Set the charset.
|
||||||
* @param charset the charset
|
* @param charset the charset
|
||||||
|
|
@ -71,57 +67,11 @@ public class MustacheViewResolver extends AbstractTemplateViewResolver {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected View loadView(String viewName, Locale locale) throws Exception {
|
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
|
||||||
Resource resource = resolveResource(viewName, locale);
|
MustacheView view = (MustacheView) super.buildView(viewName);
|
||||||
if (resource == null) {
|
view.setCompiler(this.compiler);
|
||||||
return null;
|
view.setCharset(this.charset);
|
||||||
}
|
return view;
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
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.Before;
|
||||||
import org.junit.Test;
|
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.mock.web.MockServletContext;
|
||||||
import org.springframework.web.context.support.StaticWebApplicationContext;
|
|
||||||
import org.springframework.web.servlet.View;
|
import org.springframework.web.servlet.View;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
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}.
|
* Tests for {@link MustacheViewResolver}.
|
||||||
|
|
@ -46,7 +37,9 @@ public class MustacheViewResolverTests {
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void init() {
|
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.setServletContext(new MockServletContext());
|
||||||
this.resolver.setPrefix("classpath:/mustache-templates/");
|
this.resolver.setPrefix("classpath:/mustache-templates/");
|
||||||
this.resolver.setSuffix(".html");
|
this.resolver.setSuffix(".html");
|
||||||
|
|
@ -58,32 +51,10 @@ public class MustacheViewResolverTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void resolveNullLocale() throws Exception {
|
public void resolveExisting() throws Exception {
|
||||||
assertThat(this.resolver.resolveViewName("foo", null)).isNotNull();
|
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
|
@Test
|
||||||
public void setsContentType() throws Exception {
|
public void setsContentType() throws Exception {
|
||||||
this.resolver.setContentType("application/octet-stream");
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -55,12 +55,12 @@ public class MustacheViewTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void viewResolvesHandlebars() throws Exception {
|
public void viewResolvesHandlebars() throws Exception {
|
||||||
MustacheView view = new MustacheView(
|
MustacheView view = new MustacheView();
|
||||||
Mustache.compiler().compile("Hello {{msg}}"));
|
view.setCompiler(Mustache.compiler());
|
||||||
|
view.setUrl("classpath:/mustache-templates/foo.html");
|
||||||
view.setApplicationContext(this.context);
|
view.setApplicationContext(this.context);
|
||||||
view.render(Collections.singletonMap("msg", "World"), this.request,
|
view.render(Collections.singletonMap("World", "Spring"), this.request, this.response);
|
||||||
this.response);
|
assertThat(this.response.getContentAsString()).isEqualTo("Hello Spring");
|
||||||
assertThat(this.response.getContentAsString()).isEqualTo("Hello World");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -120,12 +120,12 @@ public class MustacheWebIntegrationTests {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public MustacheViewResolver viewResolver() {
|
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.setPrefix("classpath:/mustache-templates/");
|
||||||
resolver.setSuffix(".html");
|
resolver.setSuffix(".html");
|
||||||
resolver.setCompiler(
|
|
||||||
Mustache.compiler().withLoader(new MustacheResourceTemplateLoader(
|
|
||||||
"classpath:/mustache-templates/", ".html")));
|
|
||||||
return resolver;
|
return resolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue