diff --git a/buildSrc/src/main/java/org/springframework/boot/build/context/properties/CheckAggregatedSpringConfigurationMetadata.java b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/CheckAggregatedSpringConfigurationMetadata.java new file mode 100644 index 00000000000..c896edd22c2 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/context/properties/CheckAggregatedSpringConfigurationMetadata.java @@ -0,0 +1,121 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.build.context.properties; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.gradle.api.DefaultTask; +import org.gradle.api.Task; +import org.gradle.api.file.FileCollection; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; +import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.VerificationException; + +/** + * {@link Task} that checks aggregated Spring configuration metadata. + * + * @author Andy Wilkinson + */ +public abstract class CheckAggregatedSpringConfigurationMetadata extends DefaultTask { + + private FileCollection configurationPropertyMetadata; + + @OutputFile + public abstract RegularFileProperty getReportLocation(); + + @InputFiles + @PathSensitive(PathSensitivity.RELATIVE) + public FileCollection getConfigurationPropertyMetadata() { + return this.configurationPropertyMetadata; + } + + public void setConfigurationPropertyMetadata(FileCollection configurationPropertyMetadata) { + this.configurationPropertyMetadata = configurationPropertyMetadata; + } + + @TaskAction + void check() throws IOException { + Report report = createReport(); + File reportFile = getReportLocation().get().getAsFile(); + Files.write(reportFile.toPath(), report, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + if (report.hasProblems()) { + throw new VerificationException( + "Problems found in aggregated Spring configuration metadata. See " + reportFile + " for details."); + } + } + + private Report createReport() { + ConfigurationProperties configurationProperties = ConfigurationProperties + .fromFiles(this.configurationPropertyMetadata); + Set propertyNames = configurationProperties.stream() + .map(ConfigurationProperty::getName) + .collect(Collectors.toSet()); + List missingReplacement = configurationProperties.stream() + .filter(ConfigurationProperty::isDeprecated) + .filter((deprecated) -> { + String replacement = deprecated.getDeprecation().replacement(); + return replacement != null && !propertyNames.contains(replacement); + }) + .toList(); + return new Report(missingReplacement); + } + + private static final class Report implements Iterable { + + private final List propertiesWithMissingReplacement; + + private Report(List propertiesWithMissingReplacement) { + this.propertiesWithMissingReplacement = propertiesWithMissingReplacement; + } + + private boolean hasProblems() { + return !this.propertiesWithMissingReplacement.isEmpty(); + } + + @Override + public Iterator iterator() { + List lines = new ArrayList<>(); + if (this.propertiesWithMissingReplacement.isEmpty()) { + lines.add("No problems found."); + } + else { + lines.add("The following properties have a replacement that does not exist:"); + lines.add(""); + lines.addAll(this.propertiesWithMissingReplacement.stream() + .map((property) -> "\t" + property.getName() + " (replacement " + + property.getDeprecation().replacement() + ")") + .toList()); + } + lines.add(""); + return lines.iterator(); + } + + } + +} diff --git a/documentation/spring-boot-docs/build.gradle b/documentation/spring-boot-docs/build.gradle index 7a8e7d34a58..10006c39674 100644 --- a/documentation/spring-boot-docs/build.gradle +++ b/documentation/spring-boot-docs/build.gradle @@ -370,6 +370,12 @@ def configurationPropertiesMetadataAggregate = aggregates.create("configurationP usage = "configuration-properties-metadata" } +def checkAggregatedSpringConfigurationMetadata = tasks.register("checkAggregatedSpringConfigurationMetadata", org.springframework.boot.build.context.properties.CheckAggregatedSpringConfigurationMetadata) { + configurationPropertyMetadata = configurationPropertiesMetadataAggregate.files + reportLocation = layout.buildDirectory.file("checkAggregatedSpringConfigurationMetadata/report.txt") +} +tasks.named("check") { dependsOn checkAggregatedSpringConfigurationMetadata } + tasks.register("documentConfigurationProperties", org.springframework.boot.build.context.properties.DocumentConfigurationProperties) { configurationPropertyMetadata = configurationPropertiesMetadataAggregate.files deprecated = false diff --git a/module/spring-boot-kafka/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/module/spring-boot-kafka/src/main/resources/META-INF/additional-spring-configuration-metadata.json index a1bb7cbd5ff..133fbdd4b58 100644 --- a/module/spring-boot-kafka/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/module/spring-boot-kafka/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -138,7 +138,7 @@ "name": "spring.kafka.retry.topic.max-delay", "type": "java.time.Duration", "deprecation": { - "replacement": "spring.kafka.retry.topic.backoff.maxDelay", + "replacement": "spring.kafka.retry.topic.backoff.max-delay", "since": "3.4.0" } }, diff --git a/module/spring-boot-micrometer-metrics/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/module/spring-boot-micrometer-metrics/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 92b38e4e788..4b32b1d1fb1 100644 --- a/module/spring-boot-micrometer-metrics/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/module/spring-boot-micrometer-metrics/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -14,7 +14,6 @@ "defaultValue": true, "deprecation": { "level": "error", - "replacement": "management.metrics.enable.process.files", "reason": "Instead, filter 'process.files' metrics." } }, @@ -25,7 +24,6 @@ "defaultValue": true, "deprecation": { "level": "error", - "replacement": "management.metrics.enable.jvm", "reason": "Instead, disable JvmMetricsAutoConfiguration or filter 'jvm' metrics." } }, @@ -36,7 +34,6 @@ "defaultValue": true, "deprecation": { "level": "error", - "replacement": "management.metrics.enable.logback", "reason": "Instead, disable LogbackMetricsAutoConfiguration or filter 'logback' metrics." } }, @@ -366,7 +363,7 @@ "type": "java.lang.String", "deprecation": { "level": "error", - "replacement": "management.dynatrace.metrics.export.device-id" + "replacement": "management.dynatrace.metrics.export.v1.device-id" } }, { @@ -382,7 +379,7 @@ "type": "java.lang.String", "deprecation": { "level": "error", - "replacement": "management.dynatrace.metrics.export.group" + "replacement": "management.dynatrace.metrics.export.v1.group" } }, { @@ -413,7 +410,7 @@ "type": "java.lang.String", "deprecation": { "level": "error", - "replacement": "management.dynatrace.metrics.export.technology-type" + "replacement": "management.dynatrace.metrics.export.v1.technology-type" } }, { @@ -1189,7 +1186,7 @@ "type": "io.micrometer.prometheus.HistogramFlavor", "deprecation": { "level": "error", - "replacement": "management.prometheus.metrics.export.histogram-flavor" + "reason": "No longer supported by the Prometheus client." } }, { @@ -1197,7 +1194,7 @@ "type": "java.lang.String", "deprecation": { "level": "error", - "replacement": "management.prometheus.metrics.export.pushgateway.base-url" + "replacement": "management.prometheus.metrics.export.pushgateway.address" } }, { @@ -1269,7 +1266,7 @@ "type": "java.lang.String", "deprecation": { "level": "error", - "replacement": "management.signalfx.metrics.export.access-token" + "reason": "SignalFX is no longer supported." } }, { @@ -1277,7 +1274,7 @@ "type": "java.lang.Integer", "deprecation": { "level": "error", - "replacement": "management.signalfx.metrics.export.batch-size" + "reason": "SignalFX is no longer supported." } }, { @@ -1285,7 +1282,7 @@ "type": "java.time.Duration", "deprecation": { "level": "error", - "replacement": "management.signalfx.metrics.export.connect-timeout" + "reason": "SignalFX is no longer supported." } }, { @@ -1293,21 +1290,22 @@ "type": "java.lang.Boolean", "deprecation": { "level": "error", - "replacement": "management.signalfx.metrics.export.enabled" + "reason": "SignalFX is no longer supported." } }, { "name": "management.metrics.export.signalfx.num-threads", "type": "java.lang.Integer", "deprecation": { - "level": "error" + "level": "error", + "reason": "SignalFX is no longer supported." } }, { "name": "management.metrics.export.signalfx.published-histogram-type", "deprecation": { "level": "error", - "replacement": "management.signalfx.metrics.export.published-histogram-type" + "reason": "SignalFX is no longer supported." } }, { @@ -1315,7 +1313,7 @@ "type": "java.time.Duration", "deprecation": { "level": "error", - "replacement": "management.signalfx.metrics.export.read-timeout" + "reason": "SignalFX is no longer supported." } }, { @@ -1323,7 +1321,7 @@ "type": "java.lang.String", "deprecation": { "level": "error", - "replacement": "management.signalfx.metrics.export.source" + "reason": "SignalFX is no longer supported." } }, { @@ -1331,7 +1329,7 @@ "type": "java.time.Duration", "deprecation": { "level": "error", - "replacement": "management.signalfx.metrics.export.step" + "reason": "SignalFX is no longer supported." } }, { @@ -1339,7 +1337,7 @@ "type": "java.lang.String", "deprecation": { "level": "error", - "replacement": "management.signalfx.metrics.export.uri" + "reason": "SignalFX is no longer supported." } }, { diff --git a/module/spring-boot-opentelemetry/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/module/spring-boot-opentelemetry/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 8cf59c3ef08..1a698477766 100644 --- a/module/spring-boot-opentelemetry/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/module/spring-boot-opentelemetry/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -4,19 +4,10 @@ { "name": "management.logging.export.otlp.enabled", "type": "java.lang.Boolean", - "defaultValue": true, + "defaultValue": true, "description": "Whether auto-configuration of logging is enabled to export logs over OTLP." - }, - { - "name": "management.otlp.logging", - "type": "org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.OpenTelemetryLoggingExportProperties", - "sourceType": "org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.OpenTelemetryLoggingExportProperties", - "deprecation": { - "replacement": "management.opentelemetry.logging.export.otlp", - "level": "error" - } }, - { + { "name": "management.otlp.logging.compression", "type": "org.springframework.boot.opentelemetry.actuate.autoconfigure.logging.OpenTelemetryLoggingExportProperties$Compression", "description": "Method used to compress the payload.", @@ -48,7 +39,7 @@ "level": "error" } }, - { + { "name": "management.otlp.logging.export.enabled", "deprecation": { "replacement": "management.logging.export.otlp.enabled",