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; + } + + } + }