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
This commit is contained in:
Phillip Webb 2017-11-06 11:21:16 -08:00
parent 1e7d85a632
commit e9f31f9c34
3 changed files with 110 additions and 26 deletions

View File

@ -18,7 +18,9 @@ package org.springframework.boot.context.properties.source;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.IntStream;
@ -49,6 +51,37 @@ final class SystemEnvironmentPropertyMapper implements PropertyMapper {
private SystemEnvironmentPropertyMapper() {
}
@Override
public List<PropertyMapping> map(PropertySource<?> propertySource,
ConfigurationPropertyName configurationPropertyName) {
Set<String> names = new LinkedHashSet<>();
names.add(convertName(configurationPropertyName));
names.add(convertLegacyName(configurationPropertyName));
List<PropertyMapping> result = new ArrayList<>();
names.forEach((name) -> result
.add(new PropertyMapping(name, configurationPropertyName)));
if (isListShortcutPossible(configurationPropertyName)) {
result.addAll(mapListShortcut(propertySource, configurationPropertyName));
}
return result;
}
private boolean isListShortcutPossible(ConfigurationPropertyName name) {
return (name.isLastElementIndexed() && isNumber(name.getLastElement(Form.UNIFORM))
&& name.getNumberOfElements() >= 1);
}
private List<PropertyMapping> mapListShortcut(PropertySource<?> propertySource,
ConfigurationPropertyName name) {
String result = convertName(name, name.getNumberOfElements() - 1) + "__";
if (propertySource.containsProperty(result)) {
int index = Integer.parseInt(name.getLastElement(Form.UNIFORM));
return Collections.singletonList(
new PropertyMapping(result, name, new ElementExtractor(index)));
}
return Collections.emptyList();
}
@Override
public List<PropertyMapping> map(PropertySource<?> propertySource,
String propertySourceName) {
@ -89,19 +122,6 @@ final class SystemEnvironmentPropertyMapper implements PropertyMapper {
return mappings;
}
@Override
public List<PropertyMapping> map(PropertySource<?> propertySource,
ConfigurationPropertyName configurationPropertyName) {
String name = convertName(configurationPropertyName);
List<PropertyMapping> result = Collections
.singletonList(new PropertyMapping(name, configurationPropertyName));
if (isListShortcutPossible(configurationPropertyName)) {
result = new ArrayList<>(result);
result.addAll(mapListShortcut(propertySource, configurationPropertyName));
}
return result;
}
private String convertName(ConfigurationPropertyName name) {
return convertName(name, name.getNumberOfElements());
}
@ -115,20 +135,17 @@ final class SystemEnvironmentPropertyMapper implements PropertyMapper {
return result.toString();
}
private boolean isListShortcutPossible(ConfigurationPropertyName name) {
return (name.isLastElementIndexed() && isNumber(name.getLastElement(Form.UNIFORM))
&& name.getNumberOfElements() >= 1);
private String convertLegacyName(ConfigurationPropertyName name) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < name.getNumberOfElements(); i++) {
result.append(result.length() == 0 ? "" : "_");
result.append(convertLegacyNameElement(name.getElement(i, Form.ORIGINAL)));
}
return result.toString();
}
private List<PropertyMapping> mapListShortcut(PropertySource<?> propertySource,
ConfigurationPropertyName name) {
String result = convertName(name, name.getNumberOfElements() - 1) + "__";
if (propertySource.containsProperty(result)) {
int index = Integer.parseInt(name.getLastElement(Form.UNIFORM));
return Collections.singletonList(
new PropertyMapping(result, name, new ElementExtractor(index)));
}
return Collections.emptyList();
private Object convertLegacyNameElement(String element) {
return element.replace("-", "_").toUpperCase();
}
private CharSequence processElementValue(CharSequence value) {

View File

@ -0,0 +1,66 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.bind;
import java.util.Collections;
import org.junit.Test;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.env.SystemEnvironmentPropertySource;
import org.springframework.mock.env.MockEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests to ensure that the {@link Binder} offers at least some support for
* Boot 1.5 style binding.
*
* @author Phillip Webb
*/
public class BackCompatibiltyBinderIntegrationTests {
// gh-10873
@Test
public void bindWhenBindingCamelCaseToEnvironmentWithExtractUnderscore()
throws Exception {
MockEnvironment environment = new MockEnvironment();
SystemEnvironmentPropertySource propertySource = new SystemEnvironmentPropertySource(
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
Collections.singletonMap("FOO_ZK_NODES", "foo"));
environment.getPropertySources().addFirst(propertySource);
ExampleCamelCaseBean result = Binder.get(environment)
.bind("foo", Bindable.of(ExampleCamelCaseBean.class)).get();
assertThat(result.getZkNodes()).isEqualTo("foo");
}
public static class ExampleCamelCaseBean {
private String zkNodes;
public String getZkNodes() {
return this.zkNodes;
}
public void setZkNodes(String zkNodes) {
this.zkNodes = zkNodes;
}
}
}

View File

@ -65,7 +65,8 @@ public class SystemEnvironmentPropertyMapperTests extends AbstractPropertyMapper
assertThat(namesFromConfiguration("host[0].name")).containsExactly("HOST_0_NAME");
assertThat(namesFromConfiguration("host.f00.name"))
.containsExactly("HOST_F00_NAME");
assertThat(namesFromConfiguration("foo.the-bar")).containsExactly("FOO_THEBAR");
assertThat(namesFromConfiguration("foo.the-bar")).containsExactly("FOO_THEBAR",
"FOO_THE_BAR");
}
@Test