diff --git a/spring-boot/src/main/java/org/springframework/boot/bind/PropertiesConfigurationFactory.java b/spring-boot/src/main/java/org/springframework/boot/bind/PropertiesConfigurationFactory.java index d61e8a4276f..c241f38ef69 100644 --- a/spring-boot/src/main/java/org/springframework/boot/bind/PropertiesConfigurationFactory.java +++ b/spring-boot/src/main/java/org/springframework/boot/bind/PropertiesConfigurationFactory.java @@ -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 implements FactoryBean, dataBinder.setIgnoreUnknownFields(this.ignoreUnknownFields); customizeBinder(dataBinder); + Set names = new HashSet(); + 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) { diff --git a/spring-boot/src/main/java/org/springframework/boot/bind/PropertySourcesPropertyValues.java b/spring-boot/src/main/java/org/springframework/boot/bind/PropertySourcesPropertyValues.java index bd2016268cf..c30daa9fcc3 100644 --- a/spring-boot/src/main/java/org/springframework/boot/bind/PropertySourcesPropertyValues.java +++ b/spring-boot/src/main/java/org/springframework/boot/bind/PropertySourcesPropertyValues.java @@ -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 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 systemPropertyNames) { this.propertySources = propertySources; - Collection 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); diff --git a/spring-boot/src/main/java/org/springframework/boot/bind/RelaxedDataBinder.java b/spring-boot/src/main/java/org/springframework/boot/bind/RelaxedDataBinder.java index 04c4c9cf46d..4c972bee2c8 100644 --- a/spring-boot/src/main/java/org/springframework/boot/bind/RelaxedDataBinder.java +++ b/spring-boot/src/main/java/org/springframework/boot/bind/RelaxedDataBinder.java @@ -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()); } } diff --git a/spring-boot/src/main/java/org/springframework/boot/bind/RelaxedNames.java b/spring-boot/src/main/java/org/springframework/boot/bind/RelaxedNames.java index a0cfbf30485..d7970b3c318 100644 --- a/spring-boot/src/main/java/org/springframework/boot/bind/RelaxedNames.java +++ b/spring-boot/src/main/java/org/springframework/boot/bind/RelaxedNames.java @@ -43,7 +43,7 @@ public final class RelaxedNames implements Iterable { * 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 { return value.replace("-", "_"); } }, + UNDERSCORE_TO_PERIOD { + @Override + public String apply(String value) { + return value.replace("_", "."); + } + }, PERIOD_TO_UNDERSCORE { @Override public String apply(String value) { diff --git a/spring-boot/src/test/java/org/springframework/boot/bind/RelaxedDataBinderTests.java b/spring-boot/src/test/java/org/springframework/boot/bind/RelaxedDataBinderTests.java index f9e2b7109fd..39d125948b2 100644 --- a/spring-boot/src/test/java/org/springframework/boot/bind/RelaxedDataBinderTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/bind/RelaxedDataBinderTests.java @@ -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(); diff --git a/spring-boot/src/test/java/org/springframework/boot/bind/RelaxedNamesTests.java b/spring-boot/src/test/java/org/springframework/boot/bind/RelaxedNamesTests.java index 41728ea56fb..7bdfad61011 100644 --- a/spring-boot/src/test/java/org/springframework/boot/bind/RelaxedNamesTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/bind/RelaxedNamesTests.java @@ -50,9 +50,11 @@ public class RelaxedNamesTests { public void fromUnderscores() throws Exception { Iterator 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 iterator = new RelaxedNames("").iterator(); + assertThat(iterator.next(), equalTo("")); + assertThat(iterator.hasNext(), equalTo(false)); + } + } diff --git a/spring-boot/src/test/java/org/springframework/boot/context/properties/EnableConfigurationPropertiesTests.java b/spring-boot/src/test/java/org/springframework/boot/context/properties/EnableConfigurationPropertiesTests.java index 29e17699f22..289434c4516 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/properties/EnableConfigurationPropertiesTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/properties/EnableConfigurationPropertiesTests.java @@ -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);