Skip to content

Commit a1443d1

Browse files
committed
Enforce ordering in additional-spring-configuration-metadata.json files
Closes gh-31575
1 parent 75359a2 commit a1443d1

File tree

3 files changed

+197
-5
lines changed

3 files changed

+197
-5
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/*
2+
* Copyright 2012-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.build.context.properties;
18+
19+
import java.io.File;
20+
import java.io.IOException;
21+
import java.nio.file.Files;
22+
import java.nio.file.Path;
23+
import java.nio.file.StandardOpenOption;
24+
import java.util.ArrayList;
25+
import java.util.Collection;
26+
import java.util.Collections;
27+
import java.util.Iterator;
28+
import java.util.List;
29+
import java.util.Map;
30+
import java.util.stream.Collectors;
31+
32+
import com.fasterxml.jackson.core.JsonParseException;
33+
import com.fasterxml.jackson.databind.JsonMappingException;
34+
import com.fasterxml.jackson.databind.ObjectMapper;
35+
import org.gradle.api.GradleException;
36+
import org.gradle.api.file.FileTree;
37+
import org.gradle.api.file.RegularFileProperty;
38+
import org.gradle.api.tasks.InputFiles;
39+
import org.gradle.api.tasks.OutputFile;
40+
import org.gradle.api.tasks.PathSensitive;
41+
import org.gradle.api.tasks.PathSensitivity;
42+
import org.gradle.api.tasks.SourceTask;
43+
import org.gradle.api.tasks.TaskAction;
44+
45+
/**
46+
* {@link SourceTask} that checks additional Spring configuration metadata files.
47+
*
48+
* @author Andy Wilkinson
49+
*/
50+
public class CheckAdditionalSpringConfigurationMetadata extends SourceTask {
51+
52+
private final RegularFileProperty reportLocation;
53+
54+
public CheckAdditionalSpringConfigurationMetadata() {
55+
this.reportLocation = getProject().getObjects().fileProperty();
56+
}
57+
58+
@OutputFile
59+
public RegularFileProperty getReportLocation() {
60+
return this.reportLocation;
61+
}
62+
63+
@Override
64+
@InputFiles
65+
@PathSensitive(PathSensitivity.RELATIVE)
66+
public FileTree getSource() {
67+
return super.getSource();
68+
}
69+
70+
@TaskAction
71+
void check() throws JsonParseException, IOException {
72+
Report report = createReport();
73+
File reportFile = getReportLocation().get().getAsFile();
74+
Files.write(reportFile.toPath(), report, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
75+
if (report.hasProblems()) {
76+
throw new GradleException(
77+
"Problems found in additional Spring configuration metadata. See " + reportFile + " for details.");
78+
}
79+
}
80+
81+
@SuppressWarnings("unchecked")
82+
private Report createReport() throws IOException, JsonParseException, JsonMappingException {
83+
ObjectMapper objectMapper = new ObjectMapper();
84+
Report report = new Report();
85+
for (File file : getSource().getFiles()) {
86+
Analysis analysis = report.analysis(getProject().getProjectDir().toPath().relativize(file.toPath()));
87+
Map<String, Object> json = objectMapper.readValue(file, Map.class);
88+
check("groups", json, analysis);
89+
check("properties", json, analysis);
90+
check("hints", json, analysis);
91+
}
92+
return report;
93+
}
94+
95+
@SuppressWarnings("unchecked")
96+
private void check(String key, Map<String, Object> json, Analysis analysis) {
97+
List<Map<String, Object>> groups = (List<Map<String, Object>>) json.get(key);
98+
List<String> names = groups.stream().map((group) -> (String) group.get("name")).collect(Collectors.toList());
99+
List<String> sortedNames = sortedCopy(names);
100+
for (int i = 0; i < names.size(); i++) {
101+
String actual = names.get(i);
102+
String expected = sortedNames.get(i);
103+
if (!actual.equals(expected)) {
104+
analysis.problems.add("Wrong order at $." + key + "[" + i + "].name - expected '" + expected
105+
+ "' but found '" + actual + "'");
106+
}
107+
}
108+
}
109+
110+
private List<String> sortedCopy(Collection<String> original) {
111+
List<String> copy = new ArrayList<>(original);
112+
Collections.sort(copy);
113+
return copy;
114+
}
115+
116+
private static final class Report implements Iterable<String> {
117+
118+
private final List<Analysis> analyses = new ArrayList<>();
119+
120+
private Analysis analysis(Path path) {
121+
Analysis analysis = new Analysis(path);
122+
this.analyses.add(analysis);
123+
return analysis;
124+
}
125+
126+
private boolean hasProblems() {
127+
for (Analysis analysis : this.analyses) {
128+
if (!analysis.problems.isEmpty()) {
129+
return true;
130+
}
131+
}
132+
return false;
133+
}
134+
135+
@Override
136+
public Iterator<String> iterator() {
137+
List<String> lines = new ArrayList<>();
138+
for (Analysis analysis : this.analyses) {
139+
lines.add(analysis.source.toString());
140+
lines.add("");
141+
if (analysis.problems.isEmpty()) {
142+
lines.add("No problems found.");
143+
}
144+
else {
145+
lines.addAll(analysis.problems);
146+
}
147+
lines.add("");
148+
}
149+
return lines.iterator();
150+
}
151+
152+
}
153+
154+
private static final class Analysis {
155+
156+
private final List<String> problems = new ArrayList<>();
157+
158+
private final Path source;
159+
160+
private Analysis(Path source) {
161+
this.source = source;
162+
}
163+
164+
}
165+
166+
}

buildSrc/src/main/java/org/springframework/boot/build/context/properties/ConfigurationPropertiesPlugin.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,7 +29,9 @@
2929
import org.gradle.api.plugins.JavaPluginConvention;
3030
import org.gradle.api.tasks.PathSensitivity;
3131
import org.gradle.api.tasks.SourceSet;
32+
import org.gradle.api.tasks.TaskProvider;
3233
import org.gradle.api.tasks.compile.JavaCompile;
34+
import org.gradle.language.base.plugins.LifecycleBasePlugin;
3335

3436
import org.springframework.util.StringUtils;
3537

@@ -43,6 +45,8 @@
4345
* argument.
4446
* <li>Adding the outputs of the processResources task as inputs of the compileJava task
4547
* to ensure that the additional metadata is available when the annotation processor runs.
48+
* <li>Registering a {@link CheckAdditionalSpringConfigurationMetadata} task and
49+
* configuring the {@code check} task to depend upon it.
4650
* <li>Defining an artifact for the resulting configuration property metadata so that it
4751
* can be consumed by downstream projects.
4852
* </ul>
@@ -57,11 +61,17 @@ public class ConfigurationPropertiesPlugin implements Plugin<Project> {
5761
*/
5862
public static final String CONFIGURATION_PROPERTIES_METADATA_CONFIGURATION_NAME = "configurationPropertiesMetadata";
5963

64+
/**
65+
* Name of the {@link CheckAdditionalSpringConfigurationMetadata} task.
66+
*/
67+
public static final String CHECK_ADDITIONAL_SPRING_CONFIGURATION_METADATA_TASK_NAME = "checkAdditionalSpringConfigurationMetadata";
68+
6069
@Override
6170
public void apply(Project project) {
6271
project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> {
6372
addConfigurationProcessorDependency(project);
6473
configureAdditionalMetadataLocationsCompilerArgument(project);
74+
registerCheckAdditionalMetadataTask(project);
6575
addMetadataArtifact(project);
6676
});
6777
}
@@ -98,4 +108,20 @@ private void configureAdditionalMetadataLocationsCompilerArgument(Project projec
98108
.stream().map(project.getRootProject()::relativePath).collect(Collectors.toSet())));
99109
}
100110

111+
private void registerCheckAdditionalMetadataTask(Project project) {
112+
TaskProvider<CheckAdditionalSpringConfigurationMetadata> checkConfigurationMetadata = project.getTasks()
113+
.register(CHECK_ADDITIONAL_SPRING_CONFIGURATION_METADATA_TASK_NAME,
114+
CheckAdditionalSpringConfigurationMetadata.class);
115+
checkConfigurationMetadata.configure((check) -> {
116+
SourceSet mainSourceSet = project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets()
117+
.getByName(SourceSet.MAIN_SOURCE_SET_NAME);
118+
check.setSource(mainSourceSet.getResources());
119+
check.include("META-INF/additional-spring-configuration-metadata.json");
120+
check.getReportLocation().set(project.getLayout().getBuildDirectory()
121+
.file("reports/additional-spring-configuration-metadata/check.txt"));
122+
});
123+
project.getTasks().named(LifecycleBasePlugin.CHECK_TASK_NAME)
124+
.configure((check) -> check.dependsOn(checkConfigurationMetadata));
125+
}
126+
101127
}

spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -492,16 +492,16 @@
492492
"name": "spring.data.cassandra.connection.init-query-timeout",
493493
"defaultValue": "5s"
494494
},
495-
{
496-
"name": "spring.data.cassandra.controlconnection.timeout",
497-
"defaultValue": "5s"
498-
},
499495
{
500496
"name": "spring.data.cassandra.contact-points",
501497
"defaultValue": [
502498
"127.0.0.1:9042"
503499
]
504500
},
501+
{
502+
"name": "spring.data.cassandra.controlconnection.timeout",
503+
"defaultValue": "5s"
504+
},
505505
{
506506
"name": "spring.data.cassandra.jmx-enabled",
507507
"type": "java.lang.Boolean",

0 commit comments

Comments
 (0)