Skip to content

Commit 7e77e64

Browse files
committed
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 spring-projectsgh-8648
1 parent c2e5fd0 commit 7e77e64

File tree

19 files changed

+618
-139
lines changed

19 files changed

+618
-139
lines changed

spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mobile/DeviceDelegatingViewResolverAutoConfiguration.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
import org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration;
2929
import org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration;
3030
import org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration;
31-
import org.springframework.boot.autoconfigure.mustache.web.MustacheViewResolver;
31+
import org.springframework.boot.autoconfigure.mustache.servlet.MustacheViewResolver;
3232
import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration;
3333
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
3434
import org.springframework.boot.context.properties.EnableConfigurationProperties;

spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mustache/MustacheAutoConfiguration.java

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
3131
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
3232
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
33-
import org.springframework.boot.autoconfigure.mustache.web.MustacheViewResolver;
33+
import org.springframework.boot.autoconfigure.mustache.servlet.MustacheViewResolver;
3434
import org.springframework.boot.autoconfigure.template.TemplateLocation;
3535
import org.springframework.boot.context.properties.EnableConfigurationProperties;
3636
import org.springframework.context.ApplicationContext;
@@ -43,6 +43,7 @@
4343
* {@link EnableAutoConfiguration Auto-configuration} for Mustache.
4444
*
4545
* @author Dave Syer
46+
* @author Brian Clozel
4647
* @since 1.2.2
4748
*/
4849
@Configuration
@@ -123,4 +124,34 @@ public MustacheViewResolver mustacheViewResolver(Compiler mustacheCompiler) {
123124

124125
}
125126

127+
@Configuration
128+
@ConditionalOnWebApplication(type = Type.REACTIVE)
129+
protected static class MustacheReactiveWebConfiguration {
130+
131+
private final MustacheProperties mustache;
132+
133+
protected MustacheReactiveWebConfiguration(MustacheProperties mustache) {
134+
this.mustache = mustache;
135+
}
136+
137+
@Bean
138+
@ConditionalOnMissingBean(org.springframework.boot.autoconfigure
139+
.mustache.reactive.MustacheViewResolver.class)
140+
public org.springframework.boot.autoconfigure
141+
.mustache.reactive.MustacheViewResolver mustacheViewResolver(Compiler mustacheCompiler) {
142+
org.springframework.boot.autoconfigure
143+
.mustache.reactive.MustacheViewResolver resolver
144+
= new org.springframework.boot.autoconfigure
145+
.mustache.reactive.MustacheViewResolver(mustacheCompiler);
146+
resolver.setPrefix(this.mustache.getPrefix());
147+
resolver.setSuffix(this.mustache.getSuffix());
148+
resolver.setViewNames(this.mustache.getViewNames());
149+
resolver.setRequestContextAttribute(this.mustache.getRequestContextAttribute());
150+
resolver.setCharset(this.mustache.getCharsetName());
151+
resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
152+
return resolver;
153+
}
154+
155+
}
156+
126157
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.mustache.reactive;
18+
19+
import java.io.IOException;
20+
import java.io.InputStreamReader;
21+
import java.io.OutputStreamWriter;
22+
import java.io.Reader;
23+
import java.io.Writer;
24+
import java.nio.charset.Charset;
25+
import java.util.Locale;
26+
import java.util.Map;
27+
import java.util.Optional;
28+
29+
import com.samskivert.mustache.Mustache;
30+
import com.samskivert.mustache.Template;
31+
import reactor.core.publisher.Flux;
32+
import reactor.core.publisher.Mono;
33+
34+
import org.springframework.core.io.Resource;
35+
import org.springframework.core.io.buffer.DataBuffer;
36+
import org.springframework.http.MediaType;
37+
import org.springframework.web.reactive.result.view.AbstractUrlBasedView;
38+
import org.springframework.web.reactive.result.view.View;
39+
import org.springframework.web.server.ServerWebExchange;
40+
41+
/**
42+
* Spring WebFlux {@link View} using the Mustache template engine.
43+
*
44+
* @author Brian Clozel
45+
* @since 2.0.0
46+
*/
47+
public class MustacheView extends AbstractUrlBasedView {
48+
49+
private Mustache.Compiler compiler;
50+
51+
private String charset;
52+
53+
/**
54+
* Set the JMustache compiler to be used by this view.
55+
* <p>Typically this property is not set directly. Instead a single
56+
* {@link Mustache.Compiler} is expected in the Spring application context
57+
* which is used to compile Mustache templates.
58+
* @param compiler the Mustache compiler
59+
*/
60+
public void setCompiler(Mustache.Compiler compiler) {
61+
this.compiler = compiler;
62+
}
63+
64+
/**
65+
* Set the charset used for reading Mustache template files.
66+
* @param charset the charset to use for reading template files
67+
*/
68+
public void setCharset(String charset) {
69+
this.charset = charset;
70+
}
71+
72+
@Override
73+
public boolean checkResourceExists(Locale locale) throws Exception {
74+
return resolveResource() != null;
75+
}
76+
77+
private Resource resolveResource() {
78+
Resource resource = getApplicationContext().getResource(getUrl());
79+
if (resource == null || !resource.exists()) {
80+
return null;
81+
}
82+
return resource;
83+
}
84+
85+
@Override
86+
protected Mono<Void> renderInternal(Map<String, Object> model,
87+
MediaType contentType, ServerWebExchange exchange) {
88+
Resource resource = resolveResource();
89+
if (resource == null) {
90+
return Mono.error(new IllegalStateException("Could not find Mustache template with URL ["
91+
+ getUrl() + "]"));
92+
}
93+
DataBuffer dataBuffer = exchange.getResponse().bufferFactory().allocateBuffer();
94+
try (Reader reader = getReader(resource)) {
95+
Template template = this.compiler.compile(reader);
96+
Charset charset = getCharset(contentType).orElse(getDefaultCharset());
97+
try (Writer writer = new OutputStreamWriter(dataBuffer.asOutputStream(), charset)) {
98+
template.execute(model, writer);
99+
writer.flush();
100+
}
101+
}
102+
catch (Throwable exc) {
103+
return Mono.error(exc);
104+
}
105+
return exchange.getResponse().writeWith(Flux.just(dataBuffer));
106+
}
107+
108+
private Reader getReader(Resource resource) throws IOException {
109+
if (this.charset != null) {
110+
return new InputStreamReader(resource.getInputStream(), this.charset);
111+
}
112+
return new InputStreamReader(resource.getInputStream());
113+
}
114+
115+
private Optional<Charset> getCharset(MediaType mediaType) {
116+
return (mediaType != null ? Optional.ofNullable(mediaType.getCharset()) : Optional.empty());
117+
}
118+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.mustache.reactive;
18+
19+
import com.samskivert.mustache.Mustache;
20+
21+
import org.springframework.web.reactive.result.view.AbstractUrlBasedView;
22+
import org.springframework.web.reactive.result.view.UrlBasedViewResolver;
23+
import org.springframework.web.reactive.result.view.ViewResolver;
24+
25+
/**
26+
* Spring WebFlux {@link ViewResolver} for Mustache.
27+
*
28+
* @author Brian Clozel
29+
* @since 2.0.0
30+
*/
31+
public class MustacheViewResolver extends UrlBasedViewResolver {
32+
33+
private final Mustache.Compiler compiler;
34+
35+
private String charset;
36+
37+
/**
38+
* Create a {@code MustacheViewResolver} backed by a default
39+
* instance of a {@link Mustache.Compiler}.
40+
*/
41+
public MustacheViewResolver() {
42+
this.compiler = Mustache.compiler();
43+
setViewClass(requiredViewClass());
44+
}
45+
46+
/**
47+
* Create a {@code MustacheViewResolver} backed by a custom
48+
* instance of a {@link Mustache.Compiler}.
49+
* @param compiler the Mustache compiler used to compile templates
50+
*/
51+
public MustacheViewResolver(Mustache.Compiler compiler) {
52+
this.compiler = compiler;
53+
setViewClass(requiredViewClass());
54+
}
55+
56+
/**
57+
* Set the charset.
58+
* @param charset the charset
59+
*/
60+
public void setCharset(String charset) {
61+
this.charset = charset;
62+
}
63+
64+
@Override
65+
protected Class<?> requiredViewClass() {
66+
return MustacheView.class;
67+
}
68+
69+
@Override
70+
protected AbstractUrlBasedView createUrlBasedView(String viewName) {
71+
MustacheView view = (MustacheView) super.createUrlBasedView(viewName);
72+
view.setCompiler(this.compiler);
73+
view.setCharset(this.charset);
74+
return view;
75+
}
76+
77+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* Auto-configuration for Mustache with Spring WebFlux.
19+
*/
20+
package org.springframework.boot.autoconfigure.mustache.reactive;
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2015 the original author or authors.
2+
* Copyright 2012-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.boot.autoconfigure.mustache.web;
17+
package org.springframework.boot.autoconfigure.mustache.servlet;
1818

1919
import java.util.Map;
2020

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2016 the original author or authors.
2+
* Copyright 2012-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.boot.autoconfigure.mustache.web;
17+
package org.springframework.boot.autoconfigure.mustache.servlet;
1818

1919
import java.io.IOException;
2020
import java.io.InputStreamReader;
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2015 the original author or authors.
2+
* Copyright 2012-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,4 +17,4 @@
1717
/**
1818
* Auto-configuration for Mustache with Spring MVC.
1919
*/
20-
package org.springframework.boot.autoconfigure.mustache.web;
20+
package org.springframework.boot.autoconfigure.mustache.servlet;

spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/mobile/DeviceDelegatingViewResolverAutoConfigurationTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
import org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration;
3535
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
3636
import org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration;
37-
import org.springframework.boot.autoconfigure.mustache.web.MustacheViewResolver;
37+
import org.springframework.boot.autoconfigure.mustache.servlet.MustacheViewResolver;
3838
import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration;
3939
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
4040
import org.springframework.boot.test.util.EnvironmentTestUtils;

0 commit comments

Comments
 (0)