Skip to content

Commit e9f31f9

Browse files
committed
Improve configuration properties back-compatibility
Refine `SystemEnvironmentPropertyMapper` to support environment variables that would have worked in Spring Boot 1.5. Specifically, camelCase property bindings now support an additional underscore. The recommended way to map `fooBar` is still `PREFIX_FOOBAR`, however, `PREFIX_FOO_BAR` will now also work. Fixes gh-10873
1 parent 1e7d85a commit e9f31f9

File tree

3 files changed

+110
-26
lines changed

3 files changed

+110
-26
lines changed

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

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818

1919
import java.util.ArrayList;
2020
import java.util.Collections;
21+
import java.util.LinkedHashSet;
2122
import java.util.List;
23+
import java.util.Set;
2224
import java.util.function.Function;
2325
import java.util.stream.IntStream;
2426

@@ -49,6 +51,37 @@ final class SystemEnvironmentPropertyMapper implements PropertyMapper {
4951
private SystemEnvironmentPropertyMapper() {
5052
}
5153

54+
@Override
55+
public List<PropertyMapping> map(PropertySource<?> propertySource,
56+
ConfigurationPropertyName configurationPropertyName) {
57+
Set<String> names = new LinkedHashSet<>();
58+
names.add(convertName(configurationPropertyName));
59+
names.add(convertLegacyName(configurationPropertyName));
60+
List<PropertyMapping> result = new ArrayList<>();
61+
names.forEach((name) -> result
62+
.add(new PropertyMapping(name, configurationPropertyName)));
63+
if (isListShortcutPossible(configurationPropertyName)) {
64+
result.addAll(mapListShortcut(propertySource, configurationPropertyName));
65+
}
66+
return result;
67+
}
68+
69+
private boolean isListShortcutPossible(ConfigurationPropertyName name) {
70+
return (name.isLastElementIndexed() && isNumber(name.getLastElement(Form.UNIFORM))
71+
&& name.getNumberOfElements() >= 1);
72+
}
73+
74+
private List<PropertyMapping> mapListShortcut(PropertySource<?> propertySource,
75+
ConfigurationPropertyName name) {
76+
String result = convertName(name, name.getNumberOfElements() - 1) + "__";
77+
if (propertySource.containsProperty(result)) {
78+
int index = Integer.parseInt(name.getLastElement(Form.UNIFORM));
79+
return Collections.singletonList(
80+
new PropertyMapping(result, name, new ElementExtractor(index)));
81+
}
82+
return Collections.emptyList();
83+
}
84+
5285
@Override
5386
public List<PropertyMapping> map(PropertySource<?> propertySource,
5487
String propertySourceName) {
@@ -89,19 +122,6 @@ private List<PropertyMapping> expandListShortcut(String propertySourceName,
89122
return mappings;
90123
}
91124

92-
@Override
93-
public List<PropertyMapping> map(PropertySource<?> propertySource,
94-
ConfigurationPropertyName configurationPropertyName) {
95-
String name = convertName(configurationPropertyName);
96-
List<PropertyMapping> result = Collections
97-
.singletonList(new PropertyMapping(name, configurationPropertyName));
98-
if (isListShortcutPossible(configurationPropertyName)) {
99-
result = new ArrayList<>(result);
100-
result.addAll(mapListShortcut(propertySource, configurationPropertyName));
101-
}
102-
return result;
103-
}
104-
105125
private String convertName(ConfigurationPropertyName name) {
106126
return convertName(name, name.getNumberOfElements());
107127
}
@@ -115,20 +135,17 @@ private String convertName(ConfigurationPropertyName name, int numberOfElements)
115135
return result.toString();
116136
}
117137

118-
private boolean isListShortcutPossible(ConfigurationPropertyName name) {
119-
return (name.isLastElementIndexed() && isNumber(name.getLastElement(Form.UNIFORM))
120-
&& name.getNumberOfElements() >= 1);
138+
private String convertLegacyName(ConfigurationPropertyName name) {
139+
StringBuilder result = new StringBuilder();
140+
for (int i = 0; i < name.getNumberOfElements(); i++) {
141+
result.append(result.length() == 0 ? "" : "_");
142+
result.append(convertLegacyNameElement(name.getElement(i, Form.ORIGINAL)));
143+
}
144+
return result.toString();
121145
}
122146

123-
private List<PropertyMapping> mapListShortcut(PropertySource<?> propertySource,
124-
ConfigurationPropertyName name) {
125-
String result = convertName(name, name.getNumberOfElements() - 1) + "__";
126-
if (propertySource.containsProperty(result)) {
127-
int index = Integer.parseInt(name.getLastElement(Form.UNIFORM));
128-
return Collections.singletonList(
129-
new PropertyMapping(result, name, new ElementExtractor(index)));
130-
}
131-
return Collections.emptyList();
147+
private Object convertLegacyNameElement(String element) {
148+
return element.replace("-", "_").toUpperCase();
132149
}
133150

134151
private CharSequence processElementValue(CharSequence value) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2012-2017 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+
* http://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.context.properties.bind;
18+
19+
import java.util.Collections;
20+
21+
import org.junit.Test;
22+
23+
import org.springframework.core.env.StandardEnvironment;
24+
import org.springframework.core.env.SystemEnvironmentPropertySource;
25+
import org.springframework.mock.env.MockEnvironment;
26+
27+
import static org.assertj.core.api.Assertions.assertThat;
28+
29+
/**
30+
* Integration tests to ensure that the {@link Binder} offers at least some support for
31+
* Boot 1.5 style binding.
32+
*
33+
* @author Phillip Webb
34+
*/
35+
public class BackCompatibiltyBinderIntegrationTests {
36+
37+
// gh-10873
38+
39+
@Test
40+
public void bindWhenBindingCamelCaseToEnvironmentWithExtractUnderscore()
41+
throws Exception {
42+
MockEnvironment environment = new MockEnvironment();
43+
SystemEnvironmentPropertySource propertySource = new SystemEnvironmentPropertySource(
44+
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
45+
Collections.singletonMap("FOO_ZK_NODES", "foo"));
46+
environment.getPropertySources().addFirst(propertySource);
47+
ExampleCamelCaseBean result = Binder.get(environment)
48+
.bind("foo", Bindable.of(ExampleCamelCaseBean.class)).get();
49+
assertThat(result.getZkNodes()).isEqualTo("foo");
50+
}
51+
52+
public static class ExampleCamelCaseBean {
53+
54+
private String zkNodes;
55+
56+
public String getZkNodes() {
57+
return this.zkNodes;
58+
}
59+
60+
public void setZkNodes(String zkNodes) {
61+
this.zkNodes = zkNodes;
62+
}
63+
64+
}
65+
66+
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ public void mapFromConfigurationShouldReturnBestGuess() throws Exception {
6565
assertThat(namesFromConfiguration("host[0].name")).containsExactly("HOST_0_NAME");
6666
assertThat(namesFromConfiguration("host.f00.name"))
6767
.containsExactly("HOST_F00_NAME");
68-
assertThat(namesFromConfiguration("foo.the-bar")).containsExactly("FOO_THEBAR");
68+
assertThat(namesFromConfiguration("foo.the-bar")).containsExactly("FOO_THEBAR",
69+
"FOO_THE_BAR");
6970
}
7071

7172
@Test

0 commit comments

Comments
 (0)