Skip to content

Commit 52951ed

Browse files
committed
Apply checks for manual configuration metadata
This commit adds a 'org.springframework.boot.configuration-metadata' plugin to be used for projects that only define manual metadata. Such project do not need the annotation processor, but do not to check that the structure of the metadata content matches the same rules. Closes gh-47984
1 parent bd2a8ac commit 52951ed

File tree

7 files changed

+611
-187
lines changed

7 files changed

+611
-187
lines changed

buildSrc/build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@ gradlePlugin {
112112
id = "org.springframework.boot.bom"
113113
implementationClass = "org.springframework.boot.build.bom.BomPlugin"
114114
}
115+
configurationMetadataPlugin {
116+
id = "org.springframework.boot.configuration-metadata"
117+
implementationClass = "org.springframework.boot.build.context.properties.ConfigurationMetadataPlugin"
118+
}
115119
configurationPropertiesPlugin {
116120
id = "org.springframework.boot.configuration-properties"
117121
implementationClass = "org.springframework.boot.build.context.properties.ConfigurationPropertiesPlugin"

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

Lines changed: 7 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,7 @@
1818

1919
import java.io.File;
2020
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;
3021

31-
import com.fasterxml.jackson.core.JsonParseException;
32-
import com.fasterxml.jackson.databind.JsonMappingException;
33-
import com.fasterxml.jackson.databind.ObjectMapper;
3422
import org.gradle.api.file.FileTree;
3523
import org.gradle.api.file.RegularFileProperty;
3624
import org.gradle.api.tasks.InputFiles;
@@ -41,6 +29,8 @@
4129
import org.gradle.api.tasks.TaskAction;
4230
import org.gradle.api.tasks.VerificationException;
4331

32+
import org.springframework.boot.build.context.properties.ConfigurationPropertiesAnalyzer.Report;
33+
4434
/**
4535
* {@link SourceTask} that checks additional Spring configuration metadata files.
4636
*
@@ -65,99 +55,16 @@ public FileTree getSource() {
6555
}
6656

6757
@TaskAction
68-
void check() throws JsonParseException, IOException {
69-
Report report = createReport();
58+
void check() throws IOException {
59+
ConfigurationPropertiesAnalyzer analyzer = new ConfigurationPropertiesAnalyzer(getSource().getFiles());
60+
Report report = new Report(this.projectDir);
61+
analyzer.analyzeSort(report);
7062
File reportFile = getReportLocation().get().getAsFile();
71-
Files.write(reportFile.toPath(), report, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
63+
report.write(reportFile);
7264
if (report.hasProblems()) {
7365
throw new VerificationException(
7466
"Problems found in additional Spring configuration metadata. See " + reportFile + " for details.");
7567
}
7668
}
7769

78-
@SuppressWarnings("unchecked")
79-
private Report createReport() throws IOException, JsonParseException, JsonMappingException {
80-
ObjectMapper objectMapper = new ObjectMapper();
81-
Report report = new Report();
82-
for (File file : getSource().getFiles()) {
83-
Analysis analysis = report.analysis(this.projectDir.toPath().relativize(file.toPath()));
84-
Map<String, Object> json = objectMapper.readValue(file, Map.class);
85-
check("groups", json, analysis);
86-
check("properties", json, analysis);
87-
check("hints", json, analysis);
88-
}
89-
return report;
90-
}
91-
92-
@SuppressWarnings("unchecked")
93-
private void check(String key, Map<String, Object> json, Analysis analysis) {
94-
List<Map<String, Object>> groups = (List<Map<String, Object>>) json.getOrDefault(key, Collections.emptyList());
95-
List<String> names = groups.stream().map((group) -> (String) group.get("name")).toList();
96-
List<String> sortedNames = sortedCopy(names);
97-
for (int i = 0; i < names.size(); i++) {
98-
String actual = names.get(i);
99-
String expected = sortedNames.get(i);
100-
if (!actual.equals(expected)) {
101-
analysis.problems.add("Wrong order at $." + key + "[" + i + "].name - expected '" + expected
102-
+ "' but found '" + actual + "'");
103-
}
104-
}
105-
}
106-
107-
private List<String> sortedCopy(Collection<String> original) {
108-
List<String> copy = new ArrayList<>(original);
109-
Collections.sort(copy);
110-
return copy;
111-
}
112-
113-
private static final class Report implements Iterable<String> {
114-
115-
private final List<Analysis> analyses = new ArrayList<>();
116-
117-
private Analysis analysis(Path path) {
118-
Analysis analysis = new Analysis(path);
119-
this.analyses.add(analysis);
120-
return analysis;
121-
}
122-
123-
private boolean hasProblems() {
124-
for (Analysis analysis : this.analyses) {
125-
if (!analysis.problems.isEmpty()) {
126-
return true;
127-
}
128-
}
129-
return false;
130-
}
131-
132-
@Override
133-
public Iterator<String> iterator() {
134-
List<String> lines = new ArrayList<>();
135-
for (Analysis analysis : this.analyses) {
136-
lines.add(analysis.source.toString());
137-
lines.add("");
138-
if (analysis.problems.isEmpty()) {
139-
lines.add("No problems found.");
140-
}
141-
else {
142-
lines.addAll(analysis.problems);
143-
}
144-
lines.add("");
145-
}
146-
return lines.iterator();
147-
}
148-
149-
}
150-
151-
private static final class Analysis {
152-
153-
private final List<String> problems = new ArrayList<>();
154-
155-
private final Path source;
156-
157-
private Analysis(Path source) {
158-
this.source = source;
159-
}
160-
161-
}
162-
16370
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright 2012-present 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.util.List;
22+
23+
import org.gradle.api.DefaultTask;
24+
import org.gradle.api.file.RegularFileProperty;
25+
import org.gradle.api.provider.ListProperty;
26+
import org.gradle.api.provider.Property;
27+
import org.gradle.api.tasks.Input;
28+
import org.gradle.api.tasks.InputFile;
29+
import org.gradle.api.tasks.OutputFile;
30+
import org.gradle.api.tasks.PathSensitive;
31+
import org.gradle.api.tasks.PathSensitivity;
32+
import org.gradle.api.tasks.SourceTask;
33+
import org.gradle.api.tasks.TaskAction;
34+
import org.gradle.api.tasks.VerificationException;
35+
36+
import org.springframework.boot.build.context.properties.ConfigurationPropertiesAnalyzer.Report;
37+
38+
/**
39+
* {@link SourceTask} that checks manual Spring configuration metadata files.
40+
*
41+
* @author Andy Wilkinson
42+
* @author Stephane Nicoll
43+
*/
44+
public abstract class CheckManualSpringConfigurationMetadata extends DefaultTask {
45+
46+
private final File projectDir;
47+
48+
public CheckManualSpringConfigurationMetadata() {
49+
this.projectDir = getProject().getProjectDir();
50+
}
51+
52+
@OutputFile
53+
public abstract RegularFileProperty getReportLocation();
54+
55+
@InputFile
56+
@PathSensitive(PathSensitivity.RELATIVE)
57+
public abstract Property<File> getMetadataLocation();
58+
59+
@Input
60+
public abstract ListProperty<String> getExclusions();
61+
62+
@TaskAction
63+
void check() throws IOException {
64+
ConfigurationPropertiesAnalyzer analyzer = new ConfigurationPropertiesAnalyzer(
65+
List.of(getMetadataLocation().get()));
66+
Report report = new Report(this.projectDir);
67+
analyzer.analyzeSort(report);
68+
analyzer.analyzePropertyDescription(report, getExclusions().get());
69+
File reportFile = getReportLocation().get().getAsFile();
70+
report.write(reportFile);
71+
if (report.hasProblems()) {
72+
throw new VerificationException(
73+
"Problems found in manual Spring configuration metadata. See " + reportFile + " for details.");
74+
}
75+
}
76+
77+
}

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

Lines changed: 11 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,8 @@
1818

1919
import java.io.File;
2020
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.Iterator;
2621
import java.util.List;
27-
import java.util.Map;
2822

29-
import com.fasterxml.jackson.core.JsonParseException;
30-
import com.fasterxml.jackson.databind.JsonMappingException;
31-
import com.fasterxml.jackson.databind.ObjectMapper;
3223
import org.gradle.api.DefaultTask;
3324
import org.gradle.api.file.RegularFileProperty;
3425
import org.gradle.api.provider.ListProperty;
@@ -41,17 +32,19 @@
4132
import org.gradle.api.tasks.TaskAction;
4233
import org.gradle.api.tasks.VerificationException;
4334

35+
import org.springframework.boot.build.context.properties.ConfigurationPropertiesAnalyzer.Report;
36+
4437
/**
4538
* {@link SourceTask} that checks {@code spring-configuration-metadata.json} files.
4639
*
4740
* @author Andy Wilkinson
4841
*/
4942
public abstract class CheckSpringConfigurationMetadata extends DefaultTask {
5043

51-
private final Path projectRoot;
44+
private final File projectRoot;
5245

5346
public CheckSpringConfigurationMetadata() {
54-
this.projectRoot = getProject().getProjectDir().toPath();
47+
this.projectRoot = getProject().getProjectDir();
5548
}
5649

5750
@OutputFile
@@ -65,87 +58,18 @@ public CheckSpringConfigurationMetadata() {
6558
public abstract ListProperty<String> getExclusions();
6659

6760
@TaskAction
68-
void check() throws JsonParseException, IOException {
69-
Report report = createReport();
61+
void check() throws IOException {
62+
Report report = new Report(this.projectRoot);
63+
ConfigurationPropertiesAnalyzer analyzer = new ConfigurationPropertiesAnalyzer(
64+
List.of(getMetadataLocation().get().getAsFile()));
65+
analyzer.analyzePropertyDescription(report, getExclusions().get());
66+
7067
File reportFile = getReportLocation().get().getAsFile();
71-
Files.write(reportFile.toPath(), report, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
68+
report.write(reportFile);
7269
if (report.hasProblems()) {
7370
throw new VerificationException(
7471
"Problems found in Spring configuration metadata. See " + reportFile + " for details.");
7572
}
7673
}
7774

78-
@SuppressWarnings("unchecked")
79-
private Report createReport() throws IOException, JsonParseException, JsonMappingException {
80-
ObjectMapper objectMapper = new ObjectMapper();
81-
File file = getMetadataLocation().get().getAsFile();
82-
Report report = new Report(this.projectRoot.relativize(file.toPath()));
83-
Map<String, Object> json = objectMapper.readValue(file, Map.class);
84-
List<Map<String, Object>> properties = (List<Map<String, Object>>) json.get("properties");
85-
for (Map<String, Object> property : properties) {
86-
String name = (String) property.get("name");
87-
if (!isDeprecated(property) && !isDescribed(property) && !isExcluded(name)) {
88-
report.propertiesWithNoDescription.add(name);
89-
}
90-
}
91-
return report;
92-
}
93-
94-
private boolean isExcluded(String propertyName) {
95-
for (String exclusion : getExclusions().get()) {
96-
if (propertyName.equals(exclusion)) {
97-
return true;
98-
}
99-
if (exclusion.endsWith(".*")) {
100-
if (propertyName.startsWith(exclusion.substring(0, exclusion.length() - 2))) {
101-
return true;
102-
}
103-
}
104-
}
105-
return false;
106-
}
107-
108-
@SuppressWarnings("unchecked")
109-
private boolean isDeprecated(Map<String, Object> property) {
110-
return (Map<String, Object>) property.get("deprecation") != null;
111-
}
112-
113-
private boolean isDescribed(Map<String, Object> property) {
114-
return property.get("description") != null;
115-
}
116-
117-
private static final class Report implements Iterable<String> {
118-
119-
private final List<String> propertiesWithNoDescription = new ArrayList<>();
120-
121-
private final Path source;
122-
123-
private Report(Path source) {
124-
this.source = source;
125-
}
126-
127-
private boolean hasProblems() {
128-
return !this.propertiesWithNoDescription.isEmpty();
129-
}
130-
131-
@Override
132-
public Iterator<String> iterator() {
133-
List<String> lines = new ArrayList<>();
134-
lines.add(this.source.toString());
135-
lines.add("");
136-
if (this.propertiesWithNoDescription.isEmpty()) {
137-
lines.add("No problems found.");
138-
}
139-
else {
140-
lines.add("The following properties have no description:");
141-
lines.add("");
142-
lines.addAll(this.propertiesWithNoDescription.stream().map((line) -> "\t" + line).toList());
143-
}
144-
lines.add("");
145-
return lines.iterator();
146-
147-
}
148-
149-
}
150-
15175
}

0 commit comments

Comments
 (0)