From be97e79ad6f5f3711b9e3b22ddb84029a94832ce Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 17 Mar 2025 15:14:26 +0000 Subject: [PATCH] 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 --- .../DocumentConfigurationProperties.java | 1 - .../GroovyTemplateAutoConfiguration.java | 25 +++- .../template/GroovyTemplateProperties.java | 135 ++++++++++++++++++ ...itional-spring-configuration-metadata.json | 93 ++++++++++++ .../GroovyTemplateAutoConfigurationTests.java | 80 +++++++++++ 5 files changed, 328 insertions(+), 6 deletions(-) diff --git a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java index 164350f408..a249b4d921 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/DocumentConfigurationProperties.java @@ -191,7 +191,6 @@ public abstract class DocumentConfigurationProperties extends DefaultTask { prefix.accept("spring.groovy"); prefix.accept("spring.mustache"); prefix.accept("spring.thymeleaf"); - prefix.accept("spring.groovy.template.configuration", "See GroovyMarkupConfigurer"); } private void serverPrefixes(Config prefix) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/groovy/template/GroovyTemplateAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/groovy/template/GroovyTemplateAutoConfiguration.java index a8bd8c88ee..cd367c140c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/groovy/template/GroovyTemplateAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/groovy/template/GroovyTemplateAutoConfiguration.java @@ -33,12 +33,15 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; import org.springframework.boot.autoconfigure.template.TemplateLocation; 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.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.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.core.env.Environment; import org.springframework.core.log.LogMessage; import org.springframework.web.servlet.view.UrlBasedViewResolver; import org.springframework.web.servlet.view.groovy.GroovyMarkupConfig; @@ -109,11 +112,23 @@ public class GroovyTemplateAutoConfiguration { @Bean @ConditionalOnMissingBean(GroovyMarkupConfig.class) - @ConfigurationProperties("spring.groovy.template.configuration") - public GroovyMarkupConfigurer groovyMarkupConfigurer(ObjectProvider templateEngine) { + GroovyMarkupConfigurer groovyMarkupConfigurer(ObjectProvider templateEngine, + Environment environment) { GroovyMarkupConfigurer configurer = new GroovyMarkupConfigurer(); - configurer.setResourceLoaderPath(this.properties.getResourceLoaderPath()); - configurer.setCacheTemplates(this.properties.isCache()); + PropertyMapper map = PropertyMapper.get(); + 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); return configurer; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/groovy/template/GroovyTemplateProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/groovy/template/GroovyTemplateProperties.java index 85c694d3c9..b43497ca41 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/groovy/template/GroovyTemplateProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/groovy/template/GroovyTemplateProperties.java @@ -16,6 +16,10 @@ 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.context.properties.ConfigurationProperties; @@ -38,16 +42,139 @@ public class GroovyTemplateProperties extends AbstractTemplateViewResolverProper 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 baseTemplateClass = BaseTemplate.class; + + /** + * Encoding used to write the declaration heading. + */ + private String declarationEncoding; + + /** + * Whether elements without a body should be written expanded (<br></br>) + * or not (<br/>). + */ + 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. */ private String resourceLoaderPath = DEFAULT_RESOURCE_LOADER_PATH; + /** + * Whether attributes should use double quotes. + */ + private boolean useDoubleQuotes; + public GroovyTemplateProperties() { super(DEFAULT_PREFIX, DEFAULT_SUFFIX); 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 getBaseTemplateClass() { + return this.baseTemplateClass; + } + + public void setBaseTemplateClass(Class 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() { return this.resourceLoaderPath; } @@ -56,4 +183,12 @@ public class GroovyTemplateProperties extends AbstractTemplateViewResolverProper this.resourceLoaderPath = resourceLoaderPath; } + public boolean isUseDoubleQuotes() { + return this.useDoubleQuotes; + } + + public void setUseDoubleQuotes(boolean useDoubleQuotes) { + this.useDoubleQuotes = useDoubleQuotes; + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index da4f173eae..387fe2b964 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -1511,6 +1511,99 @@ "name": "spring.graphql.schema.locations", "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", "defaultValue": "" diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/groovy/template/GroovyTemplateAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/groovy/template/GroovyTemplateAutoConfigurationTests.java index d2eba9d5fb..885b950f46 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/groovy/template/GroovyTemplateAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/groovy/template/GroovyTemplateAutoConfigurationTests.java @@ -22,8 +22,11 @@ import java.io.Writer; import java.util.Collections; import java.util.HashMap; import java.util.Locale; +import java.util.Map; +import groovy.text.markup.BaseTemplate; import groovy.text.markup.MarkupTemplateEngine; +import groovy.text.markup.TemplateConfiguration; import jakarta.servlet.http.HttpServletRequest; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -185,11 +188,73 @@ class GroovyTemplateAutoConfigurationTests { } @Test + @Deprecated(since = "3.5.0", forRemoval = true) void customConfiguration() { registerAndRefreshContext("spring.groovy.template.configuration.auto-indent:true"); 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) { TestPropertyValues.of(env).applyTo(this.context); this.context.register(GroovyTemplateAutoConfiguration.class); @@ -212,4 +277,19 @@ class GroovyTemplateAutoConfigurationTests { return response; } + static class CustomBaseTemplate extends BaseTemplate { + + @SuppressWarnings("rawtypes") + CustomBaseTemplate(MarkupTemplateEngine templateEngine, Map model, Map modelTypes, + TemplateConfiguration configuration) { + super(templateEngine, model, modelTypes, configuration); + } + + @Override + public Object run() { + return null; + } + + } + }