Skip to content

Commit a8f56b5

Browse files
mbhavephilwebb
andcommitted
Consider legacy environment names in isAncestorOf
Update the `isAncestorOf` method of SpringConfigurationPropertySources so that legacy names are considered for the system environment. Prior to this commit, binding a property such as `my.camelCase.prop` would detect `MY_CAMELCASE_PROP` but not `MY_CAMEL_CASE_PROP` in the system environment. Fixes gh-14479 Co-authored-by: Phillip Webb <[email protected]>
1 parent fb9bf7a commit a8f56b5

File tree

8 files changed

+80
-13
lines changed

8 files changed

+80
-13
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/DefaultPropertyMapper.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 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.
@@ -76,6 +76,11 @@ private PropertyMapping[] tryMap(String propertySourceName) {
7676
return NO_MAPPINGS;
7777
}
7878

79+
@Override
80+
public boolean isAncestorOf(ConfigurationPropertyName name, ConfigurationPropertyName candidate) {
81+
return name.isAncestorOf(candidate);
82+
}
83+
7984
private static class LastMapping<T> {
8085

8186
private final T from;

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/PropertyMapper.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 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.
@@ -55,4 +55,13 @@ interface PropertyMapper {
5555
*/
5656
PropertyMapping[] map(String propertySourceName);
5757

58+
/**
59+
* Returns {@code true} if {@code name} is an ancestor (immediate or nested parent) of
60+
* the given candidate when considering mapping rules.
61+
* @param name the source name
62+
* @param candidate the candidate to check
63+
* @return {@code true} if the candidate is an ancestor of the name
64+
*/
65+
boolean isAncestorOf(ConfigurationPropertyName name, ConfigurationPropertyName candidate);
66+
5867
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/SpringConfigurationPropertySource.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 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.
@@ -249,6 +249,16 @@ private PropertyMapping[] map(PropertyMapper mapper, String propertySourceName)
249249
}
250250
}
251251

252+
@Override
253+
public boolean isAncestorOf(ConfigurationPropertyName name, ConfigurationPropertyName candidate) {
254+
return isAncestorOf(this.first, name, candidate) || isAncestorOf(this.second, name, candidate);
255+
}
256+
257+
private boolean isAncestorOf(PropertyMapper mapper, ConfigurationPropertyName name,
258+
ConfigurationPropertyName candidate) {
259+
return mapper != null && mapper.isAncestorOf(name, candidate);
260+
}
261+
252262
private PropertyMapping[] merge(PropertyMapping[] first, PropertyMapping[] second) {
253263
if (ObjectUtils.isEmpty(second)) {
254264
return first;

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/SpringIterableConfigurationPropertySource.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 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.
@@ -86,7 +86,7 @@ public Iterator<ConfigurationPropertyName> iterator() {
8686

8787
@Override
8888
public ConfigurationPropertyState containsDescendantOf(ConfigurationPropertyName name) {
89-
return ConfigurationPropertyState.search(this, name::isAncestorOf);
89+
return ConfigurationPropertyState.search(this, (candidate) -> getMapper().isAncestorOf(name, candidate));
9090
}
9191

9292
private List<ConfigurationPropertyName> getConfigurationPropertyNames() {

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/SystemEnvironmentPropertyMapper.java

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 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.
@@ -39,7 +39,7 @@ final class SystemEnvironmentPropertyMapper implements PropertyMapper {
3939
@Override
4040
public PropertyMapping[] map(ConfigurationPropertyName configurationPropertyName) {
4141
String name = convertName(configurationPropertyName);
42-
String legacyName = convertLegacyName(configurationPropertyName);
42+
String legacyName = convertLegacyName(configurationPropertyName, '_', true);
4343
if (name.equals(legacyName)) {
4444
return new PropertyMapping[] { new PropertyMapping(name, configurationPropertyName) };
4545
}
@@ -56,6 +56,15 @@ public PropertyMapping[] map(String propertySourceName) {
5656
return new PropertyMapping[] { new PropertyMapping(propertySourceName, name) };
5757
}
5858

59+
@Override
60+
public boolean isAncestorOf(ConfigurationPropertyName name, ConfigurationPropertyName candidate) {
61+
return name.isAncestorOf(candidate) || isLegacyAncestorOf(name, candidate);
62+
}
63+
64+
private boolean isLegacyAncestorOf(ConfigurationPropertyName name, ConfigurationPropertyName candidate) {
65+
return ConfigurationPropertyName.of(convertLegacyName(name, '.', false)).isAncestorOf(candidate);
66+
}
67+
5968
private ConfigurationPropertyName convertName(String propertySourceName) {
6069
try {
6170
return ConfigurationPropertyName.adapt(propertySourceName, '_', this::processElementValue);
@@ -80,19 +89,21 @@ private String convertName(ConfigurationPropertyName name, int numberOfElements)
8089
return result.toString();
8190
}
8291

83-
private String convertLegacyName(ConfigurationPropertyName name) {
92+
private String convertLegacyName(ConfigurationPropertyName name, char joinChar, boolean uppercase) {
8493
StringBuilder result = new StringBuilder();
8594
for (int i = 0; i < name.getNumberOfElements(); i++) {
8695
if (result.length() > 0) {
87-
result.append("_");
96+
result.append(joinChar);
8897
}
89-
result.append(convertLegacyNameElement(name.getElement(i, Form.ORIGINAL)));
98+
String element = name.getElement(i, Form.ORIGINAL);
99+
result.append(convertLegacyNameElement(element, joinChar, uppercase));
90100
}
91101
return result.toString();
92102
}
93103

94-
private Object convertLegacyNameElement(String element) {
95-
return element.replace('-', '_').toUpperCase(Locale.ENGLISH);
104+
private Object convertLegacyNameElement(String element, char joinChar, boolean uppercase) {
105+
String converted = element.replace('-', joinChar);
106+
return !uppercase ? converted : converted.toUpperCase(Locale.ENGLISH);
96107
}
97108

98109
private CharSequence processElementValue(CharSequence value) {

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/source/SpringIterableConfigurationPropertySourceTests.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 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.
@@ -31,6 +31,8 @@
3131
import org.springframework.core.env.EnumerablePropertySource;
3232
import org.springframework.core.env.MapPropertySource;
3333
import org.springframework.core.env.PropertySource;
34+
import org.springframework.core.env.StandardEnvironment;
35+
import org.springframework.core.env.SystemEnvironmentPropertySource;
3436

3537
import static org.assertj.core.api.Assertions.assertThat;
3638
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
@@ -154,6 +156,23 @@ void containsDescendantOfShouldCheckSourceNames() {
154156
.isEqualTo(ConfigurationPropertyState.ABSENT);
155157
}
156158

159+
@Test
160+
void containsDescendantOfWhenSystemEnvironmentPropertySourceShouldLegacyProperty() {
161+
Map<String, Object> source = new LinkedHashMap<>();
162+
source.put("FOO_BAR_BAZ_BONG", "bing");
163+
source.put("FOO_ALPHABRAVO_GAMMA", "delta");
164+
SystemEnvironmentPropertySource propertySource = new SystemEnvironmentPropertySource(
165+
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, source);
166+
SpringIterableConfigurationPropertySource adapter = new SpringIterableConfigurationPropertySource(
167+
propertySource, SystemEnvironmentPropertyMapper.INSTANCE);
168+
assertThat(adapter.containsDescendantOf(ConfigurationPropertyName.of("foo.bar-baz")))
169+
.isEqualTo(ConfigurationPropertyState.PRESENT);
170+
assertThat(adapter.containsDescendantOf(ConfigurationPropertyName.of("foo.alpha-bravo")))
171+
.isEqualTo(ConfigurationPropertyState.PRESENT);
172+
assertThat(adapter.containsDescendantOf(ConfigurationPropertyName.of("foo.blah")))
173+
.isEqualTo(ConfigurationPropertyState.ABSENT);
174+
}
175+
157176
@Test
158177
void simpleMapPropertySourceKeyDataChangeInvalidatesCache() {
159178
// gh-13344

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/source/SystemEnvironmentPropertyMapperTests.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,12 @@ void underscoreWithWhitespaceShouldNotMapToEmptyString() {
7575
assertThat(applicable).isFalse();
7676
}
7777

78+
@Test
79+
void isAncestorOfConsidersLegacyNames() {
80+
ConfigurationPropertyName name = ConfigurationPropertyName.of("my.spring-boot");
81+
assertThat(getMapper().isAncestorOf(name, ConfigurationPropertyName.of("my.spring-boot.property"))).isTrue();
82+
assertThat(getMapper().isAncestorOf(name, ConfigurationPropertyName.of("my.springboot.property"))).isTrue();
83+
assertThat(getMapper().isAncestorOf(name, ConfigurationPropertyName.of("my.boot.property"))).isFalse();
84+
}
85+
7886
}

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/source/TestPropertyMapper.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,9 @@ public PropertyMapping[] map(ConfigurationPropertyName configurationPropertyName
5555
.toArray(new PropertyMapping[0]);
5656
}
5757

58+
@Override
59+
public boolean isAncestorOf(ConfigurationPropertyName name, ConfigurationPropertyName candidate) {
60+
return name.isAncestorOf(candidate);
61+
}
62+
5863
}

0 commit comments

Comments
 (0)