Rationalize Groovy Template configuration properties

Previously, spring.groovy.template.configuration properties were
bound directly to GroovyMarkupConfigurer. This meant that the
properties had no description or default values in the metadata.
It also resulted in some duplicate where a specific
spring.groovy.template property was also provided.

This commit deprecates the spring.groovy.template.configuration.*
properties in favour of specific spring.groovy.template.* properties
that are mapped to the GroovyMarkupConfigurer.

Closes gh-44722
This commit is contained in:
Andy Wilkinson 2025-03-17 15:14:26 +00:00
parent abc43894c7
commit be97e79ad6
5 changed files with 328 additions and 6 deletions

View File

@ -191,7 +191,6 @@ public abstract class DocumentConfigurationProperties extends DefaultTask {
prefix.accept("spring.groovy"); prefix.accept("spring.groovy");
prefix.accept("spring.mustache"); prefix.accept("spring.mustache");
prefix.accept("spring.thymeleaf"); prefix.accept("spring.thymeleaf");
prefix.accept("spring.groovy.template.configuration", "See GroovyMarkupConfigurer");
} }
private void serverPrefixes(Config prefix) { private void serverPrefixes(Config prefix) {

View File

@ -33,12 +33,15 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.template.TemplateLocation; import org.springframework.boot.autoconfigure.template.TemplateLocation;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
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.context.i18n.LocaleContextHolder; import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.env.Environment;
import org.springframework.core.log.LogMessage; import org.springframework.core.log.LogMessage;
import org.springframework.web.servlet.view.UrlBasedViewResolver; import org.springframework.web.servlet.view.UrlBasedViewResolver;
import org.springframework.web.servlet.view.groovy.GroovyMarkupConfig; import org.springframework.web.servlet.view.groovy.GroovyMarkupConfig;
@ -109,11 +112,23 @@ public class GroovyTemplateAutoConfiguration {
@Bean @Bean
@ConditionalOnMissingBean(GroovyMarkupConfig.class) @ConditionalOnMissingBean(GroovyMarkupConfig.class)
@ConfigurationProperties("spring.groovy.template.configuration") GroovyMarkupConfigurer groovyMarkupConfigurer(ObjectProvider<MarkupTemplateEngine> templateEngine,
public GroovyMarkupConfigurer groovyMarkupConfigurer(ObjectProvider<MarkupTemplateEngine> templateEngine) { Environment environment) {
GroovyMarkupConfigurer configurer = new GroovyMarkupConfigurer(); GroovyMarkupConfigurer configurer = new GroovyMarkupConfigurer();
configurer.setResourceLoaderPath(this.properties.getResourceLoaderPath()); PropertyMapper map = PropertyMapper.get();
configurer.setCacheTemplates(this.properties.isCache()); map.from(this.properties::isAutoEscape).to(configurer::setAutoEscape);
map.from(this.properties::isAutoIndent).to(configurer::setAutoIndent);
map.from(this.properties::getAutoIndentString).to(configurer::setAutoIndentString);
map.from(this.properties::isAutoNewLine).to(configurer::setAutoNewLine);
map.from(this.properties::getBaseTemplateClass).to(configurer::setBaseTemplateClass);
map.from(this.properties::isCache).to(configurer::setCacheTemplates);
map.from(this.properties::getDeclarationEncoding).to(configurer::setDeclarationEncoding);
map.from(this.properties::isExpandEmptyElements).to(configurer::setExpandEmptyElements);
map.from(this.properties::getLocale).to(configurer::setLocale);
map.from(this.properties::getNewLineString).to(configurer::setNewLineString);
map.from(this.properties::getResourceLoaderPath).to(configurer::setResourceLoaderPath);
map.from(this.properties::isUseDoubleQuotes).to(configurer::setUseDoubleQuotes);
Binder.get(environment).bind("spring.groovy.template.configuration", Bindable.ofInstance(configurer));
templateEngine.ifAvailable(configurer::setTemplateEngine); templateEngine.ifAvailable(configurer::setTemplateEngine);
return configurer; return configurer;
} }

View File

@ -16,6 +16,10 @@
package org.springframework.boot.autoconfigure.groovy.template; package org.springframework.boot.autoconfigure.groovy.template;
import java.util.Locale;
import groovy.text.markup.BaseTemplate;
import org.springframework.boot.autoconfigure.template.AbstractTemplateViewResolverProperties; import org.springframework.boot.autoconfigure.template.AbstractTemplateViewResolverProperties;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
@ -38,16 +42,139 @@ public class GroovyTemplateProperties extends AbstractTemplateViewResolverProper
public static final String DEFAULT_REQUEST_CONTEXT_ATTRIBUTE = "spring"; public static final String DEFAULT_REQUEST_CONTEXT_ATTRIBUTE = "spring";
/**
* Whether models that are assignable to CharSequence are escaped automatically.
*/
private boolean autoEscape;
/**
* Whether indents are rendered automatically.
*/
private boolean autoIndent;
/**
* String used for auto-indents.
*/
private String autoIndentString;
/**
* Whether new lines are rendered automatically.
*/
private boolean autoNewLine;
/**
* Template base class.
*/
private Class<? extends BaseTemplate> baseTemplateClass = BaseTemplate.class;
/**
* Encoding used to write the declaration heading.
*/
private String declarationEncoding;
/**
* Whether elements without a body should be written expanded (&lt;br&gt;&lt;/br&gt;)
* or not (&lt;br/&gt;).
*/
private boolean expandEmptyElements;
/**
* Default locale for template resolution.
*/
private Locale locale;
/**
* String used to write a new line. Defaults to the system's line separator.
*/
private String newLineString;
/** /**
* Template path. * Template path.
*/ */
private String resourceLoaderPath = DEFAULT_RESOURCE_LOADER_PATH; private String resourceLoaderPath = DEFAULT_RESOURCE_LOADER_PATH;
/**
* Whether attributes should use double quotes.
*/
private boolean useDoubleQuotes;
public GroovyTemplateProperties() { public GroovyTemplateProperties() {
super(DEFAULT_PREFIX, DEFAULT_SUFFIX); super(DEFAULT_PREFIX, DEFAULT_SUFFIX);
setRequestContextAttribute(DEFAULT_REQUEST_CONTEXT_ATTRIBUTE); setRequestContextAttribute(DEFAULT_REQUEST_CONTEXT_ATTRIBUTE);
} }
public boolean isAutoEscape() {
return this.autoEscape;
}
public void setAutoEscape(boolean autoEscape) {
this.autoEscape = autoEscape;
}
public boolean isAutoIndent() {
return this.autoIndent;
}
public void setAutoIndent(boolean autoIndent) {
this.autoIndent = autoIndent;
}
public String getAutoIndentString() {
return this.autoIndentString;
}
public void setAutoIndentString(String autoIndentString) {
this.autoIndentString = autoIndentString;
}
public boolean isAutoNewLine() {
return this.autoNewLine;
}
public void setAutoNewLine(boolean autoNewLine) {
this.autoNewLine = autoNewLine;
}
public Class<? extends BaseTemplate> getBaseTemplateClass() {
return this.baseTemplateClass;
}
public void setBaseTemplateClass(Class<? extends BaseTemplate> baseTemplateClass) {
this.baseTemplateClass = baseTemplateClass;
}
public String getDeclarationEncoding() {
return this.declarationEncoding;
}
public void setDeclarationEncoding(String declarationEncoding) {
this.declarationEncoding = declarationEncoding;
}
public boolean isExpandEmptyElements() {
return this.expandEmptyElements;
}
public void setExpandEmptyElements(boolean expandEmptyElements) {
this.expandEmptyElements = expandEmptyElements;
}
public Locale getLocale() {
return this.locale;
}
public void setLocale(Locale locale) {
this.locale = locale;
}
public String getNewLineString() {
return this.newLineString;
}
public void setNewLineString(String newLineString) {
this.newLineString = newLineString;
}
public String getResourceLoaderPath() { public String getResourceLoaderPath() {
return this.resourceLoaderPath; return this.resourceLoaderPath;
} }
@ -56,4 +183,12 @@ public class GroovyTemplateProperties extends AbstractTemplateViewResolverProper
this.resourceLoaderPath = resourceLoaderPath; this.resourceLoaderPath = resourceLoaderPath;
} }
public boolean isUseDoubleQuotes() {
return this.useDoubleQuotes;
}
public void setUseDoubleQuotes(boolean useDoubleQuotes) {
this.useDoubleQuotes = useDoubleQuotes;
}
} }

View File

@ -1511,6 +1511,99 @@
"name": "spring.graphql.schema.locations", "name": "spring.graphql.schema.locations",
"defaultValue": "classpath:graphql/**/" "defaultValue": "classpath:graphql/**/"
}, },
{
"name": "spring.groovy.template.configuration.auto-escape",
"deprecation": {
"replacement": "spring.groovy.template.auto-escape",
"level": "warning"
}
},
{
"name": "spring.groovy.template.configuration.auto-indent",
"deprecation": {
"replacement": "spring.groovy.template.auto-indent",
"level": "warning"
}
},
{
"name": "spring.groovy.template.configuration.auto-indent-string",
"deprecation": {
"replacement": "spring.groovy.template.auto-indent-string",
"level": "warning"
}
},
{
"name": "spring.groovy.template.configuration.auto-new-line",
"deprecation": {
"replacement": "spring.groovy.template.auto-new-line",
"level": "warning",
"since": "3.5.0"
}
},
{
"name": "spring.groovy.template.configuration.base-template-class",
"deprecation": {
"replacement": "spring.groovy.template.base-template-class",
"level": "warning",
"since": "3.5.0"
}
},
{
"name": "spring.groovy.template.configuration.cache-templates",
"deprecation": {
"replacement": "spring.groovy.template.cache",
"level": "warning",
"since": "3.5.0"
}
},
{
"name": "spring.groovy.template.configuration.declaration-encoding",
"deprecation": {
"replacement": "spring.groovy.template.declaration-encoding",
"level": "warning",
"since": "3.5.0"
}
},
{
"name": "spring.groovy.template.configuration.expand-empty-elements",
"deprecation": {
"replacement": "spring.groovy.template.expand-empty-elements",
"level": "warning",
"since": "3.5.0"
}
},
{
"name": "spring.groovy.template.configuration.locale",
"deprecation": {
"replacement": "spring.groovy.template.locale",
"level": "warning",
"since": "3.5.0"
}
},
{
"name": "spring.groovy.template.configuration.new-line-string",
"deprecation": {
"replacement": "spring.groovy.template.new-line-string",
"level": "warning",
"since": "3.5.0"
}
},
{
"name": "spring.groovy.template.configuration.resource-loader-path",
"deprecation": {
"replacement": "spring.groovy.template.resource-loader-path",
"level": "warning",
"since": "3.5.0"
}
},
{
"name": "spring.groovy.template.configuration.use-double-quotes",
"deprecation": {
"replacement": "spring.groovy.template.use-double-quotes",
"level": "warning",
"since": "3.5.0"
}
},
{ {
"name": "spring.groovy.template.prefix", "name": "spring.groovy.template.prefix",
"defaultValue": "" "defaultValue": ""

View File

@ -22,8 +22,11 @@ import java.io.Writer;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import groovy.text.markup.BaseTemplate;
import groovy.text.markup.MarkupTemplateEngine; import groovy.text.markup.MarkupTemplateEngine;
import groovy.text.markup.TemplateConfiguration;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@ -185,11 +188,73 @@ class GroovyTemplateAutoConfigurationTests {
} }
@Test @Test
@Deprecated(since = "3.5.0", forRemoval = true)
void customConfiguration() { void customConfiguration() {
registerAndRefreshContext("spring.groovy.template.configuration.auto-indent:true"); registerAndRefreshContext("spring.groovy.template.configuration.auto-indent:true");
assertThat(this.context.getBean(GroovyMarkupConfigurer.class).isAutoIndent()).isTrue(); assertThat(this.context.getBean(GroovyMarkupConfigurer.class).isAutoIndent()).isTrue();
} }
@Test
void enableAutoEscape() {
registerAndRefreshContext("spring.groovy.template.auto-escape:true");
assertThat(this.context.getBean(GroovyMarkupConfigurer.class).isAutoEscape()).isTrue();
}
@Test
void enableAutoIndent() {
registerAndRefreshContext("spring.groovy.template.auto-indent:true");
assertThat(this.context.getBean(GroovyMarkupConfigurer.class).isAutoIndent()).isTrue();
}
@Test
void customAutoIndentString() {
registerAndRefreshContext("spring.groovy.template.auto-indent-string:\\t");
assertThat(this.context.getBean(GroovyMarkupConfigurer.class).getAutoIndentString()).isEqualTo("\\t");
}
@Test
void enableAutoNewLine() {
registerAndRefreshContext("spring.groovy.template.auto-new-line:true");
assertThat(this.context.getBean(GroovyMarkupConfigurer.class).isAutoNewLine()).isTrue();
}
@Test
void customBaseTemplateClass() {
registerAndRefreshContext("spring.groovy.template.base-template-class:" + CustomBaseTemplate.class.getName());
assertThat(this.context.getBean(GroovyMarkupConfigurer.class).getBaseTemplateClass())
.isEqualTo(CustomBaseTemplate.class);
}
@Test
void customDeclarationEncoding() {
registerAndRefreshContext("spring.groovy.template.declaration-encoding:UTF-8");
assertThat(this.context.getBean(GroovyMarkupConfigurer.class).getDeclarationEncoding()).isEqualTo("UTF-8");
}
@Test
void enableExpandEmptyElements() {
registerAndRefreshContext("spring.groovy.template.expand-empty-elements:true");
assertThat(this.context.getBean(GroovyMarkupConfigurer.class).isExpandEmptyElements()).isTrue();
}
@Test
void customLocale() {
registerAndRefreshContext("spring.groovy.template.locale:en_US");
assertThat(this.context.getBean(GroovyMarkupConfigurer.class).getLocale()).isEqualTo(Locale.US);
}
@Test
void customNewLineString() {
registerAndRefreshContext("spring.groovy.template.new-line-string:\\r\\n");
assertThat(this.context.getBean(GroovyMarkupConfigurer.class).getNewLineString()).isEqualTo("\\r\\n");
}
@Test
void enableUseDoubleQuotes() {
registerAndRefreshContext("spring.groovy.template.use-double-quotes:true");
assertThat(this.context.getBean(GroovyMarkupConfigurer.class).isUseDoubleQuotes()).isTrue();
}
private void registerAndRefreshContext(String... env) { private void registerAndRefreshContext(String... env) {
TestPropertyValues.of(env).applyTo(this.context); TestPropertyValues.of(env).applyTo(this.context);
this.context.register(GroovyTemplateAutoConfiguration.class); this.context.register(GroovyTemplateAutoConfiguration.class);
@ -212,4 +277,19 @@ class GroovyTemplateAutoConfigurationTests {
return response; return response;
} }
static class CustomBaseTemplate extends BaseTemplate {
@SuppressWarnings("rawtypes")
CustomBaseTemplate(MarkupTemplateEngine templateEngine, Map model, Map<String, String> modelTypes,
TemplateConfiguration configuration) {
super(templateEngine, model, modelTypes, configuration);
}
@Override
public Object run() {
return null;
}
}
} }