Commit 7e77e648 authored by Brian Clozel's avatar Brian Clozel

Add Mustache support for Spring WebFlux apps

This commit moves the existing Spring MVC Mustache support to its own
`servlet` package and adds a new one under `reactive` for the WebFlux
web applications.

New `MustacheView` and `MustacheViewResolver` types resolve and render
Mustache views for WebFlux applications.

Since this templating engine is now supported by two flavors of Spring
web apps, the `spring-boot-starter-mustache` does not depend anymore on
the `spring-boot-starter-web` one: it's up to the developer to add the
relevant starter `web` or `webflux` to their application.

Fixes gh-8648
parent c2e5fd03
...@@ -28,7 +28,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat ...@@ -28,7 +28,7 @@ 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.web.MustacheViewResolver; 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;
......
...@@ -30,7 +30,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; ...@@ -30,7 +30,7 @@ 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;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.mustache.web.MustacheViewResolver; 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;
...@@ -43,6 +43,7 @@ import org.springframework.core.env.Environment; ...@@ -43,6 +43,7 @@ import org.springframework.core.env.Environment;
* {@link EnableAutoConfiguration Auto-configuration} for Mustache. * {@link EnableAutoConfiguration Auto-configuration} for Mustache.
* *
* @author Dave Syer * @author Dave Syer
* @author Brian Clozel
* @since 1.2.2 * @since 1.2.2
*/ */
@Configuration @Configuration
...@@ -123,4 +124,34 @@ public class MustacheAutoConfiguration { ...@@ -123,4 +124,34 @@ public class MustacheAutoConfiguration {
} }
@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.reactive;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Template;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.io.Resource;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.result.view.AbstractUrlBasedView;
import org.springframework.web.reactive.result.view.View;
import org.springframework.web.server.ServerWebExchange;
/**
* Spring WebFlux {@link View} using the Mustache template engine.
*
* @author Brian Clozel
* @since 2.0.0
*/
public class MustacheView extends AbstractUrlBasedView {
private Mustache.Compiler compiler;
private String charset;
/**
* Set the JMustache 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 void setCompiler(Mustache.Compiler compiler) {
this.compiler = compiler;
}
/**
* Set the charset used for reading Mustache template files.
* @param charset the charset to use for reading template files
*/
public void setCharset(String charset) {
this.charset = charset;
}
@Override
public boolean checkResourceExists(Locale locale) throws Exception {
return resolveResource() != null;
}
private Resource resolveResource() {
Resource resource = getApplicationContext().getResource(getUrl());
if (resource == null || !resource.exists()) {
return null;
}
return resource;
}
@Override
protected Mono<Void> renderInternal(Map<String, Object> model,
MediaType contentType, ServerWebExchange exchange) {
Resource resource = resolveResource();
if (resource == null) {
return Mono.error(new IllegalStateException("Could not find Mustache template with URL ["
+ getUrl() + "]"));
}
DataBuffer dataBuffer = exchange.getResponse().bufferFactory().allocateBuffer();
try (Reader reader = getReader(resource)) {
Template template = this.compiler.compile(reader);
Charset charset = getCharset(contentType).orElse(getDefaultCharset());
try (Writer writer = new OutputStreamWriter(dataBuffer.asOutputStream(), charset)) {
template.execute(model, writer);
writer.flush();
}
}
catch (Throwable exc) {
return Mono.error(exc);
}
return exchange.getResponse().writeWith(Flux.just(dataBuffer));
}
private Reader getReader(Resource resource) throws IOException {
if (this.charset != null) {
return new InputStreamReader(resource.getInputStream(), this.charset);
}
return new InputStreamReader(resource.getInputStream());
}
private Optional<Charset> getCharset(MediaType mediaType) {
return (mediaType != null ? Optional.ofNullable(mediaType.getCharset()) : Optional.empty());
}
}
/*
* 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.reactive;
import com.samskivert.mustache.Mustache;
import org.springframework.web.reactive.result.view.AbstractUrlBasedView;
import org.springframework.web.reactive.result.view.UrlBasedViewResolver;
import org.springframework.web.reactive.result.view.ViewResolver;
/**
* Spring WebFlux {@link ViewResolver} for Mustache.
*
* @author Brian Clozel
* @since 2.0.0
*/
public class MustacheViewResolver extends UrlBasedViewResolver {
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());
}
/**
* Set the charset.
* @param charset the charset
*/
public void setCharset(String charset) {
this.charset = charset;
}
@Override
protected Class<?> requiredViewClass() {
return MustacheView.class;
}
@Override
protected AbstractUrlBasedView createUrlBasedView(String viewName) {
MustacheView view = (MustacheView) super.createUrlBasedView(viewName);
view.setCompiler(this.compiler);
view.setCharset(this.charset);
return view;
}
}
/*
* 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.
*/
/**
* Auto-configuration for Mustache with Spring WebFlux.
*/
package org.springframework.boot.autoconfigure.mustache.reactive;
/* /*
* Copyright 2012-2015 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.
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.autoconfigure.mustache.web; package org.springframework.boot.autoconfigure.mustache.servlet;
import java.util.Map; import java.util.Map;
......
/* /*
* 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.
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.autoconfigure.mustache.web; package org.springframework.boot.autoconfigure.mustache.servlet;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
......
/* /*
* Copyright 2012-2015 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.
...@@ -17,4 +17,4 @@ ...@@ -17,4 +17,4 @@
/** /**
* Auto-configuration for Mustache with Spring MVC. * Auto-configuration for Mustache with Spring MVC.
*/ */
package org.springframework.boot.autoconfigure.mustache.web; package org.springframework.boot.autoconfigure.mustache.servlet;
...@@ -34,7 +34,7 @@ import org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfigura ...@@ -34,7 +34,7 @@ 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.web.MustacheViewResolver; 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;
......
/*
* 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;
import org.junit.Test;
import org.springframework.boot.autoconfigure.mustache.servlet.MustacheViewResolver;
import org.springframework.boot.test.util.EnvironmentTestUtils;
import org.springframework.boot.web.reactive.context.GenericReactiveWebApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link MustacheAutoConfiguration}.
*
* @author Brian Clozel
*/
public class MustacheAutoConfigurationTests {
private AnnotationConfigWebApplicationContext webContext;
private GenericReactiveWebApplicationContext reactiveWebContext;
@Test
public void registerBeansForServletApp() {
loadWithServlet(null);
assertThat(this.webContext.getBeansOfType(Mustache.Compiler.class)).hasSize(1);
assertThat(this.webContext.getBeansOfType(MustacheResourceTemplateLoader.class)).hasSize(1);
assertThat(this.webContext.getBeansOfType(MustacheViewResolver.class)).hasSize(1);
}
@Test
public void registerCompilerForServletApp() {
loadWithServlet(CustomCompilerConfiguration.class);
assertThat(this.webContext.getBeansOfType(MustacheResourceTemplateLoader.class)).hasSize(1);
assertThat(this.webContext.getBeansOfType(MustacheViewResolver.class)).hasSize(1);
assertThat(this.webContext.getBeansOfType(Mustache.Compiler.class)).hasSize(1);
assertThat(this.webContext.getBean(Mustache.Compiler.class).standardsMode).isTrue();
}
@Test
public void registerBeansForReactiveApp() {
loadWithReactive(null);
assertThat(this.reactiveWebContext.getBeansOfType(Mustache.Compiler.class)).hasSize(1);
assertThat(this.reactiveWebContext.getBeansOfType(MustacheResourceTemplateLoader.class)).hasSize(1);
assertThat(this.reactiveWebContext.getBeansOfType(MustacheViewResolver.class)).isEmpty();
assertThat(this.reactiveWebContext
.getBeansOfType(org.springframework.boot.autoconfigure.mustache.reactive.MustacheViewResolver.class)
).hasSize(1);
}
@Test
public void registerCompilerForReactiveApp() {
loadWithReactive(CustomCompilerConfiguration.class);
assertThat(this.reactiveWebContext.getBeansOfType(Mustache.Compiler.class)).hasSize(1);
assertThat(this.reactiveWebContext.getBeansOfType(MustacheResourceTemplateLoader.class)).hasSize(1);
assertThat(this.reactiveWebContext.getBeansOfType(MustacheViewResolver.class)).isEmpty();
assertThat(this.reactiveWebContext
.getBeansOfType(org.springframework.boot.autoconfigure.mustache.reactive.MustacheViewResolver.class)
).hasSize(1);
assertThat(this.reactiveWebContext.getBean(Mustache.Compiler.class).standardsMode).isTrue();
}
private void loadWithServlet(Class<?> config) {
this.webContext = new AnnotationConfigWebApplicationContext();
EnvironmentTestUtils.addEnvironment(this.webContext,
"spring.mustache.prefix=classpath:/mustache-templates/");
if (config != null) {
this.webContext.register(config);
}
this.webContext.register(BaseConfiguration.class);
this.webContext.refresh();
}
private void loadWithReactive(Class<?> config) {
this.reactiveWebContext = new GenericReactiveWebApplicationContext();
EnvironmentTestUtils.addEnvironment(this.reactiveWebContext,
"spring.mustache.prefix=classpath:/mustache-templates/");
if (config != null) {
this.reactiveWebContext.register(config);
}
this.reactiveWebContext.register(BaseConfiguration.class);
this.reactiveWebContext.refresh();
}
@Configuration
@Import({MustacheAutoConfiguration.class})
protected static class BaseConfiguration {
}
@Configuration
protected static class CustomCompilerConfiguration {
@Bean
public Mustache.Compiler compiler(Mustache.TemplateLoader mustacheTemplateLoader) {
return Mustache.compiler().standardsMode(true).withLoader(mustacheTemplateLoader);
}
}
}
/*
* 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.reactive;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.support.GenericApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link org.springframework.boot.autoconfigure.mustache.reactive.MustacheViewResolver}.
*
* @author Brian Clozel
*/
public class MustacheViewResolverTests {
private MustacheViewResolver resolver = new MustacheViewResolver();
@Before
public void init() {
GenericApplicationContext applicationContext = new GenericApplicationContext();
applicationContext.refresh();
this.resolver.setApplicationContext(applicationContext);
this.resolver.setPrefix("classpath:/mustache-templates/");
this.resolver.setSuffix(".html");
}
@Test
public void resolveNonExistent() throws Exception {
assertThat(this.resolver.resolveViewName("bar", null).block()).isNull();
}
@Test
public void resolveExisting() throws Exception {
assertThat(this.resolver.resolveViewName("foo", null).block()).isNotNull();
}
}
/*
* 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.reactive;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import com.samskivert.mustache.Mustache;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.http.MediaType;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.MockServerWebExchange;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link MustacheView}.
*
* @author Brian Clozel
*/
public class MustacheViewTests {
private GenericApplicationContext context = new GenericApplicationContext();
private MockServerWebExchange exchange;
@Before
public void init() {
this.context.refresh();
}
@Test
public void viewResolvesHandlebars() throws Exception {
this.exchange = MockServerHttpRequest.get("/test").toExchange();
MustacheView view = new MustacheView();
view.setCompiler(Mustache.compiler());
view.setUrl("classpath:/mustache-templates/foo.html");
view.setCharset(StandardCharsets.UTF_8.displayName());
view.setApplicationContext(this.context);
view.render(Collections.singletonMap("World", "Spring"), MediaType.TEXT_HTML, this.exchange).block();
assertThat(this.exchange.getResponse().getBodyAsString().block()).isEqualTo("Hello Spring");
}
}
...@@ -14,108 +14,105 @@ ...@@ -14,108 +14,105 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.autoconfigure.mustache; package org.springframework.boot.autoconfigure.mustache.reactive;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Date; import java.util.Date;
import java.util.Map;
import org.junit.Before; import com.samskivert.mustache.Mustache;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; 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.WebApplicationType;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration; import org.springframework.boot.autoconfigure.mustache.MustacheResourceTemplateLoader;
import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration;
import org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerAutoConfiguration;
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.test.web.client.TestRestTemplate; import org.springframework.context.annotation.Bean;
import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Integration tests for {@link MustacheAutoConfiguration}. * Integration Tests for {@link MustacheAutoConfiguration}, {@link MustacheViewResolver}
* and {@link MustacheView}.
* *
* @author Dave Syer * @author Brian Clozel
*/ */
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@DirtiesContext @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT,
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = { properties = "spring.main.web-application-type=reactive")
"spring.mustache.prefix:classpath:/mustache-templates/" }) public class MustacheWebIntegrationTests {
public class MustacheAutoConfigurationIntegrationTests {
@Autowired @Autowired
private ServletWebServerApplicationContext context; private WebTestClient client;
private int port;
@Before
public void init() {
this.port = this.context.getWebServer().getPort();
}
@Test @Test
public void testHomePage() throws Exception { public void testHomePage() throws Exception {
String body = new TestRestTemplate().getForObject("http://localhost:" + this.port, String result = (String) this.client.get().uri("/").exchange()
String.class); .expectStatus().isOk()
assertThat(body.contains("Hello World")).isTrue(); .expectBody(String.class).returnResult().getResponseBody();
assertThat(result).contains("Hello App").contains("Hello World");
} }
@Test @Test
public void testPartialPage() throws Exception { public void testPartialPage() throws Exception {
String body = new TestRestTemplate() String result = (String) this.client.get().uri("/partial").exchange()
.getForObject("http://localhost:" + this.port + "/partial", String.class); .expectStatus().isOk()
assertThat(body.contains("Hello World")).isTrue(); .expectBody(String.class).returnResult().getResponseBody();
assertThat(result).contains("Hello App").contains("Hello World");
} }
@Configuration @Configuration
@MinimalWebConfiguration @Import({ReactiveWebServerAutoConfiguration.class,
WebFluxAutoConfiguration.class,
HttpHandlerAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class})
@Controller @Controller
public static class Application { public static class Application {
@RequestMapping("/") @RequestMapping("/")
public String home(Map<String, Object> model) { public String home(Model model) {
model.put("time", new Date()); model.addAttribute("time", new Date());
model.put("message", "Hello World"); model.addAttribute("message", "Hello World");
model.put("title", "Hello App"); model.addAttribute("title", "Hello App");
return "home"; return "home";
} }
@RequestMapping("/partial") @RequestMapping("/partial")
public String layout(Map<String, Object> model) { public String layout(Model model) {
model.put("time", new Date()); model.addAttribute("time", new Date());
model.put("message", "Hello World"); model.addAttribute("message", "Hello World");
model.put("title", "Hello App"); model.addAttribute("title", "Hello App");
return "partial"; return "partial";
} }
public static void main(String[] args) { @Bean
SpringApplication.run(Application.class, args); public MustacheViewResolver viewResolver() {
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");
return resolver;
} }
} public static void main(String[] args) {
SpringApplication application = new SpringApplication(Application.class);
@Target(ElementType.TYPE) application.setWebApplicationType(WebApplicationType.REACTIVE);
@Retention(RetentionPolicy.RUNTIME) application.run(args);
@Documented }
@Import({ MustacheAutoConfiguration.class,
ServletWebServerFactoryAutoConfiguration.class,
DispatcherServletAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class })
protected @interface MinimalWebConfiguration {
} }
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.autoconfigure.mustache.web; package org.springframework.boot.autoconfigure.mustache.servlet;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.InputStream; import java.io.InputStream;
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.autoconfigure.mustache.web; package org.springframework.boot.autoconfigure.mustache.servlet;
import java.util.Collections; import java.util.Collections;
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.autoconfigure.mustache.web; package org.springframework.boot.autoconfigure.mustache.servlet;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
......
...@@ -21,6 +21,10 @@ ...@@ -21,6 +21,10 @@
</properties> </properties>
<dependencies> <dependencies>
<!-- Compile --> <!-- Compile -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mustache</artifactId> <artifactId>spring-boot-starter-mustache</artifactId>
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
</parent> </parent>
<artifactId>spring-boot-starter-mustache</artifactId> <artifactId>spring-boot-starter-mustache</artifactId>
<name>Spring Boot Mustache Starter</name> <name>Spring Boot Mustache Starter</name>
<description>Starter for building MVC web applications using Mustache views</description> <description>Starter for building web applications using Mustache views</description>
<url>http://projects.spring.io/spring-boot/</url> <url>http://projects.spring.io/spring-boot/</url>
<organization> <organization>
<name>Pivotal Software, Inc.</name> <name>Pivotal Software, Inc.</name>
...@@ -22,10 +22,6 @@ ...@@ -22,10 +22,6 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId> <artifactId>spring-boot-starter</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.samskivert</groupId> <groupId>com.samskivert</groupId>
<artifactId>jmustache</artifactId> <artifactId>jmustache</artifactId>
......
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