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
This commit is contained in:
Andy Wilkinson 2015-12-15 12:52:58 +00:00
parent 5a7dece144
commit e203a689bf
2 changed files with 55 additions and 13 deletions

View File

@ -17,6 +17,7 @@
package org.springframework.boot.bind;
import java.beans.PropertyDescriptor;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Map;
@ -262,19 +263,23 @@ public class PropertiesConfigurationFactory<T>
dataBinder.setIgnoreInvalidFields(this.ignoreInvalidFields);
dataBinder.setIgnoreUnknownFields(this.ignoreUnknownFields);
customizeBinder(dataBinder);
Set<String> names = getNames();
PropertyValues propertyValues = getPropertyValues(names);
Iterable<String> relaxedTargetNames = getRelaxedTargetNames();
Set<String> names = getNames(relaxedTargetNames);
PropertyValues propertyValues = getPropertyValues(names, relaxedTargetNames);
dataBinder.bind(propertyValues);
if (this.validator != null) {
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>();
if (this.target != null) {
Iterable<String> prefixes = (StringUtils.hasLength(this.targetName)
? new RelaxedNames(this.targetName) : null);
PropertyDescriptor[] descriptors = BeanUtils
.getPropertyDescriptors(this.target.getClass());
for (PropertyDescriptor descriptor : descriptors) {
@ -300,31 +305,38 @@ public class PropertiesConfigurationFactory<T>
return names;
}
private PropertyValues getPropertyValues(Set<String> names) {
private PropertyValues getPropertyValues(Set<String> names,
Iterable<String> relaxedTargetNames) {
if (this.properties != null) {
return new MutablePropertyValues(this.properties);
}
return getPropertySourcesPropertyValues(names);
return getPropertySourcesPropertyValues(names, relaxedTargetNames);
}
private PropertyValues getPropertySourcesPropertyValues(Set<String> names) {
PropertyNamePatternsMatcher includes = getPropertyNamePatternsMatcher(names);
private PropertyValues getPropertySourcesPropertyValues(Set<String> names,
Iterable<String> relaxedTargetNames) {
PropertyNamePatternsMatcher includes = getPropertyNamePatternsMatcher(names,
relaxedTargetNames);
return new PropertySourcesPropertyValues(this.propertySources, names, includes);
}
private PropertyNamePatternsMatcher getPropertyNamePatternsMatcher(
Set<String> names) {
private PropertyNamePatternsMatcher getPropertyNamePatternsMatcher(Set<String> names,
Iterable<String> relaxedTargetNames) {
if (this.ignoreUnknownFields && !isMapTarget()) {
// Since unknown fields are ignored we can filter them out early to save
// unnecessary calls to the PropertySource.
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't do a complete filter since we need to trigger the
// unknown fields check
Set<String> relaxedNames = new HashSet<String>();
for (String relaxedTargetName : relaxedTargetNames) {
relaxedNames.add(relaxedTargetName);
}
return new DefaultPropertyNamePatternsMatcher(TARGET_NAME_DELIMITERS, true,
this.targetName);
relaxedNames);
}
// Not ideal, we basically can't filter anything
return PropertyNamePatternsMatcher.ALL;

View File

@ -134,6 +134,36 @@ public class PropertiesConfigurationFactoryTests {
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 {
setupFactory();
return bindFoo(values);