Commit 1ad318d8 authored by Phillip Webb's avatar Phillip Webb

Refine Mustache support

Refine Mustache support to provide a cleaner separation between the
reactive and servlet implementations. The views have now moved to the
`spring-boot` project and the auto-configuration has been split into
two distinct `@Imports` to save needing full package declarations.

See gh-8941
parent 06558675
...@@ -28,10 +28,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat ...@@ -28,10 +28,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat
import org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration; import org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration;
import org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration; import org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration;
import org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration; import org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration;
import org.springframework.boot.autoconfigure.mustache.servlet.MustacheViewResolver;
import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration; import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.view.MustacheViewResolver;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.mobile.device.view.LiteDeviceDelegatingViewResolver; import org.springframework.mobile.device.view.LiteDeviceDelegatingViewResolver;
......
...@@ -20,7 +20,6 @@ import javax.annotation.PostConstruct; ...@@ -20,7 +20,6 @@ import javax.annotation.PostConstruct;
import com.samskivert.mustache.Mustache; import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Mustache.Collector; import com.samskivert.mustache.Mustache.Collector;
import com.samskivert.mustache.Mustache.Compiler;
import com.samskivert.mustache.Mustache.TemplateLoader; import com.samskivert.mustache.Mustache.TemplateLoader;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
...@@ -28,15 +27,12 @@ import org.apache.commons.logging.LogFactory; ...@@ -28,15 +27,12 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.mustache.servlet.MustacheViewResolver;
import org.springframework.boot.autoconfigure.template.TemplateLocation; import org.springframework.boot.autoconfigure.template.TemplateLocation;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered; import org.springframework.context.annotation.Import;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
/** /**
...@@ -49,6 +45,7 @@ import org.springframework.core.env.Environment; ...@@ -49,6 +45,7 @@ import org.springframework.core.env.Environment;
@Configuration @Configuration
@ConditionalOnClass(Mustache.class) @ConditionalOnClass(Mustache.class)
@EnableConfigurationProperties(MustacheProperties.class) @EnableConfigurationProperties(MustacheProperties.class)
@Import({ MustacheServletWebConfiguration.class, MustacheReactiveWebConfiguration.class })
public class MustacheAutoConfiguration { public class MustacheAutoConfiguration {
private static final Log logger = LogFactory.getLog(MustacheAutoConfiguration.class); private static final Log logger = LogFactory.getLog(MustacheAutoConfiguration.class);
...@@ -101,56 +98,4 @@ public class MustacheAutoConfiguration { ...@@ -101,56 +98,4 @@ public class MustacheAutoConfiguration {
return loader; return loader;
} }
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
protected static class MustacheWebConfiguration {
private final MustacheProperties mustache;
protected MustacheWebConfiguration(MustacheProperties mustache) {
this.mustache = mustache;
}
@Bean
@ConditionalOnMissingBean(MustacheViewResolver.class)
public MustacheViewResolver mustacheViewResolver(Compiler mustacheCompiler) {
MustacheViewResolver resolver = new MustacheViewResolver(mustacheCompiler);
this.mustache.applyToViewResolver(resolver);
resolver.setCharset(this.mustache.getCharsetName());
resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
return resolver;
}
}
@Configuration
@ConditionalOnWebApplication(type = Type.REACTIVE)
protected static class MustacheReactiveWebConfiguration {
private final MustacheProperties mustache;
protected MustacheReactiveWebConfiguration(MustacheProperties mustache) {
this.mustache = mustache;
}
@Bean
@ConditionalOnMissingBean(org.springframework.boot.autoconfigure
.mustache.reactive.MustacheViewResolver.class)
public org.springframework.boot.autoconfigure
.mustache.reactive.MustacheViewResolver mustacheViewResolver(Compiler mustacheCompiler) {
org.springframework.boot.autoconfigure
.mustache.reactive.MustacheViewResolver resolver
= new org.springframework.boot.autoconfigure
.mustache.reactive.MustacheViewResolver(mustacheCompiler);
resolver.setPrefix(this.mustache.getPrefix());
resolver.setSuffix(this.mustache.getSuffix());
resolver.setViewNames(this.mustache.getViewNames());
resolver.setRequestContextAttribute(this.mustache.getRequestContextAttribute());
resolver.setCharset(this.mustache.getCharsetName());
resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
return resolver;
}
}
} }
/*
* 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.
* 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.boot.autoconfigure.mustache;
import com.samskivert.mustache.Mustache.Compiler;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.web.reactive.result.view.MustacheViewResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
@Configuration
@ConditionalOnWebApplication(type = Type.REACTIVE)
class MustacheReactiveWebConfiguration {
private final MustacheProperties mustache;
protected MustacheReactiveWebConfiguration(MustacheProperties mustache) {
this.mustache = mustache;
}
@Bean
@ConditionalOnMissingBean(MustacheViewResolver.class)
public MustacheViewResolver mustacheViewResolver(Compiler mustacheCompiler) {
MustacheViewResolver resolver = new MustacheViewResolver(mustacheCompiler);
resolver.setPrefix(this.mustache.getPrefix());
resolver.setSuffix(this.mustache.getSuffix());
resolver.setViewNames(this.mustache.getViewNames());
resolver.setRequestContextAttribute(this.mustache.getRequestContextAttribute());
resolver.setCharset(this.mustache.getCharsetName());
resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
return resolver;
}
}
...@@ -20,6 +20,7 @@ import java.io.InputStreamReader; ...@@ -20,6 +20,7 @@ import java.io.InputStreamReader;
import java.io.Reader; import java.io.Reader;
import com.samskivert.mustache.Mustache; import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Mustache.Compiler;
import com.samskivert.mustache.Mustache.TemplateLoader; import com.samskivert.mustache.Mustache.TemplateLoader;
import org.springframework.context.ResourceLoaderAware; import org.springframework.context.ResourceLoaderAware;
...@@ -30,8 +31,8 @@ import org.springframework.core.io.ResourceLoader; ...@@ -30,8 +31,8 @@ import org.springframework.core.io.ResourceLoader;
/** /**
* Mustache TemplateLoader implementation that uses a prefix, suffix and the Spring * Mustache TemplateLoader implementation that uses a prefix, suffix and the Spring
* Resource abstraction to load a template from a file, classpath, URL etc. A * Resource abstraction to load a template from a file, classpath, URL etc. A
* TemplateLoader is needed in the Compiler when you want to render partials (i.e. * {@link TemplateLoader} is needed in the {@link Compiler} when you want to render
* tiles-like features). * partials (i.e. tiles-like features).
* *
* @author Dave Syer * @author Dave Syer
* @since 1.2.2 * @since 1.2.2
......
/*
* 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.
* 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.boot.autoconfigure.mustache;
import com.samskivert.mustache.Mustache.Compiler;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.web.servlet.view.MustacheViewResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
class MustacheServletWebConfiguration {
private final MustacheProperties mustache;
protected MustacheServletWebConfiguration(MustacheProperties mustache) {
this.mustache = mustache;
}
@Bean
@ConditionalOnMissingBean(MustacheViewResolver.class)
public MustacheViewResolver mustacheViewResolver(Compiler mustacheCompiler) {
MustacheViewResolver resolver = new MustacheViewResolver(mustacheCompiler);
this.mustache.applyToViewResolver(resolver);
resolver.setCharset(this.mustache.getCharsetName());
resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
return resolver;
}
}
...@@ -34,10 +34,10 @@ import org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfigura ...@@ -34,10 +34,10 @@ import org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfigura
import org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration; import org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration;
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration; import org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration;
import org.springframework.boot.autoconfigure.mustache.servlet.MustacheViewResolver;
import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration; import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.boot.test.util.EnvironmentTestUtils;
import org.springframework.boot.web.servlet.view.MustacheViewResolver;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.mobile.device.view.LiteDeviceDelegatingViewResolver; import org.springframework.mobile.device.view.LiteDeviceDelegatingViewResolver;
import org.springframework.mock.web.MockServletContext; import org.springframework.mock.web.MockServletContext;
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.autoconfigure.mustache.reactive; package org.springframework.boot.autoconfigure.mustache;
import java.util.Date; import java.util.Date;
...@@ -26,13 +26,13 @@ import org.springframework.beans.factory.annotation.Autowired; ...@@ -26,13 +26,13 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType; import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration;
import org.springframework.boot.autoconfigure.mustache.MustacheResourceTemplateLoader;
import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration; import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration;
import org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerAutoConfiguration; import org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerAutoConfiguration;
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration; import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.web.reactive.result.view.MustacheView;
import org.springframework.boot.web.reactive.result.view.MustacheViewResolver;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
...@@ -51,34 +51,30 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -51,34 +51,30 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Brian Clozel * @author Brian Clozel
*/ */
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = "spring.main.web-application-type=reactive")
properties = "spring.main.web-application-type=reactive") public class MustacheAutoConfigurationReactiveIntegrationTests {
public class MustacheWebIntegrationTests {
@Autowired @Autowired
private WebTestClient client; private WebTestClient client;
@Test @Test
public void testHomePage() throws Exception { public void testHomePage() throws Exception {
String result = (String) this.client.get().uri("/").exchange() String result = this.client.get().uri("/").exchange().expectStatus().isOk()
.expectStatus().isOk()
.expectBody(String.class).returnResult().getResponseBody(); .expectBody(String.class).returnResult().getResponseBody();
assertThat(result).contains("Hello App").contains("Hello World"); assertThat(result).contains("Hello App").contains("Hello World");
} }
@Test @Test
public void testPartialPage() throws Exception { public void testPartialPage() throws Exception {
String result = (String) this.client.get().uri("/partial").exchange() String result = this.client.get().uri("/partial").exchange().expectStatus().isOk()
.expectStatus().isOk()
.expectBody(String.class).returnResult().getResponseBody(); .expectBody(String.class).returnResult().getResponseBody();
assertThat(result).contains("Hello App").contains("Hello World"); assertThat(result).contains("Hello App").contains("Hello World");
} }
@Configuration @Configuration
@Import({ReactiveWebServerAutoConfiguration.class, @Import({ ReactiveWebServerAutoConfiguration.class, WebFluxAutoConfiguration.class,
WebFluxAutoConfiguration.class,
HttpHandlerAutoConfiguration.class, HttpHandlerAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class}) PropertyPlaceholderAutoConfiguration.class })
@Controller @Controller
public static class Application { public static class Application {
...@@ -101,7 +97,8 @@ public class MustacheWebIntegrationTests { ...@@ -101,7 +97,8 @@ public class MustacheWebIntegrationTests {
@Bean @Bean
public MustacheViewResolver viewResolver() { public MustacheViewResolver viewResolver() {
Mustache.Compiler compiler = Mustache.compiler().withLoader( Mustache.Compiler compiler = Mustache.compiler().withLoader(
new MustacheResourceTemplateLoader("classpath:/mustache-templates/", ".html")); new MustacheResourceTemplateLoader("classpath:/mustache-templates/",
".html"));
MustacheViewResolver resolver = new MustacheViewResolver(compiler); MustacheViewResolver resolver = new MustacheViewResolver(compiler);
resolver.setPrefix("classpath:/mustache-templates/"); resolver.setPrefix("classpath:/mustache-templates/");
resolver.setSuffix(".html"); resolver.setSuffix(".html");
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.autoconfigure.mustache.servlet; package org.springframework.boot.autoconfigure.mustache;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
...@@ -34,14 +34,14 @@ import org.junit.runner.RunWith; ...@@ -34,14 +34,14 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration;
import org.springframework.boot.autoconfigure.mustache.MustacheResourceTemplateLoader;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext; import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;
import org.springframework.boot.web.servlet.view.MustacheView;
import org.springframework.boot.web.servlet.view.MustacheViewResolver;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
...@@ -61,7 +61,7 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -61,7 +61,7 @@ import static org.assertj.core.api.Assertions.assertThat;
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@DirtiesContext @DirtiesContext
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class MustacheWebIntegrationTests { public class MustacheAutoConfigurationServletIntegrationTests {
@Autowired @Autowired
private ServletWebServerApplicationContext context; private ServletWebServerApplicationContext context;
...@@ -79,8 +79,7 @@ public class MustacheWebIntegrationTests { ...@@ -79,8 +79,7 @@ public class MustacheWebIntegrationTests {
Template tmpl = Mustache.compiler().compile(source); Template tmpl = Mustache.compiler().compile(source);
Map<String, String> context = new HashMap<>(); Map<String, String> context = new HashMap<>();
context.put("arg", "world"); context.put("arg", "world");
assertThat(tmpl.execute(context)).isEqualTo("Hello world!"); // returns "Hello assertThat(tmpl.execute(context)).isEqualTo("Hello world!");
// world!"
} }
@Test @Test
...@@ -120,8 +119,8 @@ public class MustacheWebIntegrationTests { ...@@ -120,8 +119,8 @@ public class MustacheWebIntegrationTests {
@Bean @Bean
public MustacheViewResolver viewResolver() { public MustacheViewResolver viewResolver() {
Mustache.Compiler compiler = Mustache.compiler() Mustache.Compiler compiler = Mustache.compiler().withLoader(
.withLoader(new MustacheResourceTemplateLoader("classpath:/mustache-templates/", new MustacheResourceTemplateLoader("classpath:/mustache-templates/",
".html")); ".html"));
MustacheViewResolver resolver = new MustacheViewResolver(compiler); MustacheViewResolver resolver = new MustacheViewResolver(compiler);
resolver.setPrefix("classpath:/mustache-templates/"); resolver.setPrefix("classpath:/mustache-templates/");
......
...@@ -19,9 +19,9 @@ package org.springframework.boot.autoconfigure.mustache; ...@@ -19,9 +19,9 @@ package org.springframework.boot.autoconfigure.mustache;
import com.samskivert.mustache.Mustache; import com.samskivert.mustache.Mustache;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.autoconfigure.mustache.servlet.MustacheViewResolver;
import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.boot.test.util.EnvironmentTestUtils;
import org.springframework.boot.web.reactive.context.GenericReactiveWebApplicationContext; import org.springframework.boot.web.reactive.context.GenericReactiveWebApplicationContext;
import org.springframework.boot.web.servlet.view.MustacheViewResolver;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
...@@ -64,7 +64,7 @@ public class MustacheAutoConfigurationTests { ...@@ -64,7 +64,7 @@ public class MustacheAutoConfigurationTests {
assertThat(this.reactiveWebContext.getBeansOfType(MustacheResourceTemplateLoader.class)).hasSize(1); assertThat(this.reactiveWebContext.getBeansOfType(MustacheResourceTemplateLoader.class)).hasSize(1);
assertThat(this.reactiveWebContext.getBeansOfType(MustacheViewResolver.class)).isEmpty(); assertThat(this.reactiveWebContext.getBeansOfType(MustacheViewResolver.class)).isEmpty();
assertThat(this.reactiveWebContext assertThat(this.reactiveWebContext
.getBeansOfType(org.springframework.boot.autoconfigure.mustache.reactive.MustacheViewResolver.class) .getBeansOfType(org.springframework.boot.web.reactive.result.view.MustacheViewResolver.class)
).hasSize(1); ).hasSize(1);
} }
...@@ -76,7 +76,7 @@ public class MustacheAutoConfigurationTests { ...@@ -76,7 +76,7 @@ public class MustacheAutoConfigurationTests {
assertThat(this.reactiveWebContext.getBeansOfType(MustacheResourceTemplateLoader.class)).hasSize(1); assertThat(this.reactiveWebContext.getBeansOfType(MustacheResourceTemplateLoader.class)).hasSize(1);
assertThat(this.reactiveWebContext.getBeansOfType(MustacheViewResolver.class)).isEmpty(); assertThat(this.reactiveWebContext.getBeansOfType(MustacheViewResolver.class)).isEmpty();
assertThat(this.reactiveWebContext assertThat(this.reactiveWebContext
.getBeansOfType(org.springframework.boot.autoconfigure.mustache.reactive.MustacheViewResolver.class) .getBeansOfType(org.springframework.boot.web.reactive.result.view.MustacheViewResolver.class)
).hasSize(1); ).hasSize(1);
assertThat(this.reactiveWebContext.getBean(Mustache.Compiler.class).standardsMode).isTrue(); assertThat(this.reactiveWebContext.getBean(Mustache.Compiler.class).standardsMode).isTrue();
} }
......
...@@ -50,6 +50,9 @@ ...@@ -50,6 +50,9 @@
<allow pkg="org.springframework.boot.web.servlet" /> <allow pkg="org.springframework.boot.web.servlet" />
<allow pkg="org.springframework.boot.web.server" /> <allow pkg="org.springframework.boot.web.server" />
</subpackage> </subpackage>
<subpackage name="view">
<allow pkg="org.springframework.web.servlet" />
</subpackage>
</subpackage> </subpackage>
<!-- Reactive --> <!-- Reactive -->
...@@ -63,6 +66,11 @@ ...@@ -63,6 +66,11 @@
<allow pkg="org.springframework.boot.web.server" /> <allow pkg="org.springframework.boot.web.server" />
<disallow pkg="org.springframework.context" /> <disallow pkg="org.springframework.context" />
</subpackage> </subpackage>
<subpackage name="result">
<subpackage name="view">
<allow pkg="org.springframework.boot.web.reactive.result.view" />
</subpackage>
</subpackage>
</subpackage> </subpackage>
<!-- Embedded Servers --> <!-- Embedded Servers -->
......
...@@ -54,6 +54,11 @@ ...@@ -54,6 +54,11 @@
<artifactId>jackson-databind</artifactId> <artifactId>jackson-databind</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>com.samskivert</groupId>
<artifactId>jmustache</artifactId>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>com.sendgrid</groupId> <groupId>com.sendgrid</groupId>
<artifactId>sendgrid-java</artifactId> <artifactId>sendgrid-java</artifactId>
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.autoconfigure.mustache.reactive; package org.springframework.boot.web.reactive.result.view;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
...@@ -26,7 +26,7 @@ import java.util.Locale; ...@@ -26,7 +26,7 @@ import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import com.samskivert.mustache.Mustache; import com.samskivert.mustache.Mustache.Compiler;
import com.samskivert.mustache.Template; import com.samskivert.mustache.Template;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
...@@ -46,18 +46,17 @@ import org.springframework.web.server.ServerWebExchange; ...@@ -46,18 +46,17 @@ import org.springframework.web.server.ServerWebExchange;
*/ */
public class MustacheView extends AbstractUrlBasedView { public class MustacheView extends AbstractUrlBasedView {
private Mustache.Compiler compiler; private Compiler compiler;
private String charset; private String charset;
/** /**
* Set the JMustache compiler to be used by this view. * Set the JMustache compiler to be used by this view. Typically this property is not
* <p>Typically this property is not set directly. Instead a single * set directly. Instead a single {@link Compiler} is expected in the Spring
* {@link Mustache.Compiler} is expected in the Spring application context * application context which is used to compile Mustache templates.
* which is used to compile Mustache templates.
* @param compiler the Mustache compiler * @param compiler the Mustache compiler
*/ */
public void setCompiler(Mustache.Compiler compiler) { public void setCompiler(Compiler compiler) {
this.compiler = compiler; this.compiler = compiler;
} }
...@@ -74,27 +73,20 @@ public class MustacheView extends AbstractUrlBasedView { ...@@ -74,27 +73,20 @@ public class MustacheView extends AbstractUrlBasedView {
return resolveResource() != null; return resolveResource() != null;
} }
private Resource resolveResource() {
Resource resource = getApplicationContext().getResource(getUrl());
if (resource == null || !resource.exists()) {
return null;
}
return resource;
}
@Override @Override
protected Mono<Void> renderInternal(Map<String, Object> model, protected Mono<Void> renderInternal(Map<String, Object> model, MediaType contentType,
MediaType contentType, ServerWebExchange exchange) { ServerWebExchange exchange) {
Resource resource = resolveResource(); Resource resource = resolveResource();
if (resource == null) { if (resource == null) {
return Mono.error(new IllegalStateException("Could not find Mustache template with URL [" return Mono.error(new IllegalStateException(
+ getUrl() + "]")); "Could not find Mustache template with URL [" + getUrl() + "]"));
} }
DataBuffer dataBuffer = exchange.getResponse().bufferFactory().allocateBuffer(); DataBuffer dataBuffer = exchange.getResponse().bufferFactory().allocateBuffer();
try (Reader reader = getReader(resource)) { try (Reader reader = getReader(resource)) {
Template template = this.compiler.compile(reader); Template template = this.compiler.compile(reader);
Charset charset = getCharset(contentType).orElse(getDefaultCharset()); Charset charset = getCharset(contentType).orElse(getDefaultCharset());
try (Writer writer = new OutputStreamWriter(dataBuffer.asOutputStream(), charset)) { try (Writer writer = new OutputStreamWriter(dataBuffer.asOutputStream(),
charset)) {
template.execute(model, writer); template.execute(model, writer);
writer.flush(); writer.flush();
} }
...@@ -105,6 +97,14 @@ public class MustacheView extends AbstractUrlBasedView { ...@@ -105,6 +97,14 @@ public class MustacheView extends AbstractUrlBasedView {
return exchange.getResponse().writeWith(Flux.just(dataBuffer)); return exchange.getResponse().writeWith(Flux.just(dataBuffer));
} }
private Resource resolveResource() {
Resource resource = getApplicationContext().getResource(getUrl());
if (resource == null || !resource.exists()) {
return null;
}
return resource;
}
private Reader getReader(Resource resource) throws IOException { private Reader getReader(Resource resource) throws IOException {
if (this.charset != null) { if (this.charset != null) {
return new InputStreamReader(resource.getInputStream(), this.charset); return new InputStreamReader(resource.getInputStream(), this.charset);
...@@ -113,6 +113,7 @@ public class MustacheView extends AbstractUrlBasedView { ...@@ -113,6 +113,7 @@ public class MustacheView extends AbstractUrlBasedView {
} }
private Optional<Charset> getCharset(MediaType mediaType) { private Optional<Charset> getCharset(MediaType mediaType) {
return (mediaType != null ? Optional.ofNullable(mediaType.getCharset()) : Optional.empty()); return Optional.ofNullable(mediaType != null ? mediaType.getCharset() : null);
} }
} }
...@@ -14,9 +14,10 @@ ...@@ -14,9 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.autoconfigure.mustache.reactive; package org.springframework.boot.web.reactive.result.view;
import com.samskivert.mustache.Mustache; import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Mustache.Compiler;
import org.springframework.web.reactive.result.view.AbstractUrlBasedView; import org.springframework.web.reactive.result.view.AbstractUrlBasedView;
import org.springframework.web.reactive.result.view.UrlBasedViewResolver; import org.springframework.web.reactive.result.view.UrlBasedViewResolver;
...@@ -30,13 +31,13 @@ import org.springframework.web.reactive.result.view.ViewResolver; ...@@ -30,13 +31,13 @@ import org.springframework.web.reactive.result.view.ViewResolver;
*/ */
public class MustacheViewResolver extends UrlBasedViewResolver { public class MustacheViewResolver extends UrlBasedViewResolver {
private final Mustache.Compiler compiler; private final Compiler compiler;
private String charset; private String charset;
/** /**
* Create a {@code MustacheViewResolver} backed by a default * Create a {@code MustacheViewResolver} backed by a default instance of a
* instance of a {@link Mustache.Compiler}. * {@link Compiler}.
*/ */
public MustacheViewResolver() { public MustacheViewResolver() {
this.compiler = Mustache.compiler(); this.compiler = Mustache.compiler();
...@@ -44,11 +45,11 @@ public class MustacheViewResolver extends UrlBasedViewResolver { ...@@ -44,11 +45,11 @@ public class MustacheViewResolver extends UrlBasedViewResolver {
} }
/** /**
* Create a {@code MustacheViewResolver} backed by a custom * Create a {@code MustacheViewResolver} backed by a custom instance of a
* instance of a {@link Mustache.Compiler}. * {@link Compiler}.
* @param compiler the Mustache compiler used to compile templates * @param compiler the Mustache compiler used to compile templates
*/ */
public MustacheViewResolver(Mustache.Compiler compiler) { public MustacheViewResolver(Compiler compiler) {
this.compiler = compiler; this.compiler = compiler;
setViewClass(requiredViewClass()); setViewClass(requiredViewClass());
} }
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
/** /**
* Auto-configuration for Mustache with Spring MVC. * Additional {@link org.springframework.web.reactive.result.view.View Views} for use with
* WebFlux.
*/ */
package org.springframework.boot.autoconfigure.mustache.servlet; package org.springframework.boot.web.reactive.result.view;
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.autoconfigure.mustache.servlet; package org.springframework.boot.web.servlet.view;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
...@@ -25,7 +25,7 @@ import java.util.Map; ...@@ -25,7 +25,7 @@ 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.Mustache.Compiler;
import com.samskivert.mustache.Template; import com.samskivert.mustache.Template;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
...@@ -42,18 +42,19 @@ import org.springframework.web.servlet.view.AbstractTemplateView; ...@@ -42,18 +42,19 @@ import org.springframework.web.servlet.view.AbstractTemplateView;
*/ */
public class MustacheView extends AbstractTemplateView { public class MustacheView extends AbstractTemplateView {
private Mustache.Compiler compiler; private Compiler compiler;
private String charset; private String charset;
/** /**
* Set the Mustache compiler to be used by this view. * Set the Mustache compiler to be used by this view.
* <p>Typically this property is not set directly. Instead a single * <p>
* {@link Mustache.Compiler} is expected in the Spring application context * Typically this property is not set directly. Instead a single {@link Compiler} is
* which is used to compile Mustache templates. * expected in the Spring application context which is used to compile Mustache
* templates.
* @param compiler the Mustache compiler * @param compiler the Mustache compiler
*/ */
public void setCompiler(Mustache.Compiler compiler) { public void setCompiler(Compiler compiler) {
this.compiler = compiler; this.compiler = compiler;
} }
...@@ -72,9 +73,10 @@ public class MustacheView extends AbstractTemplateView { ...@@ -72,9 +73,10 @@ public class MustacheView extends AbstractTemplateView {
} }
@Override @Override
protected void renderMergedTemplateModel(Map<String, Object> model, HttpServletRequest request, protected void renderMergedTemplateModel(Map<String, Object> model,
HttpServletResponse response) throws Exception { HttpServletRequest request, HttpServletResponse response) throws Exception {
Template template = createTemplate(getApplicationContext().getResource(this.getUrl())); Template template = createTemplate(
getApplicationContext().getResource(this.getUrl()));
if (template != null) { if (template != null) {
template.execute(model, response.getWriter()); template.execute(model, response.getWriter());
} }
...@@ -96,4 +98,5 @@ public class MustacheView extends AbstractTemplateView { ...@@ -96,4 +98,5 @@ public class MustacheView extends AbstractTemplateView {
} }
return new InputStreamReader(resource.getInputStream()); return new InputStreamReader(resource.getInputStream());
} }
} }
...@@ -14,9 +14,10 @@ ...@@ -14,9 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.autoconfigure.mustache.servlet; package org.springframework.boot.web.servlet.view;
import com.samskivert.mustache.Mustache; import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Mustache.Compiler;
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;
...@@ -35,8 +36,8 @@ public class MustacheViewResolver extends AbstractTemplateViewResolver { ...@@ -35,8 +36,8 @@ public class MustacheViewResolver extends AbstractTemplateViewResolver {
private String charset; private String charset;
/** /**
* Create a {@code MustacheViewResolver} backed by a default * Create a {@code MustacheViewResolver} backed by a default instance of a
* instance of a {@link Mustache.Compiler}. * {@link Compiler}.
*/ */
public MustacheViewResolver() { public MustacheViewResolver() {
this.compiler = Mustache.compiler(); this.compiler = Mustache.compiler();
...@@ -44,11 +45,11 @@ public class MustacheViewResolver extends AbstractTemplateViewResolver { ...@@ -44,11 +45,11 @@ public class MustacheViewResolver extends AbstractTemplateViewResolver {
} }
/** /**
* Create a {@code MustacheViewResolver} backed by a custom * Create a {@code MustacheViewResolver} backed by a custom instance of a
* instance of a {@link Mustache.Compiler}. * {@link Compiler}.
* @param compiler the Mustache compiler used to compile templates * @param compiler the Mustache compiler used to compile templates
*/ */
public MustacheViewResolver(Mustache.Compiler compiler) { public MustacheViewResolver(Compiler compiler) {
this.compiler = compiler; this.compiler = compiler;
setViewClass(requiredViewClass()); setViewClass(requiredViewClass());
} }
......
...@@ -15,6 +15,6 @@ ...@@ -15,6 +15,6 @@
*/ */
/** /**
* Auto-configuration for Mustache with Spring WebFlux. * Additional {@link org.springframework.web.servlet.View Views} for use with Web MVC.
*/ */
package org.springframework.boot.autoconfigure.mustache.reactive; package org.springframework.boot.web.servlet.view;
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.autoconfigure.mustache.reactive; package org.springframework.boot.web.reactive.result.view;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
...@@ -24,12 +24,15 @@ import org.springframework.context.support.GenericApplicationContext; ...@@ -24,12 +24,15 @@ import org.springframework.context.support.GenericApplicationContext;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Tests for {@link org.springframework.boot.autoconfigure.mustache.reactive.MustacheViewResolver}. * Tests for {@link MustacheViewResolver}.
* *
* @author Brian Clozel * @author Brian Clozel
*/ */
public class MustacheViewResolverTests { public class MustacheViewResolverTests {
private final String prefix = "classpath:/"
+ getClass().getPackage().getName().replace(".", "/") + "/";
private MustacheViewResolver resolver = new MustacheViewResolver(); private MustacheViewResolver resolver = new MustacheViewResolver();
@Before @Before
...@@ -37,7 +40,7 @@ public class MustacheViewResolverTests { ...@@ -37,7 +40,7 @@ public class MustacheViewResolverTests {
GenericApplicationContext applicationContext = new GenericApplicationContext(); GenericApplicationContext applicationContext = new GenericApplicationContext();
applicationContext.refresh(); applicationContext.refresh();
this.resolver.setApplicationContext(applicationContext); this.resolver.setApplicationContext(applicationContext);
this.resolver.setPrefix("classpath:/mustache-templates/"); this.resolver.setPrefix(this.prefix);
this.resolver.setSuffix(".html"); this.resolver.setSuffix(".html");
} }
...@@ -48,7 +51,7 @@ public class MustacheViewResolverTests { ...@@ -48,7 +51,7 @@ public class MustacheViewResolverTests {
@Test @Test
public void resolveExisting() throws Exception { public void resolveExisting() throws Exception {
assertThat(this.resolver.resolveViewName("foo", null).block()).isNotNull(); assertThat(this.resolver.resolveViewName("template", null).block()).isNotNull();
} }
} }
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.autoconfigure.mustache.reactive; package org.springframework.boot.web.reactive.result.view;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Collections; import java.util.Collections;
...@@ -37,6 +37,9 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -37,6 +37,9 @@ import static org.assertj.core.api.Assertions.assertThat;
*/ */
public class MustacheViewTests { public class MustacheViewTests {
private final String templateUrl = "classpath:/"
+ getClass().getPackage().getName().replace(".", "/") + "/template.html";
private GenericApplicationContext context = new GenericApplicationContext(); private GenericApplicationContext context = new GenericApplicationContext();
private MockServerWebExchange exchange; private MockServerWebExchange exchange;
...@@ -51,11 +54,13 @@ public class MustacheViewTests { ...@@ -51,11 +54,13 @@ public class MustacheViewTests {
this.exchange = MockServerHttpRequest.get("/test").toExchange(); this.exchange = MockServerHttpRequest.get("/test").toExchange();
MustacheView view = new MustacheView(); MustacheView view = new MustacheView();
view.setCompiler(Mustache.compiler()); view.setCompiler(Mustache.compiler());
view.setUrl("classpath:/mustache-templates/foo.html"); view.setUrl(this.templateUrl);
view.setCharset(StandardCharsets.UTF_8.displayName()); view.setCharset(StandardCharsets.UTF_8.displayName());
view.setApplicationContext(this.context); view.setApplicationContext(this.context);
view.render(Collections.singletonMap("World", "Spring"), MediaType.TEXT_HTML, this.exchange).block(); view.render(Collections.singletonMap("World", "Spring"), MediaType.TEXT_HTML,
assertThat(this.exchange.getResponse().getBodyAsString().block()).isEqualTo("Hello Spring"); this.exchange).block();
assertThat(this.exchange.getResponse().getBodyAsString().block())
.isEqualTo("Hello Spring");
} }
} }
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.autoconfigure.mustache.servlet; package org.springframework.boot.web.servlet.view;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
...@@ -33,6 +33,9 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -33,6 +33,9 @@ import static org.assertj.core.api.Assertions.assertThat;
*/ */
public class MustacheViewResolverTests { public class MustacheViewResolverTests {
private final String prefix = "classpath:/"
+ getClass().getPackage().getName().replace(".", "/") + "/";
private MustacheViewResolver resolver = new MustacheViewResolver(); private MustacheViewResolver resolver = new MustacheViewResolver();
@Before @Before
...@@ -41,7 +44,7 @@ public class MustacheViewResolverTests { ...@@ -41,7 +44,7 @@ public class MustacheViewResolverTests {
applicationContext.refresh(); applicationContext.refresh();
this.resolver.setApplicationContext(applicationContext); this.resolver.setApplicationContext(applicationContext);
this.resolver.setServletContext(new MockServletContext()); this.resolver.setServletContext(new MockServletContext());
this.resolver.setPrefix("classpath:/mustache-templates/"); this.resolver.setPrefix(this.prefix);
this.resolver.setSuffix(".html"); this.resolver.setSuffix(".html");
} }
...@@ -52,13 +55,13 @@ public class MustacheViewResolverTests { ...@@ -52,13 +55,13 @@ public class MustacheViewResolverTests {
@Test @Test
public void resolveExisting() throws Exception { public void resolveExisting() throws Exception {
assertThat(this.resolver.resolveViewName("foo", null)).isNotNull(); assertThat(this.resolver.resolveViewName("template", null)).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");
View view = this.resolver.resolveViewName("foo", null); View view = this.resolver.resolveViewName("template", null);
assertThat(view.getContentType()).isEqualTo("application/octet-stream"); assertThat(view.getContentType()).isEqualTo("application/octet-stream");
} }
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.autoconfigure.mustache.servlet; package org.springframework.boot.web.servlet.view;
import java.util.Collections; import java.util.Collections;
...@@ -37,6 +37,9 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -37,6 +37,9 @@ import static org.assertj.core.api.Assertions.assertThat;
*/ */
public class MustacheViewTests { public class MustacheViewTests {
private final String templateUrl = "classpath:/"
+ getClass().getPackage().getName().replace(".", "/") + "/template.html";
private MockHttpServletRequest request = new MockHttpServletRequest(); private MockHttpServletRequest request = new MockHttpServletRequest();
private MockHttpServletResponse response = new MockHttpServletResponse(); private MockHttpServletResponse response = new MockHttpServletResponse();
...@@ -57,9 +60,10 @@ public class MustacheViewTests { ...@@ -57,9 +60,10 @@ public class MustacheViewTests {
public void viewResolvesHandlebars() throws Exception { public void viewResolvesHandlebars() throws Exception {
MustacheView view = new MustacheView(); MustacheView view = new MustacheView();
view.setCompiler(Mustache.compiler()); view.setCompiler(Mustache.compiler());
view.setUrl("classpath:/mustache-templates/foo.html"); view.setUrl(this.templateUrl);
view.setApplicationContext(this.context); view.setApplicationContext(this.context);
view.render(Collections.singletonMap("World", "Spring"), this.request, this.response); view.render(Collections.singletonMap("World", "Spring"), this.request,
this.response);
assertThat(this.response.getContentAsString()).isEqualTo("Hello Spring"); assertThat(this.response.getContentAsString()).isEqualTo("Hello Spring");
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment