Commit e203a689 authored by Andy Wilkinson's avatar Andy Wilkinson

Consider relaxed variants of target name when filtering property names

Previously, when ignoreUnknownFields was false and property names were
being filtered based on whether or not they begin with the target name,
relaxed variants of the target name were not considered. This resulted
in different delimiters resulting in a non-match. For example, the
property ENV_FOO_NAME would be filtered out when the target name
was env.foo.

This commit updates PropertiesConfigurationFactory to pass all of the
relaxed variants for the target name to the matcher. For the example
above one of those variants will be env_foo which matches ENV_FOO_NAME
due to the matching delimiter.

PropertiesConfigurationFactory was already creating a RelaxedNames
instance for the target name. The code has been reworked a little to
allow these relaxed names to be reused, thereby avoiding the cost of
computing all of the relaxed variants of the target name a second time.

Closes gh-4775
parent 5a7dece1
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
package org.springframework.boot.bind; package org.springframework.boot.bind;
import java.beans.PropertyDescriptor; import java.beans.PropertyDescriptor;
import java.util.HashSet;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
...@@ -262,19 +263,23 @@ public class PropertiesConfigurationFactory<T> ...@@ -262,19 +263,23 @@ public class PropertiesConfigurationFactory<T>
dataBinder.setIgnoreInvalidFields(this.ignoreInvalidFields); dataBinder.setIgnoreInvalidFields(this.ignoreInvalidFields);
dataBinder.setIgnoreUnknownFields(this.ignoreUnknownFields); dataBinder.setIgnoreUnknownFields(this.ignoreUnknownFields);
customizeBinder(dataBinder); customizeBinder(dataBinder);
Set<String> names = getNames(); Iterable<String> relaxedTargetNames = getRelaxedTargetNames();
PropertyValues propertyValues = getPropertyValues(names); Set<String> names = getNames(relaxedTargetNames);
PropertyValues propertyValues = getPropertyValues(names, relaxedTargetNames);
dataBinder.bind(propertyValues); dataBinder.bind(propertyValues);
if (this.validator != null) { if (this.validator != null) {
validate(dataBinder); validate(dataBinder);
} }
} }
private Set<String> getNames() { private Iterable<String> getRelaxedTargetNames() {
return (this.target != null && StringUtils.hasLength(this.targetName)
? new RelaxedNames(this.targetName) : null);
}
private Set<String> getNames(Iterable<String> prefixes) {
Set<String> names = new LinkedHashSet<String>(); Set<String> names = new LinkedHashSet<String>();
if (this.target != null) { if (this.target != null) {
Iterable<String> prefixes = (StringUtils.hasLength(this.targetName)
? new RelaxedNames(this.targetName) : null);
PropertyDescriptor[] descriptors = BeanUtils PropertyDescriptor[] descriptors = BeanUtils
.getPropertyDescriptors(this.target.getClass()); .getPropertyDescriptors(this.target.getClass());
for (PropertyDescriptor descriptor : descriptors) { for (PropertyDescriptor descriptor : descriptors) {
...@@ -300,31 +305,38 @@ public class PropertiesConfigurationFactory<T> ...@@ -300,31 +305,38 @@ public class PropertiesConfigurationFactory<T>
return names; return names;
} }
private PropertyValues getPropertyValues(Set<String> names) { private PropertyValues getPropertyValues(Set<String> names,
Iterable<String> relaxedTargetNames) {
if (this.properties != null) { if (this.properties != null) {
return new MutablePropertyValues(this.properties); return new MutablePropertyValues(this.properties);
} }
return getPropertySourcesPropertyValues(names); return getPropertySourcesPropertyValues(names, relaxedTargetNames);
} }
private PropertyValues getPropertySourcesPropertyValues(Set<String> names) { private PropertyValues getPropertySourcesPropertyValues(Set<String> names,
PropertyNamePatternsMatcher includes = getPropertyNamePatternsMatcher(names); Iterable<String> relaxedTargetNames) {
PropertyNamePatternsMatcher includes = getPropertyNamePatternsMatcher(names,
relaxedTargetNames);
return new PropertySourcesPropertyValues(this.propertySources, names, includes); return new PropertySourcesPropertyValues(this.propertySources, names, includes);
} }
private PropertyNamePatternsMatcher getPropertyNamePatternsMatcher( private PropertyNamePatternsMatcher getPropertyNamePatternsMatcher(Set<String> names,
Set<String> names) { Iterable<String> relaxedTargetNames) {
if (this.ignoreUnknownFields && !isMapTarget()) { if (this.ignoreUnknownFields && !isMapTarget()) {
// Since unknown fields are ignored we can filter them out early to save // Since unknown fields are ignored we can filter them out early to save
// unnecessary calls to the PropertySource. // unnecessary calls to the PropertySource.
return new DefaultPropertyNamePatternsMatcher(EXACT_DELIMITERS, true, names); return new DefaultPropertyNamePatternsMatcher(EXACT_DELIMITERS, true, names);
} }
if (this.targetName != null) { if (relaxedTargetNames != null) {
// We can filter properties to those starting with the target name, but // We can filter properties to those starting with the target name, but
// we can't do a complete filter since we need to trigger the // we can't do a complete filter since we need to trigger the
// unknown fields check // unknown fields check
Set<String> relaxedNames = new HashSet<String>();
for (String relaxedTargetName : relaxedTargetNames) {
relaxedNames.add(relaxedTargetName);
}
return new DefaultPropertyNamePatternsMatcher(TARGET_NAME_DELIMITERS, true, return new DefaultPropertyNamePatternsMatcher(TARGET_NAME_DELIMITERS, true,
this.targetName); relaxedNames);
} }
// Not ideal, we basically can't filter anything // Not ideal, we basically can't filter anything
return PropertyNamePatternsMatcher.ALL; return PropertyNamePatternsMatcher.ALL;
......
...@@ -134,6 +134,36 @@ public class PropertiesConfigurationFactoryTests { ...@@ -134,6 +134,36 @@ public class PropertiesConfigurationFactoryTests {
assertEquals("blah", foo.name); assertEquals("blah", foo.name);
} }
@Test
public void testBindWithDelimitedPrefixUsingMatchingDelimiter() throws Exception {
this.targetName = "env_foo";
this.ignoreUnknownFields = false;
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addLast(new SystemEnvironmentPropertySource("systemEnvironment",
Collections.<String, Object>singletonMap("ENV_FOO_NAME", "blah")));
propertySources.addLast(new RandomValuePropertySource("random"));
setupFactory();
this.factory.setPropertySources(propertySources);
this.factory.afterPropertiesSet();
Foo foo = this.factory.getObject();
assertEquals("blah", foo.name);
}
@Test
public void testBindWithDelimitedPrefixUsingDifferentDelimiter() throws Exception {
this.targetName = "env.foo";
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addLast(new SystemEnvironmentPropertySource("systemEnvironment",
Collections.<String, Object>singletonMap("ENV_FOO_NAME", "blah")));
propertySources.addLast(new RandomValuePropertySource("random"));
this.ignoreUnknownFields = false;
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 { private Foo createFoo(final String values) throws Exception {
setupFactory(); setupFactory();
return bindFoo(values); return bindFoo(values);
......
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