Commit f5a52ddd authored by Phillip Webb's avatar Phillip Webb

Optimize property sources pattern matching

Create a PropertyNamePatternsMatcher strategy interface that
PropertySourcesPropertyValues can use to determine when a property
can be used.

PropertiesConfigurationFactory uses DefaultPropertyNamePatternsMatcher
which is heavily optimized for frequent calls.

Fixes gh-1823
parent adffb7a3
/*
* Copyright 2012-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.bind;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* Default {@link PropertyNamePatternsMatcher} that matches when a property name exactly
* matches one of the given names, or starts with one of the given names followed by '.'
* or '_'. This implementation is optimized for frequent calls.
*
* @author Phillip Webb
* @since 1.2.0
*/
class DefaultPropertyNamePatternsMatcher implements PropertyNamePatternsMatcher {
private final String[] names;
public DefaultPropertyNamePatternsMatcher(String... names) {
this(new HashSet<String>(Arrays.asList(names)));
}
public DefaultPropertyNamePatternsMatcher(Set<String> names) {
this.names = names.toArray(new String[names.size()]);
}
@Override
public boolean matches(String propertyName) {
char[] propertNameChars = propertyName.toCharArray();
boolean[] match = new boolean[this.names.length];
boolean noneMatched = true;
for (int i = 0; i < this.names.length; i++) {
if (this.names[i].length() <= propertNameChars.length) {
match[i] = true;
noneMatched = false;
}
}
if (noneMatched) {
return false;
}
for (int charIndex = 0; charIndex < propertNameChars.length; charIndex++) {
noneMatched = true;
for (int nameIndex = 0; nameIndex < this.names.length; nameIndex++) {
if (match[nameIndex]) {
if (charIndex < this.names[nameIndex].length()) {
if (this.names[nameIndex].charAt(charIndex) == propertNameChars[charIndex]) {
match[nameIndex] = true;
noneMatched = false;
}
}
else {
char charAfter = propertNameChars[this.names[nameIndex].length()];
if (charAfter == '.' || charAfter == '_') {
match[nameIndex] = true;
noneMatched = false;
}
}
}
}
if (noneMatched) {
return false;
}
}
for (int i = 0; i < match.length; i++) {
if (match[i]) {
return true;
}
}
return false;
}
}
...@@ -249,25 +249,20 @@ public class PropertiesConfigurationFactory<T> implements FactoryBean<T>, ...@@ -249,25 +249,20 @@ public class PropertiesConfigurationFactory<T> implements FactoryBean<T>,
customizeBinder(dataBinder); customizeBinder(dataBinder);
Set<String> names = new HashSet<String>(); Set<String> names = new HashSet<String>();
Set<String> patterns = new HashSet<String>();
if (this.target != null) { if (this.target != null) {
PropertyDescriptor[] descriptors = BeanUtils PropertyDescriptor[] descriptors = BeanUtils
.getPropertyDescriptors(this.target.getClass()); .getPropertyDescriptors(this.target.getClass());
String prefix = (this.targetName != null ? this.targetName + "." : ""); String prefix = (this.targetName != null ? this.targetName + "." : "");
String[] suffixes = new String[] { ".*", "_*" };
for (PropertyDescriptor descriptor : descriptors) { for (PropertyDescriptor descriptor : descriptors) {
String name = descriptor.getName(); String name = descriptor.getName();
if (!name.equals("class")) { if (!name.equals("class")) {
for (String relaxedName : new RelaxedNames(prefix + name)) { for (String relaxedName : new RelaxedNames(prefix + name)) {
names.add(relaxedName); names.add(relaxedName);
patterns.add(relaxedName);
for (String suffix : suffixes) {
patterns.add(relaxedName + suffix);
}
} }
} }
} }
} }
PropertyNamePatternsMatcher patterns = new DefaultPropertyNamePatternsMatcher(names);
PropertyValues propertyValues = (this.properties != null ? new MutablePropertyValues( PropertyValues propertyValues = (this.properties != null ? new MutablePropertyValues(
this.properties) : new PropertySourcesPropertyValues( this.properties) : new PropertySourcesPropertyValues(
......
/*
* Copyright 2012-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.bind;
/**
* Strategy interface used to check if a property name matches specific criteria.
*
* @author Phillip Webb
* @since 1.2.0
*/
interface PropertyNamePatternsMatcher {
PropertyNamePatternsMatcher NONE = new PropertyNamePatternsMatcher() {
@Override
public boolean matches(String propertyName) {
return false;
}
};
/**
* Return {@code true} of the property name matches.
* @param propertyName the property name
* @return {@code true} if the property name matches
*/
boolean matches(String propertyName);
}
...@@ -19,6 +19,7 @@ package org.springframework.boot.bind; ...@@ -19,6 +19,7 @@ package org.springframework.boot.bind;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
...@@ -31,7 +32,6 @@ import org.springframework.core.env.PropertySource; ...@@ -31,7 +32,6 @@ 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.core.env.StandardEnvironment;
import org.springframework.util.PatternMatchUtils;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import org.springframework.validation.DataBinder; import org.springframework.validation.DataBinder;
...@@ -48,7 +48,7 @@ public class PropertySourcesPropertyValues implements PropertyValues { ...@@ -48,7 +48,7 @@ public class PropertySourcesPropertyValues implements PropertyValues {
private final PropertySources propertySources; private final PropertySources propertySources;
private static final Collection<String> NON_ENUMERABLE_ENUMERABLES = Arrays.asList( private static final Collection<String> PATTERN_MATCHED_PROPERTY_SOURCES = Arrays.asList(
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME); StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME);
...@@ -57,37 +57,48 @@ public class PropertySourcesPropertyValues implements PropertyValues { ...@@ -57,37 +57,48 @@ public class PropertySourcesPropertyValues implements PropertyValues {
* @param propertySources a PropertySources instance * @param propertySources a PropertySources instance
*/ */
public PropertySourcesPropertyValues(PropertySources propertySources) { public PropertySourcesPropertyValues(PropertySources propertySources) {
this(propertySources, null, null); this(propertySources, (PropertyNamePatternsMatcher) null,
(Collection<String>) null);
} }
/** /**
* Create a new PropertyValues from the given PropertySources * Create a new PropertyValues from the given PropertySources
* @param propertySources a PropertySources instance * @param propertySources a PropertySources instance
* @param patterns property name patterns to include from system properties and * @param includePatterns property name patterns to include from system properties and
* environment variables * environment variables
* @param names exact property names to include * @param names exact property names to include
*/ */
public PropertySourcesPropertyValues(PropertySources propertySources, public PropertySourcesPropertyValues(PropertySources propertySources,
Collection<String> patterns, Collection<String> names) { Collection<String> includePatterns, Collection<String> names) {
this(propertySources, new SimplePropertyNamePatternsMatcher(includePatterns), names);
}
/**
* Create a new PropertyValues from the given PropertySources
* @param propertySources a PropertySources instance
* @param includes property name patterns to include from system properties and
* environment variables
* @param names exact property names to include
*/
PropertySourcesPropertyValues(PropertySources propertySources,
PropertyNamePatternsMatcher includes, Collection<String> names) {
this.propertySources = propertySources; this.propertySources = propertySources;
if (includes == null) {
includes = PropertyNamePatternsMatcher.NONE;
}
if (names == null) {
names = Collections.emptySet();
}
PropertySourcesPropertyResolver resolver = new PropertySourcesPropertyResolver( PropertySourcesPropertyResolver resolver = new PropertySourcesPropertyResolver(
propertySources); propertySources);
String[] includes = toArray(patterns);
String[] exacts = toArray(names);
for (PropertySource<?> source : propertySources) { for (PropertySource<?> source : propertySources) {
processPropertySource(source, resolver, includes, exacts); processPropertySource(source, resolver, includes, names);
}
}
private String[] toArray(Collection<String> strings) {
if (strings == null) {
return new String[0];
} }
return strings.toArray(new String[strings.size()]);
} }
private void processPropertySource(PropertySource<?> source, private void processPropertySource(PropertySource<?> source,
PropertySourcesPropertyResolver resolver, String[] includes, String[] exacts) { PropertySourcesPropertyResolver resolver,
PropertyNamePatternsMatcher includes, Collection<String> exacts) {
if (source instanceof EnumerablePropertySource) { if (source instanceof EnumerablePropertySource) {
processEnumerablePropertySource((EnumerablePropertySource<?>) source, processEnumerablePropertySource((EnumerablePropertySource<?>) source,
resolver, includes, exacts); resolver, includes, exacts);
...@@ -104,12 +115,12 @@ public class PropertySourcesPropertyValues implements PropertyValues { ...@@ -104,12 +115,12 @@ public class PropertySourcesPropertyValues implements PropertyValues {
} }
private void processEnumerablePropertySource(EnumerablePropertySource<?> source, private void processEnumerablePropertySource(EnumerablePropertySource<?> source,
PropertySourcesPropertyResolver resolver, String[] includes, String[] exacts) { PropertySourcesPropertyResolver resolver,
PropertyNamePatternsMatcher includes, Collection<String> exacts) {
if (source.getPropertyNames().length > 0) { if (source.getPropertyNames().length > 0) {
for (String propertyName : source.getPropertyNames()) { for (String propertyName : source.getPropertyNames()) {
if (PropertySourcesPropertyValues.NON_ENUMERABLE_ENUMERABLES if (PropertySourcesPropertyValues.PATTERN_MATCHED_PROPERTY_SOURCES
.contains(source.getName()) .contains(source.getName()) && !includes.matches(propertyName)) {
&& !PatternMatchUtils.simpleMatch(includes, propertyName)) {
continue; continue;
} }
Object value = source.getProperty(propertyName); Object value = source.getProperty(propertyName);
...@@ -128,7 +139,8 @@ public class PropertySourcesPropertyValues implements PropertyValues { ...@@ -128,7 +139,8 @@ public class PropertySourcesPropertyValues implements PropertyValues {
} }
private void processCompositePropertySource(CompositePropertySource source, private void processCompositePropertySource(CompositePropertySource source,
PropertySourcesPropertyResolver resolver, String[] includes, String[] exacts) { PropertySourcesPropertyResolver resolver,
PropertyNamePatternsMatcher includes, Collection<String> exacts) {
for (PropertySource<?> nested : extractSources(source)) { for (PropertySource<?> nested : extractSources(source)) {
processPropertySource(nested, resolver, includes, exacts); processPropertySource(nested, resolver, includes, exacts);
} }
...@@ -151,7 +163,8 @@ public class PropertySourcesPropertyValues implements PropertyValues { ...@@ -151,7 +163,8 @@ public class PropertySourcesPropertyValues implements PropertyValues {
} }
private void processDefaultPropertySource(PropertySource<?> source, private void processDefaultPropertySource(PropertySource<?> source,
PropertySourcesPropertyResolver resolver, String[] includes, String[] exacts) { PropertySourcesPropertyResolver resolver,
PropertyNamePatternsMatcher includes, Collection<String> exacts) {
for (String propertyName : exacts) { for (String propertyName : exacts) {
Object value = null; Object value = null;
try { try {
......
/*
* Copyright 2012-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.bind;
import java.util.Collection;
import org.springframework.util.PatternMatchUtils;
/**
* {@link PropertyNamePatternsMatcher} that delegates to
* {@link PatternMatchUtils#simpleMatch(String[], String)}.
*
* @author Phillip Webb
* @since 1.2.0
*/
class SimplePropertyNamePatternsMatcher implements PropertyNamePatternsMatcher {
private final String[] patterns;
public SimplePropertyNamePatternsMatcher(Collection<String> patterns) {
this.patterns = (patterns == null ? new String[] {} : patterns
.toArray(new String[patterns.size()]));
}
@Override
public boolean matches(String propertyName) {
return PatternMatchUtils.simpleMatch(this.patterns, propertyName);
}
}
/*
* Copyright 2012-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.bind;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Tests for {@link DefaultPropertyNamePatternsMatcher}.
*
* @author Phillip Webb
*/
public class DefaultPropertyNamePatternsMatcherTests {
@Test
public void namesShorter() {
assertFalse(new DefaultPropertyNamePatternsMatcher("aaaa", "bbbb")
.matches("zzzzz"));
}
@Test
public void namesExactMatch() {
assertTrue(new DefaultPropertyNamePatternsMatcher("aaaa", "bbbb", "cccc")
.matches("bbbb"));
}
@Test
public void namesLonger() {
assertFalse(new DefaultPropertyNamePatternsMatcher("aaaaa", "bbbbb", "ccccc")
.matches("bbbb"));
}
@Test
public void nameWithDot() throws Exception {
assertTrue(new DefaultPropertyNamePatternsMatcher("aaaa", "bbbb", "cccc")
.matches("bbbb.anything"));
}
@Test
public void nameWithUnderscore() throws Exception {
assertTrue(new DefaultPropertyNamePatternsMatcher("aaaa", "bbbb", "cccc")
.matches("bbbb_anything"));
}
@Test
public void namesMatchWithDifferentLengths() throws Exception {
assertTrue(new DefaultPropertyNamePatternsMatcher("aaa", "bbbb", "ccccc")
.matches("bbbb"));
}
}
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package org.springframework.boot.bind; package org.springframework.boot.bind;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import org.junit.Before; import org.junit.Before;
...@@ -98,7 +99,8 @@ public class PropertySourcesPropertyValuesTests { ...@@ -98,7 +99,8 @@ public class PropertySourcesPropertyValuesTests {
}); });
PropertySourcesPropertyValues propertyValues = new PropertySourcesPropertyValues( PropertySourcesPropertyValues propertyValues = new PropertySourcesPropertyValues(
this.propertySources, null, Collections.singleton("baz")); this.propertySources, (Collection<String>) null,
Collections.singleton("baz"));
assertEquals("bar", propertyValues.getPropertyValue("baz").getValue()); assertEquals("bar", propertyValues.getPropertyValue("baz").getValue());
} }
...@@ -123,8 +125,8 @@ public class PropertySourcesPropertyValuesTests { ...@@ -123,8 +125,8 @@ public class PropertySourcesPropertyValuesTests {
public void testPlaceholdersBindingNonEnumerable() { public void testPlaceholdersBindingNonEnumerable() {
FooBean target = new FooBean(); FooBean target = new FooBean();
DataBinder binder = new DataBinder(target); DataBinder binder = new DataBinder(target);
binder.bind(new PropertySourcesPropertyValues(this.propertySources, null, binder.bind(new PropertySourcesPropertyValues(this.propertySources,
Collections.singleton("foo"))); (Collection<String>) null, Collections.singleton("foo")));
assertEquals("bar", target.getFoo()); assertEquals("bar", target.getFoo());
} }
...@@ -148,8 +150,8 @@ public class PropertySourcesPropertyValuesTests { ...@@ -148,8 +150,8 @@ public class PropertySourcesPropertyValuesTests {
return new Object(); return new Object();
} }
}); });
binder.bind(new PropertySourcesPropertyValues(this.propertySources, null, binder.bind(new PropertySourcesPropertyValues(this.propertySources,
Collections.singleton("name"))); (Collection<String>) null, Collections.singleton("name")));
assertEquals(null, target.getName()); assertEquals(null, target.getName());
} }
......
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