Skip to content

Commit 4cae2c9

Browse files
committedDec 28, 2018
Polish "Avoid NPE when replacement property does not exist"
Closes spring-projectsgh-15394
1 parent a1b71ef commit 4cae2c9

File tree

4 files changed

+120
-107
lines changed

4 files changed

+120
-107
lines changed
 

‎spring-boot-project/spring-boot-properties-migrator/src/main/java/org/springframework/boot/context/properties/migrator/PropertiesMigrationReport.java

+15-41
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,13 @@
1717
package org.springframework.boot.context.properties.migrator;
1818

1919
import java.util.ArrayList;
20-
import java.util.Collections;
2120
import java.util.LinkedHashMap;
2221
import java.util.List;
2322
import java.util.Map;
2423
import java.util.function.Function;
2524
import java.util.stream.Collectors;
2625

2726
import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty;
28-
import org.springframework.boot.configurationmetadata.Deprecation;
29-
import org.springframework.util.StringUtils;
3027

3128
/**
3229
* Provides a properties migration report.
@@ -51,8 +48,7 @@ public String getWarningReport() {
5148
StringBuilder report = new StringBuilder();
5249
report.append(String.format("%nThe use of configuration keys that have been "
5350
+ "renamed was found in the environment:%n%n"));
54-
append(report, content, (metadata) -> "Replacement: "
55-
+ metadata.getDeprecation().getReplacement());
51+
append(report, content);
5652
report.append(String.format("%n"));
5753
report.append("Each configuration key has been temporarily mapped to its "
5854
+ "replacement for your convenience. To silence this warning, please "
@@ -75,27 +71,14 @@ public String getErrorReport() {
7571
StringBuilder report = new StringBuilder();
7672
report.append(String.format("%nThe use of configuration keys that are no longer "
7773
+ "supported was found in the environment:%n%n"));
78-
append(report, content, this::determineReason);
74+
append(report, content);
7975
report.append(String.format("%n"));
8076
report.append("Please refer to the migration guide or reference guide for "
8177
+ "potential alternatives.");
8278
report.append(String.format("%n"));
8379
return report.toString();
8480
}
8581

86-
private String determineReason(ConfigurationMetadataProperty metadata) {
87-
Deprecation deprecation = metadata.getDeprecation();
88-
if (StringUtils.hasText(deprecation.getShortReason())) {
89-
return "Reason: " + deprecation.getShortReason();
90-
}
91-
if (StringUtils.hasText(deprecation.getReplacement())) {
92-
return String.format(
93-
"Reason: Replacement key '%s' uses an incompatible " + "target type",
94-
deprecation.getReplacement());
95-
}
96-
return "Reason: none";
97-
}
98-
9982
private Map<String, List<PropertyMigration>> getContent(
10083
Function<LegacyProperties, List<PropertyMigration>> extractor) {
10184
return this.content.entrySet().stream()
@@ -105,8 +88,7 @@ private Map<String, List<PropertyMigration>> getContent(
10588
}
10689

10790
private void append(StringBuilder report,
108-
Map<String, List<PropertyMigration>> content,
109-
Function<ConfigurationMetadataProperty, String> deprecationMessage) {
91+
Map<String, List<PropertyMigration>> content) {
11092
content.forEach((name, properties) -> {
11193
report.append(String.format("Property source '%s':%n", name));
11294
properties.sort(PropertyMigration.COMPARATOR);
@@ -117,8 +99,7 @@ private void append(StringBuilder report,
11799
report.append(
118100
String.format("\t\tLine: %d%n", property.getLineNumber()));
119101
}
120-
report.append(
121-
String.format("\t\t%s%n", deprecationMessage.apply(metadata)));
102+
report.append(String.format("\t\t%s%n", property.determineReason()));
122103
});
123104
report.append(String.format("%n"));
124105
});
@@ -127,36 +108,29 @@ private void append(StringBuilder report,
127108
/**
128109
* Register a new property source.
129110
* @param name the name of the property source
130-
* @param renamed the properties that were renamed
131-
* @param unsupported the properties that are no longer supported
111+
* @param properties the {@link PropertyMigration} instances
132112
*/
133-
void add(String name, List<PropertyMigration> renamed,
134-
List<PropertyMigration> unsupported) {
135-
this.content.put(name, new LegacyProperties(renamed, unsupported));
113+
void add(String name, List<PropertyMigration> properties) {
114+
this.content.put(name, new LegacyProperties(properties));
136115
}
137116

138117
private static class LegacyProperties {
139118

140-
private final List<PropertyMigration> renamed;
141-
142-
private final List<PropertyMigration> unsupported;
143-
144-
LegacyProperties(List<PropertyMigration> renamed,
145-
List<PropertyMigration> unsupported) {
146-
this.renamed = asNewList(renamed);
147-
this.unsupported = asNewList(unsupported);
148-
}
119+
private final List<PropertyMigration> properties;
149120

150-
private <T> List<T> asNewList(List<T> source) {
151-
return (source != null) ? new ArrayList<>(source) : Collections.emptyList();
121+
LegacyProperties(List<PropertyMigration> properties) {
122+
this.properties = new ArrayList<>(properties);
152123
}
153124

154125
public List<PropertyMigration> getRenamed() {
155-
return this.renamed;
126+
return this.properties.stream().filter(PropertyMigration::isCompatibleType)
127+
.collect(Collectors.toList());
156128
}
157129

158130
public List<PropertyMigration> getUnsupported() {
159-
return this.unsupported;
131+
return this.properties.stream()
132+
.filter((property) -> !property.isCompatibleType())
133+
.collect(Collectors.toList());
160134
}
161135

162136
}

‎spring-boot-project/spring-boot-properties-migrator/src/main/java/org/springframework/boot/context/properties/migrator/PropertiesMigrationReporter.java

+27-58
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616

1717
package org.springframework.boot.context.properties.migrator;
1818

19-
import java.time.Duration;
20-
import java.util.ArrayList;
2119
import java.util.Collections;
2220
import java.util.LinkedHashMap;
2321
import java.util.List;
@@ -83,11 +81,9 @@ public PropertiesMigrationReport getReport() {
8381
private PropertySource<?> mapPropertiesWithReplacement(
8482
PropertiesMigrationReport report, String name,
8583
List<PropertyMigration> properties) {
86-
List<PropertyMigration> renamed = new ArrayList<>();
87-
List<PropertyMigration> unsupported = new ArrayList<>();
88-
properties.forEach((property) -> (isRenamed(property) ? renamed : unsupported)
89-
.add(property));
90-
report.add(name, renamed, unsupported);
84+
report.add(name, properties);
85+
List<PropertyMigration> renamed = properties.stream()
86+
.filter(PropertyMigration::isCompatibleType).collect(Collectors.toList());
9187
if (renamed.isEmpty()) {
9288
return null;
9389
}
@@ -102,55 +98,6 @@ private PropertySource<?> mapPropertiesWithReplacement(
10298
return new OriginTrackedMapPropertySource(target, content);
10399
}
104100

105-
private boolean isRenamed(PropertyMigration property) {
106-
ConfigurationMetadataProperty metadata = property.getMetadata();
107-
String replacementId = metadata.getDeprecation().getReplacement();
108-
if (StringUtils.hasText(replacementId)) {
109-
ConfigurationMetadataProperty replacement = this.allProperties
110-
.get(replacementId);
111-
if (replacement != null) {
112-
return isCompatibleType(metadata.getType(), replacement.getType());
113-
}
114-
return isCompatibleType(metadata.getType(),
115-
detectMapValueReplacementType(replacementId));
116-
}
117-
return false;
118-
}
119-
120-
private boolean isCompatibleType(String currentType, String replacementType) {
121-
if (replacementType == null || currentType == null) {
122-
return false;
123-
}
124-
if (replacementType.equals(currentType)) {
125-
return true;
126-
}
127-
if (replacementType.equals(Duration.class.getName())
128-
&& (currentType.equals(Long.class.getName())
129-
|| currentType.equals(Integer.class.getName()))) {
130-
return true;
131-
}
132-
return false;
133-
}
134-
135-
private String detectMapValueReplacementType(String fullId) {
136-
int lastDot = fullId.lastIndexOf('.');
137-
if (lastDot != -1) {
138-
ConfigurationMetadataProperty property = this.allProperties
139-
.get(fullId.substring(0, lastDot));
140-
if (property == null) {
141-
return null;
142-
}
143-
String type = property.getType();
144-
if (type != null && type.startsWith(Map.class.getName())) {
145-
int lastComma = type.lastIndexOf(',');
146-
if (lastComma != -1) {
147-
return type.substring(lastComma + 1, type.length() - 1).trim();
148-
}
149-
}
150-
}
151-
return null;
152-
}
153-
154101
private Map<String, List<PropertyMigration>> getMatchingProperties(
155102
Predicate<ConfigurationMetadataProperty> filter) {
156103
MultiValueMap<String, PropertyMigration> result = new LinkedMultiValueMap<>();
@@ -162,14 +109,36 @@ private Map<String, List<PropertyMigration>> getMatchingProperties(
162109
.getConfigurationProperty(
163110
ConfigurationPropertyName.of(metadata.getId()));
164111
if (configurationProperty != null) {
165-
result.add(name,
166-
new PropertyMigration(metadata, configurationProperty));
112+
result.add(name, new PropertyMigration(configurationProperty,
113+
metadata, determineReplacementMetadata(metadata)));
167114
}
168115
});
169116
});
170117
return result;
171118
}
172119

120+
private ConfigurationMetadataProperty determineReplacementMetadata(
121+
ConfigurationMetadataProperty metadata) {
122+
String replacementId = metadata.getDeprecation().getReplacement();
123+
if (StringUtils.hasText(replacementId)) {
124+
ConfigurationMetadataProperty replacement = this.allProperties
125+
.get(replacementId);
126+
if (replacement != null) {
127+
return replacement;
128+
}
129+
return detectMapValueReplacement(replacementId);
130+
}
131+
return null;
132+
}
133+
134+
private ConfigurationMetadataProperty detectMapValueReplacement(String fullId) {
135+
int lastDot = fullId.lastIndexOf('.');
136+
if (lastDot != -1) {
137+
return this.allProperties.get(fullId.substring(0, lastDot));
138+
}
139+
return null;
140+
}
141+
173142
private Predicate<ConfigurationMetadataProperty> deprecatedFilter() {
174143
return (property) -> property.getDeprecation() != null
175144
&& property.getDeprecation().getLevel() == Deprecation.Level.ERROR;

‎spring-boot-project/spring-boot-properties-migrator/src/main/java/org/springframework/boot/context/properties/migrator/PropertyMigration.java

+77-7
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,16 @@
1616

1717
package org.springframework.boot.context.properties.migrator;
1818

19+
import java.time.Duration;
1920
import java.util.Comparator;
21+
import java.util.Map;
2022

2123
import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty;
24+
import org.springframework.boot.configurationmetadata.Deprecation;
2225
import org.springframework.boot.context.properties.source.ConfigurationProperty;
2326
import org.springframework.boot.origin.Origin;
2427
import org.springframework.boot.origin.TextResourceOrigin;
28+
import org.springframework.util.StringUtils;
2529

2630
/**
2731
* Description of a property migration.
@@ -33,17 +37,24 @@ class PropertyMigration {
3337
public static final Comparator<PropertyMigration> COMPARATOR = Comparator
3438
.comparing((property) -> property.getMetadata().getId());
3539

36-
private final ConfigurationMetadataProperty metadata;
37-
3840
private final ConfigurationProperty property;
3941

4042
private final Integer lineNumber;
4143

42-
PropertyMigration(ConfigurationMetadataProperty metadata,
43-
ConfigurationProperty property) {
44-
this.metadata = metadata;
44+
private final ConfigurationMetadataProperty metadata;
45+
46+
private final ConfigurationMetadataProperty replacementMetadata;
47+
48+
private final boolean compatibleType;
49+
50+
PropertyMigration(ConfigurationProperty property,
51+
ConfigurationMetadataProperty metadata,
52+
ConfigurationMetadataProperty replacementMetadata) {
4553
this.property = property;
4654
this.lineNumber = determineLineNumber(property);
55+
this.metadata = metadata;
56+
this.replacementMetadata = replacementMetadata;
57+
this.compatibleType = determineCompatibleType(metadata, replacementMetadata);
4758
}
4859

4960
private static Integer determineLineNumber(ConfigurationProperty property) {
@@ -57,8 +68,37 @@ private static Integer determineLineNumber(ConfigurationProperty property) {
5768
return null;
5869
}
5970

60-
public ConfigurationMetadataProperty getMetadata() {
61-
return this.metadata;
71+
private static boolean determineCompatibleType(ConfigurationMetadataProperty metadata,
72+
ConfigurationMetadataProperty replacementMetadata) {
73+
String currentType = metadata.getType();
74+
String replacementType = determineReplacementType(replacementMetadata);
75+
if (replacementType == null || currentType == null) {
76+
return false;
77+
}
78+
if (replacementType.equals(currentType)) {
79+
return true;
80+
}
81+
if (replacementType.equals(Duration.class.getName())
82+
&& (currentType.equals(Long.class.getName())
83+
|| currentType.equals(Integer.class.getName()))) {
84+
return true;
85+
}
86+
return false;
87+
}
88+
89+
private static String determineReplacementType(
90+
ConfigurationMetadataProperty replacementMetadata) {
91+
if (replacementMetadata == null || replacementMetadata.getType() == null) {
92+
return null;
93+
}
94+
String candidate = replacementMetadata.getType();
95+
if (candidate.startsWith(Map.class.getName())) {
96+
int lastComma = candidate.lastIndexOf(',');
97+
if (lastComma != -1) {
98+
return candidate.substring(lastComma + 1, candidate.length() - 1).trim();
99+
}
100+
}
101+
return candidate;
62102
}
63103

64104
public ConfigurationProperty getProperty() {
@@ -69,4 +109,34 @@ public Integer getLineNumber() {
69109
return this.lineNumber;
70110
}
71111

112+
public ConfigurationMetadataProperty getMetadata() {
113+
return this.metadata;
114+
}
115+
116+
public boolean isCompatibleType() {
117+
return this.compatibleType;
118+
}
119+
120+
public String determineReason() {
121+
if (this.compatibleType) {
122+
return "Replacement: " + this.metadata.getDeprecation().getReplacement();
123+
}
124+
Deprecation deprecation = this.metadata.getDeprecation();
125+
if (StringUtils.hasText(deprecation.getShortReason())) {
126+
return "Reason: " + deprecation.getShortReason();
127+
}
128+
if (StringUtils.hasText(deprecation.getReplacement())) {
129+
if (this.replacementMetadata != null) {
130+
return String.format(
131+
"Reason: Replacement key '%s' uses an incompatible target type",
132+
deprecation.getReplacement());
133+
}
134+
else {
135+
return String.format("Reason: No metadata found for replacement key '%s'",
136+
deprecation.getReplacement());
137+
}
138+
}
139+
return "Reason: none";
140+
}
141+
72142
}

‎spring-boot-project/spring-boot-properties-migrator/src/test/java/org/springframework/boot/context/properties/migrator/PropertiesMigrationReporterTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ public void invalidReplacementHandled() throws IOException {
161161
assertThat(report).isNotNull();
162162
assertThat(report).containsSubsequence("Property source 'first'",
163163
"deprecated.six.test", "Line: 1", "Reason",
164-
"Replacement key 'does.not.exist' uses an incompatible target type");
164+
"No metadata found for replacement key 'does.not.exist'");
165165
assertThat(report).doesNotContain("null");
166166
}
167167

0 commit comments

Comments
 (0)