Skip to content

Commit dc00342

Browse files
committed
Improve handling of environment variables in failure analysis
Prior to this change, the failure analysis for an invalid configuration property value filtered out the configuration property sources property source. This property source contains a "duplicate" of all of the environment's other property sources but with configuration property support (such as relaxed/fuzzy matching of environment variables). This was done to prevent the reporting of duplicates when a property was found in both the configuration property sources property source and the "normal" property sources. An unwanted side-effect of this was that fuzzy matching of environment variables was lost so the origin of com.example.some-property would be found in the environment variable was COM_EXAMPLE_SOME_PROPERTY but would not be found if it was COM_EXAMPLE_SOMEPROPERTY. This commit addresses this side-effect by no longer filtering out the configuration property sources property source. To then prevent duplicates from being reported in the analysis, it instead deduplicates things based on the origin of each property that's found in the environment's property sources. Fixes gh-43380
1 parent 42821f3 commit dc00342

File tree

2 files changed

+87
-17
lines changed

2 files changed

+87
-17
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/diagnostics/analyzer/InvalidConfigurationPropertyValueFailureAnalyzer.java

+14-7
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,18 @@
1616

1717
package org.springframework.boot.diagnostics.analyzer;
1818

19+
import java.util.HashSet;
1920
import java.util.List;
21+
import java.util.Set;
2022
import java.util.stream.Stream;
2123

22-
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
2324
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
2425
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
2526
import org.springframework.boot.diagnostics.FailureAnalysis;
2627
import org.springframework.boot.diagnostics.FailureAnalyzer;
2728
import org.springframework.boot.origin.Origin;
2829
import org.springframework.boot.origin.OriginLookup;
30+
import org.springframework.boot.origin.PropertySourceOrigin;
2931
import org.springframework.core.env.ConfigurableEnvironment;
3032
import org.springframework.core.env.Environment;
3133
import org.springframework.core.env.PropertySource;
@@ -61,18 +63,23 @@ protected FailureAnalysis analyze(Throwable rootFailure, InvalidConfigurationPro
6163
}
6264

6365
private List<Descriptor> getDescriptors(String propertyName) {
66+
Set<Origin> seen = new HashSet<>();
6467
return getPropertySources().filter((source) -> source.containsProperty(propertyName))
6568
.map((source) -> Descriptor.get(source, propertyName))
69+
.filter((descriptor) -> seen.add(getOrigin(descriptor)))
6670
.toList();
6771
}
6872

69-
private Stream<PropertySource<?>> getPropertySources() {
70-
if (this.environment == null) {
71-
return Stream.empty();
73+
private Origin getOrigin(Descriptor descriptor) {
74+
Origin origin = descriptor.origin;
75+
if (origin instanceof PropertySourceOrigin propertySourceOrigin) {
76+
origin = propertySourceOrigin.getOrigin();
7277
}
73-
return this.environment.getPropertySources()
74-
.stream()
75-
.filter((source) -> !ConfigurationPropertySources.isAttachedConfigurationPropertySource(source));
78+
return origin;
79+
}
80+
81+
private Stream<PropertySource<?>> getPropertySources() {
82+
return (this.environment != null) ? this.environment.getPropertySources().stream() : Stream.empty();
7683
}
7784

7885
private void appendDetails(StringBuilder message, InvalidConfigurationPropertyValueException cause,

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/diagnostics/analyzer/InvalidConfigurationPropertyValueFailureAnalyzerTests.java

+73-10
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,16 @@
2020

2121
import org.junit.jupiter.api.Test;
2222

23+
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
2324
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
2425
import org.springframework.boot.diagnostics.FailureAnalysis;
2526
import org.springframework.boot.origin.Origin;
2627
import org.springframework.boot.origin.OriginLookup;
2728
import org.springframework.core.env.EnumerablePropertySource;
2829
import org.springframework.core.env.MapPropertySource;
30+
import org.springframework.core.env.SystemEnvironmentPropertySource;
2931
import org.springframework.mock.env.MockEnvironment;
32+
import org.springframework.util.ObjectUtils;
3033

3134
import static org.assertj.core.api.Assertions.assertThat;
3235

@@ -63,6 +66,26 @@ void analysisWithKnownProperty() {
6366
.doesNotContain("Additionally, this property is also set");
6467
}
6568

69+
@Test
70+
void analysisWithKnownPropertyFromSystemEnvironment() {
71+
MapPropertySource source = new SystemEnvironmentPropertySource("systemEnvironment",
72+
Collections.singletonMap("COM_EXAMPLE_TESTPROPERTY", "invalid"));
73+
this.environment.getPropertySources().addFirst(source);
74+
ConfigurationPropertySources.attach(this.environment);
75+
assertThat(this.environment.getProperty("com.example.test-property")).isEqualTo("invalid");
76+
InvalidConfigurationPropertyValueException failure = new InvalidConfigurationPropertyValueException(
77+
"com.example.test-property", "invalid", "This is not valid.");
78+
FailureAnalysis analysis = performAnalysis(failure);
79+
assertThat(analysis.getDescription()).contains("com.example.test-property")
80+
.contains("invalid")
81+
.contains("property source \"systemEnvironment\"");
82+
assertThat(analysis.getCause()).isSameAs(failure);
83+
assertThat(analysis.getAction()).contains("Review the value of the property with the provided reason.");
84+
assertThat(analysis.getDescription()).contains("Validation failed for the following reason")
85+
.contains("This is not valid.")
86+
.doesNotContain("Additionally, this property is also set");
87+
}
88+
6689
@Test
6790
void analysisWithKnownPropertyAndNoReason() {
6891
MapPropertySource source = new MapPropertySource("test", Collections.singletonMap("test.property", "invalid"));
@@ -84,6 +107,8 @@ void analysisWithKnownPropertyAndOtherCandidates() {
84107
this.environment.getPropertySources().addFirst(OriginCapablePropertySource.get(source));
85108
this.environment.getPropertySources().addLast(additional);
86109
this.environment.getPropertySources().addLast(OriginCapablePropertySource.get(another));
110+
this.environment.getPropertySources().addLast(OriginCapablePropertySource.get("another-again", another));
111+
ConfigurationPropertySources.attach(this.environment);
87112
InvalidConfigurationPropertyValueException failure = new InvalidConfigurationPropertyValueException(
88113
"test.property", "invalid", "This is not valid.");
89114
FailureAnalysis analysis = performAnalysis(failure);
@@ -92,7 +117,8 @@ void analysisWithKnownPropertyAndOtherCandidates() {
92117
assertThat(analysis.getDescription())
93118
.contains("Additionally, this property is also set in the following property sources:")
94119
.contains("In 'additional' with the value 'valid'")
95-
.contains("In 'another' with the value 'test' (originating from 'TestOrigin test.property')");
120+
.contains("In 'another' with the value 'test' (originating from 'TestOrigin test.property')")
121+
.doesNotContain("another-again");
96122
}
97123

98124
@Test
@@ -122,7 +148,11 @@ static class OriginCapablePropertySource<T> extends EnumerablePropertySource<T>
122148
private final EnumerablePropertySource<T> propertySource;
123149

124150
OriginCapablePropertySource(EnumerablePropertySource<T> propertySource) {
125-
super(propertySource.getName(), propertySource.getSource());
151+
this(propertySource.getName(), propertySource);
152+
}
153+
154+
OriginCapablePropertySource(String name, EnumerablePropertySource<T> propertySource) {
155+
super(name, propertySource.getSource());
126156
this.propertySource = propertySource;
127157
}
128158

@@ -138,20 +168,53 @@ public String[] getPropertyNames() {
138168

139169
@Override
140170
public Origin getOrigin(String name) {
141-
return new Origin() {
142-
143-
@Override
144-
public String toString() {
145-
return "TestOrigin " + name;
146-
}
147-
148-
};
171+
return new TestOrigin(name, this.propertySource.getName());
149172
}
150173

151174
static <T> OriginCapablePropertySource<T> get(EnumerablePropertySource<T> propertySource) {
152175
return new OriginCapablePropertySource<>(propertySource);
153176
}
154177

178+
static <T> OriginCapablePropertySource<T> get(String name, EnumerablePropertySource<T> propertySource) {
179+
return new OriginCapablePropertySource<>(name, propertySource);
180+
}
181+
182+
static final class TestOrigin implements Origin {
183+
184+
private final String name;
185+
186+
private final String sourceName;
187+
188+
private TestOrigin(String name, String sourceName) {
189+
this.name = name;
190+
this.sourceName = sourceName;
191+
}
192+
193+
@Override
194+
public boolean equals(Object obj) {
195+
if (this == obj) {
196+
return true;
197+
}
198+
if (obj == null || getClass() != obj.getClass()) {
199+
return false;
200+
}
201+
TestOrigin other = (TestOrigin) obj;
202+
return ObjectUtils.nullSafeEquals(this.name, other.name)
203+
&& ObjectUtils.nullSafeEquals(this.sourceName, other.sourceName);
204+
}
205+
206+
@Override
207+
public int hashCode() {
208+
return ObjectUtils.nullSafeHashCode(this.name);
209+
}
210+
211+
@Override
212+
public String toString() {
213+
return "TestOrigin " + this.name;
214+
}
215+
216+
}
217+
155218
}
156219

157220
}

0 commit comments

Comments
 (0)