Support `.` and `_` binder prefix joins

Update RelaxedDataBinder so that both `.` and `_` are considered in
getPropertyValuesForNamePrefix(...).

With Spring Boot 1.2.5 binding environment variables of the form
`FOO_BAR_BAZ` to `@ConfigurationProperties(prefix="foo-bar")` objects
worked thanks to a happy accident. When `PropertySourcesPropertyValues`
processed a non enumerable `PropertySource` it called the resolver
with a property name `FOO_BAR.BAZ`. A `SystemEnvironmentPropertySource`
will replace `.` with `_` and hence find a value.

Commit 1abd0879 updated non enumerable processing such that the resolver
was never called.

Replicating the problem is quite involved as you need to ensure that you
have both a SystemEnvironmentPropertySource and a non-enumerable
property source (e.g. RandomPropertySource). A test has been added to
PropertiesConfigurationFactoryTests which passes on 1.2.5, fails on
1.2.6 and passes again following this commit.

Fixes gh-4045
pull/4212/head
Phillip Webb 9 years ago
parent 31705982a0
commit b0d9a8322e

@ -201,11 +201,15 @@ public class RelaxedDataBinder extends DataBinder {
MutablePropertyValues rtn = new MutablePropertyValues();
for (PropertyValue value : propertyValues.getPropertyValues()) {
String name = value.getName();
for (String candidate : new RelaxedNames(this.namePrefix)) {
if (name.startsWith(candidate)) {
name = name.substring(candidate.length());
if (!(this.ignoreNestedProperties && name.contains("."))) {
rtn.add(name, value.getValue());
for (String prefix : new RelaxedNames(stripLastDot(this.namePrefix))) {
for (String separator : new String[] { ".", "_" }) {
String candidate = (StringUtils.hasLength(prefix) ? prefix
+ separator : prefix);
if (name.startsWith(candidate)) {
name = name.substring(candidate.length());
if (!(this.ignoreNestedProperties && name.contains("."))) {
rtn.add(name, value.getValue());
}
}
}
}
@ -213,6 +217,13 @@ public class RelaxedDataBinder extends DataBinder {
return rtn;
}
private String stripLastDot(String string) {
if (StringUtils.hasLength(string) && string.endsWith(".")) {
string = string.substring(0, string.length() - 1);
}
return string;
}
private PropertyValue modifyProperty(BeanWrapper target, PropertyValue propertyValue) {
String name = propertyValue.getName();
String normalizedName = normalizePath(target, name);

@ -17,13 +17,17 @@
package org.springframework.boot.bind;
import java.io.IOException;
import java.util.Collections;
import javax.validation.Validation;
import javax.validation.constraints.NotNull;
import org.junit.Test;
import org.springframework.beans.NotWritablePropertyException;
import org.springframework.boot.context.config.RandomValuePropertySource;
import org.springframework.context.support.StaticMessageSource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.SystemEnvironmentPropertySource;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.validation.BindException;
@ -110,6 +114,21 @@ public class PropertiesConfigurationFactoryTests {
assertEquals("blah", foo.bar);
}
@Test
public void testBindWithDashPrefix() throws Exception {
// gh-4045
this.targetName = "foo-bar";
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addLast(new SystemEnvironmentPropertySource("systemEnvironment",
Collections.<String, Object>singletonMap("FOO_BAR_NAME", "blah")));
propertySources.addLast(new RandomValuePropertySource("random"));
setupFactory();
this.factory.setPropertySources(propertySources);
this.factory.afterPropertiesSet();
Foo foo = this.factory.getObject();
assertEquals("blah", foo.name);
}
private Foo createFoo(final String values) throws Exception {
setupFactory();
return bindFoo(values);

Loading…
Cancel
Save