diff --git a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/CompoundRow.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/CompoundRow.java index ff4fef2f39a..091dc4aa88e 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/CompoundRow.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/CompoundRow.java @@ -42,6 +42,10 @@ class CompoundRow extends Row { this.propertyNames.add(property.getDisplayName()); } + boolean isEmpty() { + return this.propertyNames.isEmpty(); + } + @Override void write(Asciidoc asciidoc) { asciidoc.append("|"); diff --git a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationProperty.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationProperty.java index a2623b54010..f99fb254d08 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationProperty.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationProperty.java @@ -22,6 +22,7 @@ import java.util.Map; * A configuration property. * * @author Andy Wilkinson + * @author Phillip Webb */ class ConfigurationProperty { @@ -35,16 +36,20 @@ class ConfigurationProperty { private final boolean deprecated; + private final Deprecation deprecation; + ConfigurationProperty(String name, String type) { - this(name, type, null, null, false); + this(name, type, null, null, false, null); } - ConfigurationProperty(String name, String type, Object defaultValue, String description, boolean deprecated) { + ConfigurationProperty(String name, String type, Object defaultValue, String description, boolean deprecated, + Deprecation deprecation) { this.name = name; this.type = type; this.defaultValue = defaultValue; this.description = description; this.deprecated = deprecated; + this.deprecation = deprecation; } String getName() { @@ -71,18 +76,39 @@ class ConfigurationProperty { return this.deprecated; } + Deprecation getDeprecation() { + return this.deprecation; + } + @Override public String toString() { return "ConfigurationProperty [name=" + this.name + ", type=" + this.type + "]"; } + @SuppressWarnings("unchecked") static ConfigurationProperty fromJsonProperties(Map property) { String name = (String) property.get("name"); String type = (String) property.get("type"); Object defaultValue = property.get("defaultValue"); String description = (String) property.get("description"); boolean deprecated = property.containsKey("deprecated"); - return new ConfigurationProperty(name, type, defaultValue, description, deprecated); + Map deprecation = (Map) property.get("deprecation"); + return new ConfigurationProperty(name, type, defaultValue, description, deprecated, + Deprecation.fromJsonProperties(deprecation)); + } + + record Deprecation(String reason, String replacement, String since) { + + static Deprecation fromJsonProperties(Map property) { + if (property == null) { + return null; + } + String reason = (String) property.get("reason"); + String replacement = (String) property.get("replacement"); + String since = (String) property.get("since"); + return new Deprecation(reason, replacement, since); + } + } } 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 4ce0ccb52ba..7a078c5b28c 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 @@ -22,6 +22,8 @@ import org.gradle.api.DefaultTask; import org.gradle.api.Task; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.FileCollection; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.PathSensitive; @@ -50,12 +52,15 @@ public abstract class DocumentConfigurationProperties extends DefaultTask { this.configurationPropertyMetadata = configurationPropertyMetadata; } + @Input + public abstract Property getDeprecated(); + @OutputDirectory public abstract DirectoryProperty getOutputDir(); @TaskAction void documentConfigurationProperties() throws IOException { - Snippets snippets = new Snippets(this.configurationPropertyMetadata); + Snippets snippets = new Snippets(this.configurationPropertyMetadata, getDeprecated().getOrElse(false)); snippets.add("application-properties.core", "Core Properties", this::corePrefixes); snippets.add("application-properties.cache", "Cache Properties", this::cachePrefixes); snippets.add("application-properties.mail", "Mail Properties", this::mailPrefixes); diff --git a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/SingleRow.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/SingleRow.java index 0eecf57e8ef..5b12efd97d0 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/SingleRow.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/SingleRow.java @@ -19,6 +19,8 @@ package org.springframework.boot.build.context.properties; import java.util.Arrays; import java.util.stream.Collectors; +import org.springframework.boot.build.context.properties.ConfigurationProperty.Deprecation; + /** * Table row containing a single configuration property. * @@ -28,17 +30,21 @@ import java.util.stream.Collectors; */ class SingleRow extends Row { - private final String displayName; - - private final String description; + private final Snippets snippets; private final String defaultValue; + private final ConfigurationProperty property; + SingleRow(Snippet snippet, ConfigurationProperty property) { + this(null, snippet, property); + } + + SingleRow(Snippets snippets, Snippet snippet, ConfigurationProperty property) { super(snippet, property.getName()); - this.displayName = property.getDisplayName(); - this.description = property.getDescription(); + this.snippets = snippets; this.defaultValue = getDefaultValue(property.getDefaultValue()); + this.property = property; } private String getDefaultValue(Object defaultValue) { @@ -57,19 +63,41 @@ class SingleRow extends Row { void write(Asciidoc asciidoc) { asciidoc.append("|"); asciidoc.append("[[" + getAnchor() + "]]"); - asciidoc.appendln("xref:#" + getAnchor() + "[`+", this.displayName, "+`]"); + asciidoc.appendln("xref:#" + getAnchor() + "[`+", this.property.getDisplayName(), "+`]"); writeDescription(asciidoc); writeDefaultValue(asciidoc); } private void writeDescription(Asciidoc builder) { - if (this.description == null || this.description.isEmpty()) { - builder.appendln("|"); + builder.append("|"); + if (this.property.isDeprecated()) { + Deprecation deprecation = this.property.getDeprecation(); + String replacement = (deprecation != null) ? deprecation.replacement() : null; + String reason = (deprecation != null) ? deprecation.reason() : null; + if (replacement != null && !replacement.isEmpty()) { + String xref = (this.snippets != null) ? this.snippets.findXref(deprecation.replacement()) : null; + if (xref != null) { + builder.append("Replaced by xref:" + xref + "[`+" + deprecation.replacement() + "+`]"); + } + else { + builder.append("Replaced by `+" + deprecation.replacement() + "+`"); + } + } + else if (reason != null && !reason.isEmpty()) { + builder.append("+++", clean(reason), "+++"); + } } else { - String cleanedDescription = this.description.replace("|", "\\|").replace("<", "<").replace(">", ">"); - builder.appendln("|+++", cleanedDescription, "+++"); + String description = this.property.getDescription(); + if (description != null && !description.isEmpty()) { + builder.append("+++", clean(description), "+++"); + } } + builder.appendln(); + } + + private String clean(String text) { + return text.replace("|", "\\|").replace("<", "<").replace(">", ">"); } private void writeDefaultValue(Asciidoc builder) { diff --git a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/Snippet.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/Snippet.java index 3e6cf688996..c70088620a1 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/Snippet.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/Snippet.java @@ -71,6 +71,14 @@ class Snippet { return this.title; } + Set getPrefixes() { + return this.prefixes; + } + + Map getOverrides() { + return this.overrides; + } + void forEachPrefix(Consumer action) { this.prefixes.forEach(action); } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/Snippets.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/Snippets.java index 9ffd239be34..b0720cc2d36 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/Snippets.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/Snippets.java @@ -42,8 +42,11 @@ class Snippets { private final List snippets = new ArrayList<>(); - Snippets(FileCollection configurationPropertyMetadata) { + private final boolean deprecated; + + Snippets(FileCollection configurationPropertyMetadata, boolean deprecated) { this.properties = ConfigurationProperties.fromFiles(configurationPropertyMetadata); + this.deprecated = deprecated; } void add(String anchor, String title, Consumer config) { @@ -53,14 +56,14 @@ class Snippets { void writeTo(Path outputDirectory) throws IOException { createDirectory(outputDirectory); Set remaining = this.properties.stream() - .filter((property) -> !property.isDeprecated()) + .filter(this::shouldAdd) .map(ConfigurationProperty::getName) .collect(Collectors.toSet()); for (Snippet snippet : this.snippets) { Set written = writeSnippet(outputDirectory, snippet, remaining); remaining.removeAll(written); } - if (!remaining.isEmpty()) { + if (!this.deprecated && !remaining.isEmpty()) { throw new IllegalStateException( "The following keys were not written to the documentation: " + String.join(", ", remaining)); } @@ -70,18 +73,22 @@ class Snippets { Table table = new Table(); Set added = new HashSet<>(); snippet.forEachOverride((prefix, description) -> { - CompoundRow row = new CompoundRow(snippet, prefix, description); + CompoundRow row = new CompoundRow(snippet, prefix, (!this.deprecated) ? description : ""); remaining.stream().filter((candidate) -> candidate.startsWith(prefix)).forEach((name) -> { - if (added.add(name)) { - row.addProperty(this.properties.get(name)); + ConfigurationProperty property = this.properties.get(name); + if (shouldAdd(property) && added.add(name)) { + row.addProperty(property); } }); - table.addRow(row); + if (!row.isEmpty()) { + table.addRow(row); + } }); snippet.forEachPrefix((prefix) -> { remaining.stream().filter((candidate) -> candidate.startsWith(prefix)).forEach((name) -> { - if (added.add(name)) { - table.addRow(new SingleRow(snippet, this.properties.get(name))); + ConfigurationProperty property = this.properties.get(name); + if (shouldAdd(property) && added.add(name)) { + table.addRow(new SingleRow(this, snippet, property)); } }); }); @@ -90,13 +97,39 @@ class Snippets { return added; } + String findXref(String name) { + ConfigurationProperty property = this.properties.get(name); + if (property == null || property.isDeprecated()) { + return null; + } + for (Snippet snippet : this.snippets) { + for (String prefix : snippet.getOverrides().keySet()) { + if (name.startsWith(prefix)) { + return null; + } + } + for (String prefix : snippet.getPrefixes()) { + if (name.startsWith(prefix)) { + return "appendix:application-properties/index.adoc#%s.%s".formatted(snippet.getAnchor(), name); + } + } + } + return null; + } + + private boolean shouldAdd(ConfigurationProperty property) { + return (property == null || property.isDeprecated() == this.deprecated); + } + private Asciidoc getAsciidoc(Snippet snippet, Table table) { Asciidoc asciidoc = new Asciidoc(); - // We have to prepend 'appendix.' as a section id here, otherwise the - // spring-asciidoctor-extensions:section-id asciidoctor extension complains - asciidoc.appendln("[[appendix." + snippet.getAnchor() + "]]"); - asciidoc.appendln("== ", snippet.getTitle()); - table.write(asciidoc); + if (!table.isEmpty()) { + // We have to prepend 'appendix.' as a section id here, otherwise the + // spring-asciidoctor-extensions:section-id asciidoctor extension complains + asciidoc.appendln("[[appendix." + ((this.deprecated) ? "deprecated-" : "") + snippet.getAnchor() + "]]"); + asciidoc.appendln("== ", ((this.deprecated) ? "Deprecated " : "") + snippet.getTitle()); + table.write(asciidoc); + } return asciidoc; } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/Table.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/Table.java index 71e89442845..31ebd20105a 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/Table.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/Table.java @@ -44,4 +44,8 @@ class Table { asciidoc.appendln("|==="); } + boolean isEmpty() { + return this.rows.isEmpty(); + } + } diff --git a/buildSrc/src/test/java/org/springframework/boot/build/context/properties/SingleRowTests.java b/buildSrc/src/test/java/org/springframework/boot/build/context/properties/SingleRowTests.java index 5c641e7508b..787d79d9d74 100644 --- a/buildSrc/src/test/java/org/springframework/boot/build/context/properties/SingleRowTests.java +++ b/buildSrc/src/test/java/org/springframework/boot/build/context/properties/SingleRowTests.java @@ -35,7 +35,7 @@ class SingleRowTests { @Test void simpleProperty() { ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String", "something", - "This is a description.", false); + "This is a description.", false, null); SingleRow row = new SingleRow(SNIPPET, property); Asciidoc asciidoc = new Asciidoc(); row.write(asciidoc); @@ -46,7 +46,7 @@ class SingleRowTests { @Test void noDefaultValue() { ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String", null, - "This is a description.", false); + "This is a description.", false, null); SingleRow row = new SingleRow(SNIPPET, property); Asciidoc asciidoc = new Asciidoc(); row.write(asciidoc); @@ -57,7 +57,7 @@ class SingleRowTests { @Test void defaultValueWithPipes() { ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String", - "first|second", "This is a description.", false); + "first|second", "This is a description.", false, null); SingleRow row = new SingleRow(SNIPPET, property); Asciidoc asciidoc = new Asciidoc(); row.write(asciidoc); @@ -68,7 +68,7 @@ class SingleRowTests { @Test void defaultValueWithBackslash() { ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String", - "first\\second", "This is a description.", false); + "first\\second", "This is a description.", false, null); SingleRow row = new SingleRow(SNIPPET, property); Asciidoc asciidoc = new Asciidoc(); row.write(asciidoc); @@ -79,7 +79,7 @@ class SingleRowTests { @Test void descriptionWithPipe() { ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", "java.lang.String", null, - "This is a description with a | pipe.", false); + "This is a description with a | pipe.", false, null); SingleRow row = new SingleRow(SNIPPET, property); Asciidoc asciidoc = new Asciidoc(); row.write(asciidoc); @@ -90,7 +90,7 @@ class SingleRowTests { @Test void mapProperty() { ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", - "java.util.Map", null, "This is a description.", false); + "java.util.Map", null, "This is a description.", false, null); SingleRow row = new SingleRow(SNIPPET, property); Asciidoc asciidoc = new Asciidoc(); row.write(asciidoc); @@ -102,7 +102,7 @@ class SingleRowTests { void listProperty() { String[] defaultValue = new String[] { "first", "second", "third" }; ConfigurationProperty property = new ConfigurationProperty("spring.test.prop", - "java.util.List", defaultValue, "This is a description.", false); + "java.util.List", defaultValue, "This is a description.", false, null); SingleRow row = new SingleRow(SNIPPET, property); Asciidoc asciidoc = new Asciidoc(); row.write(asciidoc); diff --git a/buildSrc/src/test/java/org/springframework/boot/build/context/properties/TableTests.java b/buildSrc/src/test/java/org/springframework/boot/build/context/properties/TableTests.java index ee66bd9bb46..233cc138fb6 100644 --- a/buildSrc/src/test/java/org/springframework/boot/build/context/properties/TableTests.java +++ b/buildSrc/src/test/java/org/springframework/boot/build/context/properties/TableTests.java @@ -36,9 +36,9 @@ class TableTests { void simpleTable() { Table table = new Table(); table.addRow(new SingleRow(SNIPPET, new ConfigurationProperty("spring.test.prop", "java.lang.String", - "something", "This is a description.", false))); + "something", "This is a description.", false, null))); table.addRow(new SingleRow(SNIPPET, new ConfigurationProperty("spring.test.other", "java.lang.String", - "other value", "This is another description.", false))); + "other value", "This is another description.", false, null))); Asciidoc asciidoc = new Asciidoc(); table.write(asciidoc); // @formatter:off diff --git a/spring-boot-project/spring-boot-docs/build.gradle b/spring-boot-project/spring-boot-docs/build.gradle index 2bbd49dd05f..5911f130752 100644 --- a/spring-boot-project/spring-boot-docs/build.gradle +++ b/spring-boot-project/spring-boot-docs/build.gradle @@ -273,9 +273,16 @@ def configurationPropertiesMetadataAggregate = aggregates.create("configurationP tasks.register("documentConfigurationProperties", org.springframework.boot.build.context.properties.DocumentConfigurationProperties) { configurationPropertyMetadata = configurationPropertiesMetadataAggregate.files + deprecated = false outputDir = layout.buildDirectory.dir("generated/docs/application-properties") } +tasks.register("documentDeprecatedConfigurationProperties", org.springframework.boot.build.context.properties.DocumentConfigurationProperties) { + configurationPropertyMetadata = configurationPropertiesMetadataAggregate.files + deprecated = true + outputDir = layout.buildDirectory.dir("generated/docs/deprecated-application-properties") +} + tasks.register("documentDevtoolsPropertyDefaults", org.springframework.boot.build.devtools.DocumentDevtoolsPropertyDefaults) {} tasks.register("runRemoteSpringApplicationExample", org.springframework.boot.build.docs.ApplicationRunner) { @@ -394,6 +401,9 @@ antoraContributions { from(documentConfigurationProperties) { into "modules/appendix/partials/configuration-properties" } + from(documentDeprecatedConfigurationProperties) { + into "modules/appendix/partials/deprecated-configuration-properties" + } from(tasks.getByName("generateAntoraYml")) { into "modules" } diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/pages/deprecated-application-properties/index.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/pages/deprecated-application-properties/index.adoc new file mode 100644 index 00000000000..c0aa05ec2ad --- /dev/null +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/pages/deprecated-application-properties/index.adoc @@ -0,0 +1,73 @@ +[appendix] +[[appendix.deprecated-application-properties]] += Deprecated Application Properties + +The following deprecated properties can be specified inside your `application.properties` file, inside your `application.yaml` file, or as command line switches. +Support for these properties will be removed in a future release and should you should migrate away from them. + +[TIP] +==== +Spring Boot includes a useful `spring-boot-properties-migrator` tool to help you migrate away from deprecated properties. +To use the property migrator tool, add the following dependency to your project: + +[tabs] +====== +Maven:: ++ +[source,xml] +---- + + org.springframework.boot + spring-boot-properties-migrator + runtime + +---- +Gradle:: ++ +[source,gradle] +---- +runtimeOnly("org.springframework.boot:spring-boot-properties-migrator") +---- +====== + +Once added as a dependency to your project, the tool will not only analyze your application’s environment and print diagnostics at startup, but also temporarily migrate properties at runtime for you. + +Remember to remove the dependency when your migration is complete. +==== + + +include::partial$deprecated-configuration-properties/actuator.adoc[] + +include::partial$deprecated-configuration-properties/cache.adoc[] + +include::partial$deprecated-configuration-properties/core.adoc[] + +include::partial$deprecated-configuration-properties/data-migration.adoc[] + +include::partial$deprecated-configuration-properties/data.adoc[] + +include::partial$deprecated-configuration-properties/devtools.adoc[] + +include::partial$deprecated-configuration-properties/docker-compose.adoc[] + +include::partial$deprecated-configuration-properties/integration.adoc[] + +include::partial$deprecated-configuration-properties/json.adoc[] + +include::partial$deprecated-configuration-properties/mail.adoc[] + +include::partial$deprecated-configuration-properties/rsocket.adoc[] + +include::partial$deprecated-configuration-properties/security.adoc[] + +include::partial$deprecated-configuration-properties/server.adoc[] + +include::partial$deprecated-configuration-properties/templating.adoc[] + +include::partial$deprecated-configuration-properties/testcontainers.adoc[] + +include::partial$deprecated-configuration-properties/testing.adoc[] + +include::partial$deprecated-configuration-properties/transaction.adoc[] + +include::partial$deprecated-configuration-properties/web.adoc[] diff --git a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/partials/nav-appendix.adoc b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/partials/nav-appendix.adoc index 2d1c53173d3..6dfc446d0ad 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/partials/nav-appendix.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/antora/modules/appendix/partials/nav-appendix.adoc @@ -2,6 +2,8 @@ ** xref:appendix:application-properties/index.adoc[] +** xref:appendix:deprecated-application-properties/index.adoc[] + ** xref:appendix:auto-configuration-classes/index.adoc[] include::appendix:partial$auto-configuration-classes/nav.adoc[]