Commit 98ae4ed9 authored by Dave Syer's avatar Dave Syer

Add ignoreNestedFields option to @ConfigurationProperties

@ConfigurationProperties(ignoreUnkownFields=false,ignoreNestedFields=true)
is now a useful option for binding to "top-level" command line options
(without a prefix). In that case we don't try to bind to `server.*` and
other common prefixed property values (at the cost of not being able to
bind to nested beans).
parent d8033189
...@@ -67,6 +67,8 @@ public class PropertiesConfigurationFactory<T> implements FactoryBean<T>, ...@@ -67,6 +67,8 @@ public class PropertiesConfigurationFactory<T> implements FactoryBean<T>,
private boolean hasBeenBound = false; private boolean hasBeenBound = false;
private boolean ignoreNestedProperties = false;
private String targetName; private String targetName;
private ConversionService conversionService; private ConversionService conversionService;
...@@ -90,6 +92,17 @@ public class PropertiesConfigurationFactory<T> implements FactoryBean<T>, ...@@ -90,6 +92,17 @@ public class PropertiesConfigurationFactory<T> implements FactoryBean<T>,
this.target = (T) BeanUtils.instantiate(type); this.target = (T) BeanUtils.instantiate(type);
} }
/**
* Flag to disable binding of nested properties (i.e. those with period separators in
* their paths). Can be useful to disable this if the name prefix is empty and you
* don't want to ignore unknown fields.
*
* @param ignoreNestedProperties the flag to set (default false)
*/
public void setIgnoreNestedProperties(boolean ignoreNestedProperties) {
this.ignoreNestedProperties = ignoreNestedProperties;
}
/** /**
* Set whether to ignore unknown fields, that is, whether to ignore bind parameters * Set whether to ignore unknown fields, that is, whether to ignore bind parameters
* that do not have corresponding fields in the target object. * that do not have corresponding fields in the target object.
...@@ -222,6 +235,7 @@ public class PropertiesConfigurationFactory<T> implements FactoryBean<T>, ...@@ -222,6 +235,7 @@ public class PropertiesConfigurationFactory<T> implements FactoryBean<T>,
if (this.conversionService != null) { if (this.conversionService != null) {
dataBinder.setConversionService(this.conversionService); dataBinder.setConversionService(this.conversionService);
} }
dataBinder.setIgnoreNestedProperties(this.ignoreNestedProperties);
dataBinder.setIgnoreInvalidFields(this.ignoreInvalidFields); dataBinder.setIgnoreInvalidFields(this.ignoreInvalidFields);
dataBinder.setIgnoreUnknownFields(this.ignoreUnknownFields); dataBinder.setIgnoreUnknownFields(this.ignoreUnknownFields);
customizeBinder(dataBinder); customizeBinder(dataBinder);
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package org.springframework.boot.bind; package org.springframework.boot.bind;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
...@@ -27,6 +28,7 @@ import org.springframework.core.env.EnumerablePropertySource; ...@@ -27,6 +28,7 @@ import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources; import org.springframework.core.env.PropertySources;
import org.springframework.core.env.PropertySourcesPropertyResolver; import org.springframework.core.env.PropertySourcesPropertyResolver;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.validation.DataBinder; import org.springframework.validation.DataBinder;
/** /**
...@@ -48,10 +50,14 @@ public class PropertySourcesPropertyValues implements PropertyValues { ...@@ -48,10 +50,14 @@ public class PropertySourcesPropertyValues implements PropertyValues {
*/ */
public PropertySourcesPropertyValues(PropertySources propertySources) { public PropertySourcesPropertyValues(PropertySources propertySources) {
this.propertySources = propertySources; this.propertySources = propertySources;
Collection<String> nonEnumerables = Arrays.asList(
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME);
PropertySourcesPropertyResolver resolver = new PropertySourcesPropertyResolver( PropertySourcesPropertyResolver resolver = new PropertySourcesPropertyResolver(
propertySources); propertySources);
for (PropertySource<?> source : propertySources) { for (PropertySource<?> source : propertySources) {
if (source instanceof EnumerablePropertySource) { if (source instanceof EnumerablePropertySource
&& !nonEnumerables.contains(source.getName())) {
EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) source; EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) source;
if (enumerable.getPropertyNames().length > 0) { if (enumerable.getPropertyNames().length > 0) {
for (String propertyName : enumerable.getPropertyNames()) { for (String propertyName : enumerable.getPropertyNames()) {
......
...@@ -43,6 +43,8 @@ public class RelaxedDataBinder extends DataBinder { ...@@ -43,6 +43,8 @@ public class RelaxedDataBinder extends DataBinder {
private String namePrefix; private String namePrefix;
private boolean ignoreNestedProperties = false;
/** /**
* @param target the target into which properties are bound * @param target the target into which properties are bound
*/ */
...@@ -69,6 +71,17 @@ public class RelaxedDataBinder extends DataBinder { ...@@ -69,6 +71,17 @@ public class RelaxedDataBinder extends DataBinder {
this.namePrefix = (StringUtils.hasLength(namePrefix) ? namePrefix + "." : null); this.namePrefix = (StringUtils.hasLength(namePrefix) ? namePrefix + "." : null);
} }
/**
* Flag to disable binding of nested properties (i.e. those with period separators in
* their paths). Can be useful to disable this if the name prefix is empty and you
* don't want to ignore unknown fields.
*
* @param ignoreNestedProperties the flag to set (default false)
*/
public void setIgnoreNestedProperties(boolean ignoreNestedProperties) {
this.ignoreNestedProperties = ignoreNestedProperties;
}
@Override @Override
protected void doBind(MutablePropertyValues propertyValues) { protected void doBind(MutablePropertyValues propertyValues) {
propertyValues = modifyProperties(propertyValues, getTarget()); propertyValues = modifyProperties(propertyValues, getTarget());
...@@ -115,18 +128,28 @@ public class RelaxedDataBinder extends DataBinder { ...@@ -115,18 +128,28 @@ public class RelaxedDataBinder extends DataBinder {
private MutablePropertyValues getProperyValuesForNamePrefix( private MutablePropertyValues getProperyValuesForNamePrefix(
MutablePropertyValues propertyValues) { MutablePropertyValues propertyValues) {
if (this.namePrefix == null) { if (!StringUtils.hasText(this.namePrefix) && !this.ignoreNestedProperties) {
return propertyValues; return propertyValues;
} }
int prefixLength = StringUtils.hasText(this.namePrefix) ? this.namePrefix
.length() : 0;
MutablePropertyValues rtn = new MutablePropertyValues(); MutablePropertyValues rtn = new MutablePropertyValues();
for (PropertyValue pv : propertyValues.getPropertyValues()) { for (PropertyValue pv : propertyValues.getPropertyValues()) {
String name = pv.getName(); String name = pv.getName();
for (String candidate : new RelaxedNames(this.namePrefix)) { if (this.ignoreNestedProperties) {
if (name.startsWith(candidate)) { name = name.substring(prefixLength);
name = name.substring(candidate.length()); if (!name.contains(".")) {
rtn.add(name, pv.getValue()); rtn.add(name, pv.getValue());
} }
} }
else {
for (String candidate : new RelaxedNames(this.namePrefix)) {
if (name.startsWith(candidate)) {
name = name.substring(candidate.length());
rtn.add(name, pv.getValue());
}
}
}
} }
return rtn; return rtn;
} }
......
...@@ -58,6 +58,13 @@ public @interface ConfigurationProperties { ...@@ -58,6 +58,13 @@ public @interface ConfigurationProperties {
*/ */
boolean ignoreInvalidFields() default false; boolean ignoreInvalidFields() default false;
/**
* Flag to indicate that when binding to this object fields with periods in their
* names should be ignored.
* @return the flag value (default false)
*/
boolean ignoreNestedProperties() default false;
/** /**
* Flag to indicate that when binding to this object unknown fields should be ignored. * Flag to indicate that when binding to this object unknown fields should be ignored.
* An unknown field could be a sign of a mistake in the Properties. * An unknown field could be a sign of a mistake in the Properties.
......
...@@ -302,6 +302,7 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc ...@@ -302,6 +302,7 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
if (annotation != null) { if (annotation != null) {
factory.setIgnoreInvalidFields(annotation.ignoreInvalidFields()); factory.setIgnoreInvalidFields(annotation.ignoreInvalidFields());
factory.setIgnoreUnknownFields(annotation.ignoreUnknownFields()); factory.setIgnoreUnknownFields(annotation.ignoreUnknownFields());
factory.setIgnoreNestedProperties(annotation.ignoreNestedProperties());
String targetName = (StringUtils.hasLength(annotation.value()) ? annotation String targetName = (StringUtils.hasLength(annotation.value()) ? annotation
.value() : annotation.name()); .value() : annotation.name());
if (StringUtils.hasLength(targetName)) { if (StringUtils.hasLength(targetName)) {
......
...@@ -312,6 +312,32 @@ public class RelaxedDataBinderTests { ...@@ -312,6 +312,32 @@ public class RelaxedDataBinderTests {
assertEquals(123, target.getValue()); assertEquals(123, target.getValue());
} }
@Test
public void testOnlyTopLevelFields() throws Exception {
VanillaTarget target = new VanillaTarget();
RelaxedDataBinder binder = getBinder(target, null);
binder.setIgnoreUnknownFields(false);
binder.setIgnoreNestedProperties(true);
BindingResult result = bind(binder, target, "foo: bar\n" + "value: 123\n"
+ "nested.bar: spam");
assertEquals(123, target.getValue());
assertEquals("bar", target.getFoo());
assertEquals(0, result.getErrorCount());
}
@Test
public void testNoNestedFields() throws Exception {
VanillaTarget target = new VanillaTarget();
RelaxedDataBinder binder = getBinder(target, "foo");
binder.setIgnoreUnknownFields(false);
binder.setIgnoreNestedProperties(true);
BindingResult result = bind(binder, target, "foo.foo: bar\n" + "foo.value: 123\n"
+ "foo.nested.bar: spam");
assertEquals(123, target.getValue());
assertEquals("bar", target.getFoo());
assertEquals(0, result.getErrorCount());
}
@Test @Test
public void testBindMap() throws Exception { public void testBindMap() throws Exception {
Map<String, Object> target = new LinkedHashMap<String, Object>(); Map<String, Object> target = new LinkedHashMap<String, Object>();
......
...@@ -52,6 +52,26 @@ public class EnableConfigurationPropertiesTests { ...@@ -52,6 +52,26 @@ public class EnableConfigurationPropertiesTests {
assertEquals("foo", this.context.getBean(TestProperties.class).name); assertEquals("foo", this.context.getBean(TestProperties.class).name);
} }
@Test
public void testStrictPropertiesBinding() {
this.context.register(StrictTestConfiguration.class);
TestUtils.addEnviroment(this.context, "name:foo");
this.context.refresh();
assertEquals(1,
this.context.getBeanNamesForType(StrictTestProperties.class).length);
assertEquals("foo", this.context.getBean(TestProperties.class).name);
}
@Test
public void testIgnoreNestedPropertiesBinding() {
this.context.register(IgnoreNestedTestConfiguration.class);
TestUtils.addEnviroment(this.context, "name:foo", "nested.name:bar");
this.context.refresh();
assertEquals(1,
this.context.getBeanNamesForType(IgnoreNestedTestProperties.class).length);
assertEquals("foo", this.context.getBean(TestProperties.class).name);
}
@Test @Test
public void testNestedPropertiesBinding() { public void testNestedPropertiesBinding() {
this.context.register(NestedConfiguration.class); this.context.register(NestedConfiguration.class);
...@@ -195,6 +215,16 @@ public class EnableConfigurationPropertiesTests { ...@@ -195,6 +215,16 @@ public class EnableConfigurationPropertiesTests {
protected static class TestConfiguration { protected static class TestConfiguration {
} }
@Configuration
@EnableConfigurationProperties(StrictTestProperties.class)
protected static class StrictTestConfiguration {
}
@Configuration
@EnableConfigurationProperties(IgnoreNestedTestProperties.class)
protected static class IgnoreNestedTestConfiguration {
}
@Configuration @Configuration
@EnableConfigurationProperties(DerivedProperties.class) @EnableConfigurationProperties(DerivedProperties.class)
protected static class DerivedConfiguration { protected static class DerivedConfiguration {
...@@ -300,6 +330,16 @@ public class EnableConfigurationPropertiesTests { ...@@ -300,6 +330,16 @@ public class EnableConfigurationPropertiesTests {
} }
} }
@ConfigurationProperties(ignoreUnknownFields = false)
protected static class StrictTestProperties extends TestProperties {
}
@ConfigurationProperties(ignoreUnknownFields = false, ignoreNestedProperties = true)
protected static class IgnoreNestedTestProperties extends TestProperties {
}
protected static class MoreProperties { protected static class MoreProperties {
private String name; private String name;
......
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