Commit 94c5203d authored by Dave Syer's avatar Dave Syer

Add more smarts to properties binding

* Underscores are allowed as nested property field separators
* System and env vars are only considered for binding if they
look like they apply to a given bean when ignoreUnknownFields
is false
parent 04b7b9b2
......@@ -16,8 +16,11 @@
package org.springframework.boot.bind;
import java.beans.PropertyDescriptor;
import java.util.HashSet;
import java.util.Locale;
import java.util.Properties;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
......@@ -240,9 +243,23 @@ public class PropertiesConfigurationFactory<T> implements FactoryBean<T>,
dataBinder.setIgnoreUnknownFields(this.ignoreUnknownFields);
customizeBinder(dataBinder);
Set<String> names = new HashSet<String>();
if (this.target != null) {
PropertyDescriptor[] descriptors = BeanUtils
.getPropertyDescriptors(this.target.getClass());
for (PropertyDescriptor descriptor : descriptors) {
String name = descriptor.getName();
if (!name.equals("class")) {
names.add(name);
names.add(name + ".*");
names.add(name + "_*");
}
}
}
PropertyValues propertyValues = (this.properties != null ? new MutablePropertyValues(
this.properties)
: new PropertySourcesPropertyValues(this.propertySources));
this.properties) : new PropertySourcesPropertyValues(
this.propertySources, names));
dataBinder.bind(propertyValues);
if (this.validator != null) {
......
......@@ -29,6 +29,7 @@ import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources;
import org.springframework.core.env.PropertySourcesPropertyResolver;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.util.PatternMatchUtils;
import org.springframework.validation.DataBinder;
/**
......@@ -44,23 +45,40 @@ public class PropertySourcesPropertyValues implements PropertyValues {
private PropertySources propertySources;
private Collection<String> NON_ENUMERABLES = Arrays.asList(
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME);;
/**
* Create a new PropertyValues from the given PropertySources
* @param propertySources a PropertySources instance
*/
public PropertySourcesPropertyValues(PropertySources propertySources) {
this(propertySources, null);
}
/**
* Create a new PropertyValues from the given PropertySources
* @param propertySources a PropertySources instance
* @param systemPropertyNames property names to include from system properties and
* environment variables
*/
public PropertySourcesPropertyValues(PropertySources propertySources,
Collection<String> systemPropertyNames) {
this.propertySources = propertySources;
Collection<String> nonEnumerables = Arrays.asList(
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME);
PropertySourcesPropertyResolver resolver = new PropertySourcesPropertyResolver(
propertySources);
String[] includes = systemPropertyNames == null ? new String[0]
: systemPropertyNames.toArray(new String[0]);
for (PropertySource<?> source : propertySources) {
if (source instanceof EnumerablePropertySource
&& !nonEnumerables.contains(source.getName())) {
if (source instanceof EnumerablePropertySource) {
EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) source;
if (enumerable.getPropertyNames().length > 0) {
for (String propertyName : enumerable.getPropertyNames()) {
if (this.NON_ENUMERABLES.contains(source.getName())
&& !PatternMatchUtils.simpleMatch(includes, propertyName)) {
continue;
}
Object value = source.getProperty(propertyName);
try {
value = resolver.getProperty(propertyName);
......
......@@ -102,7 +102,7 @@ public class RelaxedDataBinder extends DataBinder {
private MutablePropertyValues modifyProperties(MutablePropertyValues propertyValues,
Object target) {
propertyValues = getProperyValuesForNamePrefix(propertyValues);
propertyValues = getPropertyValuesForNamePrefix(propertyValues);
if (target instanceof MapHolder) {
propertyValues = addMapPrefix(propertyValues);
......@@ -126,26 +126,18 @@ public class RelaxedDataBinder extends DataBinder {
return rtn;
}
private MutablePropertyValues getProperyValuesForNamePrefix(
private MutablePropertyValues getPropertyValuesForNamePrefix(
MutablePropertyValues propertyValues) {
if (!StringUtils.hasText(this.namePrefix) && !this.ignoreNestedProperties) {
return propertyValues;
}
int prefixLength = StringUtils.hasText(this.namePrefix) ? this.namePrefix
.length() : 0;
MutablePropertyValues rtn = new MutablePropertyValues();
for (PropertyValue pv : propertyValues.getPropertyValues()) {
String name = pv.getName();
if (this.ignoreNestedProperties) {
name = name.substring(prefixLength);
if (!name.contains(".")) {
rtn.add(name, pv.getValue());
}
}
else {
for (String candidate : new RelaxedNames(this.namePrefix)) {
if (name.startsWith(candidate)) {
name = name.substring(candidate.length());
for (String candidate : new RelaxedNames(this.namePrefix)) {
if (name.startsWith(candidate)) {
name = name.substring(candidate.length());
if (!(this.ignoreNestedProperties && name.contains("."))) {
rtn.add(name, pv.getValue());
}
}
......
......@@ -43,7 +43,7 @@ public final class RelaxedNames implements Iterable<String> {
* using dashed notation (e.g. {@literal my-property-name}
*/
public RelaxedNames(String name) {
this.name = name;
this.name = name == null ? "" : name;
initialize(RelaxedNames.this.name, this.values);
}
......@@ -103,6 +103,12 @@ public final class RelaxedNames implements Iterable<String> {
return value.replace("-", "_");
}
},
UNDERSCORE_TO_PERIOD {
@Override
public String apply(String value) {
return value.replace("_", ".");
}
},
PERIOD_TO_UNDERSCORE {
@Override
public String apply(String value) {
......
......@@ -109,6 +109,13 @@ public class RelaxedDataBinderTests {
assertEquals("bar", target.getFoo_bar());
}
@Test
public void testBindUnderscoreToCamelCase() throws Exception {
VanillaTarget target = new VanillaTarget();
bind(target, "foo_baz: bar");
assertEquals("bar", target.getFooBaz());
}
@Test
public void testBindHyphen() throws Exception {
VanillaTarget target = new VanillaTarget();
......@@ -119,7 +126,7 @@ public class RelaxedDataBinderTests {
@Test
public void testBindCamelCase() throws Exception {
VanillaTarget target = new VanillaTarget();
bind(target, "foo-baz: bar");
bind(target, "fooBaz: bar");
assertEquals("bar", target.getFooBaz());
}
......@@ -182,6 +189,13 @@ public class RelaxedDataBinderTests {
assertEquals(123, target.getNested().getValue());
}
@Test
public void testBindNestedWithEnviromentStyle() throws Exception {
TargetWithNestedObject target = new TargetWithNestedObject();
bind(target, "nested_foo: bar\n" + "nested_value: 123");
assertEquals(123, target.getNested().getValue());
}
@Test
public void testBindNestedList() throws Exception {
TargetWithNestedList target = new TargetWithNestedList();
......
......@@ -50,9 +50,11 @@ public class RelaxedNamesTests {
public void fromUnderscores() throws Exception {
Iterator<String> iterator = new RelaxedNames("nes_ted").iterator();
assertThat(iterator.next(), equalTo("nes_ted"));
assertThat(iterator.next(), equalTo("nes.ted"));
assertThat(iterator.next(), equalTo("nesTed"));
assertThat(iterator.next(), equalTo("nested"));
assertThat(iterator.next(), equalTo("NES_TED"));
assertThat(iterator.next(), equalTo("NES.TED"));
assertThat(iterator.next(), equalTo("NESTED"));
assertThat(iterator.hasNext(), equalTo(false));
}
......@@ -99,4 +101,11 @@ public class RelaxedNamesTests {
assertThat(iterator.hasNext(), equalTo(false));
}
@Test
public void fromEmpty() throws Exception {
Iterator<String> iterator = new RelaxedNames("").iterator();
assertThat(iterator.next(), equalTo(""));
assertThat(iterator.hasNext(), equalTo(false));
}
}
......@@ -22,6 +22,7 @@ import java.util.List;
import javax.annotation.PostConstruct;
import org.junit.After;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.TestUtils;
......@@ -43,6 +44,13 @@ public class EnableConfigurationPropertiesTests {
private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
@After
public void close() {
System.clearProperty("name");
System.clearProperty("nested.name");
System.clearProperty("nested_name");
}
@Test
public void testBasicPropertiesBinding() {
this.context.register(TestConfiguration.class);
......@@ -52,6 +60,37 @@ public class EnableConfigurationPropertiesTests {
assertEquals("foo", this.context.getBean(TestProperties.class).name);
}
@Test
public void testSystemPropertiesBinding() {
this.context.register(TestConfiguration.class);
System.setProperty("name", "foo");
this.context.refresh();
assertEquals(1, this.context.getBeanNamesForType(TestProperties.class).length);
assertEquals("foo", this.context.getBean(TestProperties.class).name);
}
@Test
public void testNestedSystemPropertiesBinding() {
this.context.register(NestedConfiguration.class);
System.setProperty("name", "foo");
System.setProperty("nested.name", "bar");
this.context.refresh();
assertEquals(1, this.context.getBeanNamesForType(NestedProperties.class).length);
assertEquals("foo", this.context.getBean(NestedProperties.class).name);
assertEquals("bar", this.context.getBean(NestedProperties.class).nested.name);
}
@Test
public void testNestedSystemPropertiesBindingWithUnderscore() {
this.context.register(NestedConfiguration.class);
System.setProperty("name", "foo");
System.setProperty("nested_name", "bar");
this.context.refresh();
assertEquals(1, this.context.getBeanNamesForType(NestedProperties.class).length);
assertEquals("foo", this.context.getBean(NestedProperties.class).name);
assertEquals("bar", this.context.getBean(NestedProperties.class).nested.name);
}
@Test
public void testStrictPropertiesBinding() {
this.context.register(StrictTestConfiguration.class);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment