Skip to content

Commit b27189c

Browse files
committed
Merge branch 'thymeleaf-3.1'
2 parents e95f038 + 26fecbe commit b27189c

File tree

61 files changed

+2340
-31
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+2340
-31
lines changed

buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 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.
@@ -189,6 +189,7 @@ private void templatePrefixes(Config prefix) {
189189
prefix.accept("spring.freemarker");
190190
prefix.accept("spring.groovy");
191191
prefix.accept("spring.mustache");
192+
prefix.accept("spring.thymeleaf");
192193
prefix.accept("spring.groovy.template.configuration", "See GroovyMarkupConfigurer");
193194
}
194195

spring-boot-project/spring-boot-autoconfigure/build.gradle

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,13 @@ dependencies {
6969
optional("org.apiguardian:apiguardian-api")
7070
optional("org.codehaus.groovy:groovy-templates")
7171
optional("com.github.ben-manes.caffeine:caffeine")
72+
optional("com.github.mxab.thymeleaf.extras:thymeleaf-extras-data-attribute")
7273
optional("com.sendgrid:sendgrid-java") {
7374
exclude group: "commons-logging", module: "commons-logging"
7475
}
7576
optional("com.unboundid:unboundid-ldapsdk")
7677
optional("com.zaxxer:HikariCP")
78+
optional("nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect")
7779
optional("org.aspectj:aspectjweaver")
7880
optional("org.eclipse.jetty:jetty-webapp") {
7981
exclude group: "javax.servlet", module: "javax.servlet-api"
@@ -175,6 +177,10 @@ dependencies {
175177
exclude group: "org.eclipse.jetty", module: "jetty-servlet"
176178
exclude group: "jakarta.mail", module: "jakarta.mail-api"
177179
}
180+
optional("org.thymeleaf:thymeleaf")
181+
optional("org.thymeleaf:thymeleaf-spring6")
182+
optional("org.thymeleaf.extras:thymeleaf-extras-java8time")
183+
optional("org.thymeleaf.extras:thymeleaf-extras-springsecurity6")
178184
optional("redis.clients:jedis")
179185

180186
testImplementation(project(":spring-boot-project:spring-boot-tools:spring-boot-test-support"))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright 2012-2022 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+
* https://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.thymeleaf;
18+
19+
import org.thymeleaf.ITemplateEngine;
20+
import org.thymeleaf.dialect.IDialect;
21+
import org.thymeleaf.spring6.ISpringTemplateEngine;
22+
import org.thymeleaf.spring6.ISpringWebFluxTemplateEngine;
23+
import org.thymeleaf.spring6.SpringTemplateEngine;
24+
import org.thymeleaf.spring6.SpringWebFluxTemplateEngine;
25+
import org.thymeleaf.templateresolver.ITemplateResolver;
26+
27+
import org.springframework.beans.factory.ObjectProvider;
28+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
29+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
30+
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
31+
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
32+
import org.springframework.context.annotation.Bean;
33+
import org.springframework.context.annotation.Configuration;
34+
35+
/**
36+
* Configuration classes for Thymeleaf's {@link ITemplateEngine}. Imported by
37+
* {@link ThymeleafAutoConfiguration}.
38+
*
39+
* @author Andy Wilkinson
40+
*/
41+
class TemplateEngineConfigurations {
42+
43+
@Configuration(proxyBeanMethods = false)
44+
static class DefaultTemplateEngineConfiguration {
45+
46+
@Bean
47+
@ConditionalOnMissingBean(ISpringTemplateEngine.class)
48+
SpringTemplateEngine templateEngine(ThymeleafProperties properties,
49+
ObjectProvider<ITemplateResolver> templateResolvers, ObjectProvider<IDialect> dialects) {
50+
SpringTemplateEngine engine = new SpringTemplateEngine();
51+
engine.setEnableSpringELCompiler(properties.isEnableSpringElCompiler());
52+
engine.setRenderHiddenMarkersBeforeCheckboxes(properties.isRenderHiddenMarkersBeforeCheckboxes());
53+
templateResolvers.orderedStream().forEach(engine::addTemplateResolver);
54+
dialects.orderedStream().forEach(engine::addDialect);
55+
return engine;
56+
}
57+
58+
}
59+
60+
@Configuration(proxyBeanMethods = false)
61+
@ConditionalOnWebApplication(type = Type.REACTIVE)
62+
@ConditionalOnProperty(name = "spring.thymeleaf.enabled", matchIfMissing = true)
63+
static class ReactiveTemplateEngineConfiguration {
64+
65+
@Bean
66+
@ConditionalOnMissingBean(ISpringWebFluxTemplateEngine.class)
67+
SpringWebFluxTemplateEngine templateEngine(ThymeleafProperties properties,
68+
ObjectProvider<ITemplateResolver> templateResolvers, ObjectProvider<IDialect> dialects) {
69+
SpringWebFluxTemplateEngine engine = new SpringWebFluxTemplateEngine();
70+
engine.setEnableSpringELCompiler(properties.isEnableSpringElCompiler());
71+
engine.setRenderHiddenMarkersBeforeCheckboxes(properties.isRenderHiddenMarkersBeforeCheckboxes());
72+
templateResolvers.orderedStream().forEach(engine::addTemplateResolver);
73+
dialects.orderedStream().forEach(engine::addDialect);
74+
return engine;
75+
}
76+
77+
}
78+
79+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
/*
2+
* Copyright 2012-2022 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+
* https://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.thymeleaf;
18+
19+
import java.util.LinkedHashMap;
20+
21+
import jakarta.servlet.DispatcherType;
22+
23+
import com.github.mxab.thymeleaf.extras.dataattribute.dialect.DataAttributeDialect;
24+
import nz.net.ultraq.thymeleaf.layoutdialect.LayoutDialect;
25+
import org.apache.commons.logging.Log;
26+
import org.apache.commons.logging.LogFactory;
27+
import org.thymeleaf.extras.java8time.dialect.Java8TimeDialect;
28+
import org.thymeleaf.extras.springsecurity6.dialect.SpringSecurityDialect;
29+
import org.thymeleaf.spring6.ISpringWebFluxTemplateEngine;
30+
import org.thymeleaf.spring6.SpringTemplateEngine;
31+
import org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver;
32+
import org.thymeleaf.spring6.view.ThymeleafViewResolver;
33+
import org.thymeleaf.spring6.view.reactive.ThymeleafReactiveViewResolver;
34+
import org.thymeleaf.templatemode.TemplateMode;
35+
36+
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
37+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
38+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
39+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
40+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
41+
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
42+
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
43+
import org.springframework.boot.autoconfigure.template.TemplateLocation;
44+
import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafProperties.Reactive;
45+
import org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain;
46+
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
47+
import org.springframework.boot.autoconfigure.web.servlet.ConditionalOnMissingFilterBean;
48+
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
49+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
50+
import org.springframework.boot.context.properties.PropertyMapper;
51+
import org.springframework.boot.web.servlet.FilterRegistrationBean;
52+
import org.springframework.context.ApplicationContext;
53+
import org.springframework.context.annotation.Bean;
54+
import org.springframework.context.annotation.Configuration;
55+
import org.springframework.context.annotation.Import;
56+
import org.springframework.core.Ordered;
57+
import org.springframework.util.MimeType;
58+
import org.springframework.util.unit.DataSize;
59+
import org.springframework.web.servlet.resource.ResourceUrlEncodingFilter;
60+
61+
/**
62+
* {@link EnableAutoConfiguration Auto-configuration} for Thymeleaf.
63+
*
64+
* @author Dave Syer
65+
* @author Andy Wilkinson
66+
* @author Stephane Nicoll
67+
* @author Brian Clozel
68+
* @author Eddú Meléndez
69+
* @author Daniel Fernández
70+
* @author Kazuki Shimizu
71+
* @author Artsiom Yudovin
72+
* @since 1.0.0
73+
*/
74+
@Configuration(proxyBeanMethods = false)
75+
@EnableConfigurationProperties(ThymeleafProperties.class)
76+
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
77+
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
78+
@Import({ TemplateEngineConfigurations.ReactiveTemplateEngineConfiguration.class,
79+
TemplateEngineConfigurations.DefaultTemplateEngineConfiguration.class })
80+
public class ThymeleafAutoConfiguration {
81+
82+
@Configuration(proxyBeanMethods = false)
83+
@ConditionalOnMissingBean(name = "defaultTemplateResolver")
84+
static class DefaultTemplateResolverConfiguration {
85+
86+
private static final Log logger = LogFactory.getLog(DefaultTemplateResolverConfiguration.class);
87+
88+
private final ThymeleafProperties properties;
89+
90+
private final ApplicationContext applicationContext;
91+
92+
DefaultTemplateResolverConfiguration(ThymeleafProperties properties, ApplicationContext applicationContext) {
93+
this.properties = properties;
94+
this.applicationContext = applicationContext;
95+
checkTemplateLocationExists();
96+
}
97+
98+
private void checkTemplateLocationExists() {
99+
boolean checkTemplateLocation = this.properties.isCheckTemplateLocation();
100+
if (checkTemplateLocation) {
101+
TemplateLocation location = new TemplateLocation(this.properties.getPrefix());
102+
if (!location.exists(this.applicationContext)) {
103+
logger.warn("Cannot find template location: " + location + " (please add some templates or check "
104+
+ "your Thymeleaf configuration)");
105+
}
106+
}
107+
}
108+
109+
@Bean
110+
SpringResourceTemplateResolver defaultTemplateResolver() {
111+
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
112+
resolver.setApplicationContext(this.applicationContext);
113+
resolver.setPrefix(this.properties.getPrefix());
114+
resolver.setSuffix(this.properties.getSuffix());
115+
resolver.setTemplateMode(this.properties.getMode());
116+
if (this.properties.getEncoding() != null) {
117+
resolver.setCharacterEncoding(this.properties.getEncoding().name());
118+
}
119+
resolver.setCacheable(this.properties.isCache());
120+
Integer order = this.properties.getTemplateResolverOrder();
121+
if (order != null) {
122+
resolver.setOrder(order);
123+
}
124+
resolver.setCheckExistence(this.properties.isCheckTemplate());
125+
return resolver;
126+
}
127+
128+
}
129+
130+
@Configuration(proxyBeanMethods = false)
131+
@ConditionalOnWebApplication(type = Type.SERVLET)
132+
@ConditionalOnProperty(name = "spring.thymeleaf.enabled", matchIfMissing = true)
133+
static class ThymeleafWebMvcConfiguration {
134+
135+
@Bean
136+
@ConditionalOnEnabledResourceChain
137+
@ConditionalOnMissingFilterBean(ResourceUrlEncodingFilter.class)
138+
FilterRegistrationBean<ResourceUrlEncodingFilter> resourceUrlEncodingFilter() {
139+
FilterRegistrationBean<ResourceUrlEncodingFilter> registration = new FilterRegistrationBean<>(
140+
new ResourceUrlEncodingFilter());
141+
registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ERROR);
142+
return registration;
143+
}
144+
145+
@Configuration(proxyBeanMethods = false)
146+
static class ThymeleafViewResolverConfiguration {
147+
148+
@Bean
149+
@ConditionalOnMissingBean(name = "thymeleafViewResolver")
150+
ThymeleafViewResolver thymeleafViewResolver(ThymeleafProperties properties,
151+
SpringTemplateEngine templateEngine) {
152+
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
153+
resolver.setTemplateEngine(templateEngine);
154+
resolver.setCharacterEncoding(properties.getEncoding().name());
155+
resolver.setContentType(
156+
appendCharset(properties.getServlet().getContentType(), resolver.getCharacterEncoding()));
157+
resolver.setProducePartialOutputWhileProcessing(
158+
properties.getServlet().isProducePartialOutputWhileProcessing());
159+
resolver.setExcludedViewNames(properties.getExcludedViewNames());
160+
resolver.setViewNames(properties.getViewNames());
161+
// This resolver acts as a fallback resolver (e.g. like a
162+
// InternalResourceViewResolver) so it needs to have low precedence
163+
resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 5);
164+
resolver.setCache(properties.isCache());
165+
return resolver;
166+
}
167+
168+
private String appendCharset(MimeType type, String charset) {
169+
if (type.getCharset() != null) {
170+
return type.toString();
171+
}
172+
LinkedHashMap<String, String> parameters = new LinkedHashMap<>();
173+
parameters.put("charset", charset);
174+
parameters.putAll(type.getParameters());
175+
return new MimeType(type, parameters).toString();
176+
}
177+
178+
}
179+
180+
}
181+
182+
@Configuration(proxyBeanMethods = false)
183+
@ConditionalOnWebApplication(type = Type.REACTIVE)
184+
@ConditionalOnProperty(name = "spring.thymeleaf.enabled", matchIfMissing = true)
185+
static class ThymeleafWebFluxConfiguration {
186+
187+
@Bean
188+
@ConditionalOnMissingBean(name = "thymeleafReactiveViewResolver")
189+
ThymeleafReactiveViewResolver thymeleafViewResolver(ISpringWebFluxTemplateEngine templateEngine,
190+
ThymeleafProperties properties) {
191+
ThymeleafReactiveViewResolver resolver = new ThymeleafReactiveViewResolver();
192+
resolver.setTemplateEngine(templateEngine);
193+
mapProperties(properties, resolver);
194+
mapReactiveProperties(properties.getReactive(), resolver);
195+
// This resolver acts as a fallback resolver (e.g. like a
196+
// InternalResourceViewResolver) so it needs to have low precedence
197+
resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 5);
198+
return resolver;
199+
}
200+
201+
private void mapProperties(ThymeleafProperties properties, ThymeleafReactiveViewResolver resolver) {
202+
PropertyMapper map = PropertyMapper.get();
203+
map.from(properties::getEncoding).to(resolver::setDefaultCharset);
204+
resolver.setExcludedViewNames(properties.getExcludedViewNames());
205+
resolver.setViewNames(properties.getViewNames());
206+
}
207+
208+
private void mapReactiveProperties(Reactive properties, ThymeleafReactiveViewResolver resolver) {
209+
PropertyMapper map = PropertyMapper.get();
210+
map.from(properties::getMediaTypes).whenNonNull().to(resolver::setSupportedMediaTypes);
211+
map.from(properties::getMaxChunkSize).asInt(DataSize::toBytes).when((size) -> size > 0)
212+
.to(resolver::setResponseMaxChunkSizeBytes);
213+
map.from(properties::getFullModeViewNames).to(resolver::setFullModeViewNames);
214+
map.from(properties::getChunkedModeViewNames).to(resolver::setChunkedModeViewNames);
215+
}
216+
217+
}
218+
219+
@Configuration(proxyBeanMethods = false)
220+
@ConditionalOnClass(LayoutDialect.class)
221+
static class ThymeleafWebLayoutConfiguration {
222+
223+
@Bean
224+
@ConditionalOnMissingBean
225+
LayoutDialect layoutDialect() {
226+
return new LayoutDialect();
227+
}
228+
229+
}
230+
231+
@Configuration(proxyBeanMethods = false)
232+
@ConditionalOnClass(DataAttributeDialect.class)
233+
static class DataAttributeDialectConfiguration {
234+
235+
@Bean
236+
@ConditionalOnMissingBean
237+
DataAttributeDialect dialect() {
238+
return new DataAttributeDialect();
239+
}
240+
241+
}
242+
243+
@Configuration(proxyBeanMethods = false)
244+
@ConditionalOnClass({ SpringSecurityDialect.class })
245+
static class ThymeleafSecurityDialectConfiguration {
246+
247+
@Bean
248+
@ConditionalOnMissingBean
249+
SpringSecurityDialect securityDialect() {
250+
return new SpringSecurityDialect();
251+
}
252+
253+
}
254+
255+
@Configuration(proxyBeanMethods = false)
256+
@ConditionalOnClass(Java8TimeDialect.class)
257+
static class ThymeleafJava8TimeDialect {
258+
259+
@Bean
260+
@ConditionalOnMissingBean
261+
Java8TimeDialect java8TimeDialect() {
262+
return new Java8TimeDialect();
263+
}
264+
265+
}
266+
267+
}

0 commit comments

Comments
 (0)