Create RelaxedPropertyResolver

Create RelaxedPropertyResolver class that can be used to get values
from another PropertyResolver (probably an Environment) using the
same relaxed rules as the RelaxedDataBinder.

The commit extracts the relaxed naming rules from RelaxedDataBinder
into a new RelaxedNames class.

Issue: #55621278
pull/50/head
Phillip Webb 11 years ago
parent d64a44547c
commit 0a7ac89984

@ -37,6 +37,7 @@ import org.springframework.validation.DataBinder;
* case for example).
*
* @author Dave Syer
* @see RelaxedNames
*/
public class RelaxedDataBinder extends DataBinder {
@ -224,10 +225,7 @@ public class RelaxedDataBinder extends DataBinder {
private String getActualPropertyName(BeanWrapper target, String prefix, String name) {
prefix = StringUtils.hasText(prefix) ? prefix + "." : "";
for (Variation variation : Variation.values()) {
for (Manipulation manipulation : Manipulation.values()) {
// Apply all manipulations before attempting variations
String candidate = variation.apply(manipulation.apply(name));
for (String candidate : new RelaxedNames(name)) {
try {
if (target.getPropertyType(prefix + candidate) != null) {
return candidate;
@ -237,63 +235,9 @@ public class RelaxedDataBinder extends DataBinder {
// swallow and continue
}
}
}
return name;
}
static enum Variation {
NONE {
@Override
public String apply(String value) {
return value;
}
},
UPPERCASE {
@Override
public String apply(String value) {
return value.toUpperCase();
}
},
LOWERCASE {
@Override
public String apply(String value) {
return value.toLowerCase();
}
};
public abstract String apply(String value);
}
static enum Manipulation {
NONE {
@Override
public String apply(String value) {
return value;
}
},
UNDERSCORE {
@Override
public String apply(String value) {
return value.replace("-", "_");
}
},
CAMELCASE {
@Override
public String apply(String value) {
StringBuilder builder = new StringBuilder();
for (String field : UNDERSCORE.apply(value).split("_")) {
builder.append(builder.length() == 0 ? field : StringUtils
.capitalize(field));
}
return builder.toString();
}
};
public abstract String apply(String value);
}
static class MapHolder {
private Map<String, Object> map;

@ -0,0 +1,135 @@
/*
* Copyright 2012-2013 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.Iterator;
import java.util.NoSuchElementException;
import org.springframework.util.StringUtils;
/**
* Generates relaxed name variations from a given source.
*
* @author Phillip Webb
* @author Dave Syer
* @see RelaxedDataBinder
* @see RelaxedPropertyResolver
*/
public final class RelaxedNames implements Iterable<String> {
private final String name;
/**
* Create a new {@link RelaxedNames} instance.
*
* @param name the source name. For the maximum number of variations specify the name
* using dashed notation (e.g. {@literal my-property-name}
*/
public RelaxedNames(String name) {
this.name = name;
}
@Override
public Iterator<String> iterator() {
return new RelaxedNamesIterator();
}
private class RelaxedNamesIterator implements Iterator<String> {
private int variation = 0;
private int manipulation = 0;
@Override
public boolean hasNext() {
return (this.variation < Variation.values().length);
}
@Override
public String next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
String result = RelaxedNames.this.name;
result = Manipulation.values()[this.manipulation].apply(result);
result = Variation.values()[this.variation].apply(result);
this.manipulation++;
if (this.manipulation >= Manipulation.values().length) {
this.variation++;
this.manipulation = 0;
}
return result;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
static enum Variation {
NONE {
@Override
public String apply(String value) {
return value;
}
},
LOWERCASE {
@Override
public String apply(String value) {
return value.toLowerCase();
}
},
UPPERCASE {
@Override
public String apply(String value) {
return value.toUpperCase();
}
};
public abstract String apply(String value);
}
static enum Manipulation {
NONE {
@Override
public String apply(String value) {
return value;
}
},
UNDERSCORE {
@Override
public String apply(String value) {
return value.replace("-", "_");
}
},
CAMELCASE {
@Override
public String apply(String value) {
StringBuilder builder = new StringBuilder();
for (String field : UNDERSCORE.apply(value).split("_")) {
builder.append(builder.length() == 0 ? field : StringUtils
.capitalize(field));
}
return builder.toString();
}
};
public abstract String apply(String value);
}
}

@ -0,0 +1,194 @@
/*
* Copyright 2012-2013 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.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.PropertyResolver;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources;
import org.springframework.util.Assert;
import static java.lang.String.format;
/**
* {@link PropertyResolver} that attempts to resolve values using {@link RelaxedNames}.
*
* @author Phillip Webb
* @see RelaxedNames
*/
public class RelaxedPropertyResolver implements PropertyResolver {
private final PropertyResolver resolver;
private final String prefix;
public RelaxedPropertyResolver(PropertyResolver resolver) {
this(resolver, null);
}
public RelaxedPropertyResolver(PropertyResolver resolver, String prefix) {
Assert.notNull(resolver, "PropertyResolver must not be null");
this.resolver = resolver;
this.prefix = (prefix == null ? "" : prefix);
}
@Override
public String getRequiredProperty(String key) throws IllegalStateException {
return getRequiredProperty(key, String.class);
}
@Override
public <T> T getRequiredProperty(String key, Class<T> targetType)
throws IllegalStateException {
T value = getProperty(key, targetType);
Assert.state(value != null, format("required key [%s] not found", key));
return value;
}
@Override
public String getProperty(String key) {
return getProperty(key, String.class, null);
}
@Override
public String getProperty(String key, String defaultValue) {
return getProperty(key, String.class, defaultValue);
}
@Override
public <T> T getProperty(String key, Class<T> targetType) {
return getProperty(key, targetType, null);
}
@Override
public <T> T getProperty(String key, Class<T> targetType, T defaultValue) {
for (String relaxedKey : new RelaxedNames(key)) {
if (this.resolver.containsProperty(this.prefix + relaxedKey)) {
return this.resolver.getProperty(this.prefix + relaxedKey, targetType,
defaultValue);
}
}
return defaultValue;
}
@Override
public <T> Class<T> getPropertyAsClass(String key, Class<T> targetType) {
for (String relaxedKey : new RelaxedNames(key)) {
if (this.resolver.containsProperty(this.prefix + relaxedKey)) {
return this.resolver.getPropertyAsClass(this.prefix + relaxedKey,
targetType);
}
}
return null;
}
@Override
public boolean containsProperty(String key) {
for (String relaxedKey : new RelaxedNames(key)) {
if (this.resolver.containsProperty(this.prefix + relaxedKey)) {
return true;
}
}
return false;
}
@Override
public String resolvePlaceholders(String text) {
throw new UnsupportedOperationException(
"Unable to resolve placeholders with relaxed properties");
}
@Override
public String resolveRequiredPlaceholders(String text)
throws IllegalArgumentException {
throw new UnsupportedOperationException(
"Unable to resolve placeholders with relaxed properties");
}
/**
* Return a Map of all values from all underlying properties that start with the
* specified key. NOTE: this method can only be used in the underlying resolver is a
* {@link ConfigurableEnvironment}.
* @param keyPrefix the key prefix used to filter results
* @return a map of all sub properties starting with the specified key prefix.
* @see #getSubProperties(PropertySources, RelaxedNames)
* @see #getSubProperties(PropertySources, String, RelaxedNames)
*/
public Map<String, Object> getSubProperties(String keyPrefix) {
Assert.isInstanceOf(ConfigurableEnvironment.class, this.resolver,
"SubProperties not available.");
ConfigurableEnvironment env = (ConfigurableEnvironment) this.resolver;
return getSubProperties(env.getPropertySources(), this.prefix, new RelaxedNames(
keyPrefix));
}
/**
* Return a Map of all values from the specified {@link PropertySources} that start
* with a particular key.
* @param propertySources the property sources to scan
* @param keyPrefix the key prefixes to test
* @return a map of all sub properties starting with the specified key prefixes.
* @see #getSubProperties(PropertySources, String, RelaxedNames)
*/
public static Map<String, Object> getSubProperties(PropertySources propertySources,
RelaxedNames keyPrefix) {
return getSubProperties(propertySources, null, keyPrefix);
}
/**
* Return a Map of all values from the specified {@link PropertySources} that start
* with a particular key.
* @param propertySources the property sources to scan
* @param rootPrefix a root prefix to be prepended to the keyPrefex (can be
* {@code null})
* @param keyPrefix the key prefixes to test
* @return a map of all sub properties starting with the specified key prefixes.
* @see #getSubProperties(PropertySources, String, RelaxedNames)
*/
public static Map<String, Object> getSubProperties(PropertySources propertySources,
String rootPrefix, RelaxedNames keyPrefix) {
Map<String, Object> subProperties = new LinkedHashMap<String, Object>();
for (PropertySource<?> source : propertySources) {
if (source instanceof EnumerablePropertySource) {
for (String name : ((EnumerablePropertySource<?>) source)
.getPropertyNames()) {
String key = getSubKey(name, rootPrefix, keyPrefix);
if (key != null) {
subProperties.put(key, source.getProperty(name));
}
}
}
}
return Collections.unmodifiableMap(subProperties);
}
private static String getSubKey(String name, String rootPrefix, RelaxedNames keyPrefix) {
rootPrefix = (rootPrefix == null ? "" : rootPrefix);
for (String candidateKeyPrefix : keyPrefix) {
if (name.startsWith(rootPrefix + candidateKeyPrefix)) {
return name.substring((rootPrefix + candidateKeyPrefix).length());
}
}
return null;
}
}

@ -0,0 +1,60 @@
/*
* Copyright 2012-2013 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.Iterator;
import org.junit.Test;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link RelaxedNames}.
*
* @author Phillip Webb
*/
public class RelaxedNamesTests {
@Test
public void iterator() throws Exception {
Iterator<String> iterator = new RelaxedNames("my-RELAXED-property").iterator();
assertThat(iterator.next(), equalTo("my-RELAXED-property"));
assertThat(iterator.next(), equalTo("my_RELAXED_property"));
assertThat(iterator.next(), equalTo("myRELAXEDProperty"));
assertThat(iterator.next(), equalTo("my-relaxed-property"));
assertThat(iterator.next(), equalTo("my_relaxed_property"));
assertThat(iterator.next(), equalTo("myrelaxedproperty"));
assertThat(iterator.next(), equalTo("MY-RELAXED-PROPERTY"));
assertThat(iterator.next(), equalTo("MY_RELAXED_PROPERTY"));
assertThat(iterator.next(), equalTo("MYRELAXEDPROPERTY"));
assertThat(iterator.hasNext(), equalTo(false));
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("nes_ted"));
assertThat(iterator.next(), equalTo("nes_ted"));
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));
}
}

@ -0,0 +1,159 @@
/*
* Copyright 2012-2013 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.LinkedHashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.StandardEnvironment;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link RelaxedPropertyResolver}.
*
* @author Phillip Webb
*/
public class RelaxedPropertyResolverTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
private StandardEnvironment environment;
private RelaxedPropertyResolver resolver;
private LinkedHashMap<String, Object> source;
@Before
public void setup() {
this.environment = new StandardEnvironment();
this.source = new LinkedHashMap<String, Object>();
this.source.put("myString", "value");
this.source.put("myInteger", 123);
this.source.put("myClass", "java.lang.String");
this.environment.getPropertySources().addFirst(
new MapPropertySource("test", this.source));
this.resolver = new RelaxedPropertyResolver(this.environment);
}
@Test
public void needsPropertyResolver() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("PropertyResolver must not be null");
new RelaxedPropertyResolver(null);
}
@Test
public void getRequiredProperty() throws Exception {
assertThat(this.resolver.getRequiredProperty("my-string"), equalTo("value"));
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("required key [my-missing] not found");
this.resolver.getRequiredProperty("my-missing");
}
@Test
public void getRequiredPropertyWithType() throws Exception {
assertThat(this.resolver.getRequiredProperty("my-integer", Integer.class),
equalTo(123));
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("required key [my-missing] not found");
this.resolver.getRequiredProperty("my-missing", Integer.class);
}
@Test
public void getProperty() throws Exception {
assertThat(this.resolver.getProperty("my-string"), equalTo("value"));
assertThat(this.resolver.getProperty("my-missing"), nullValue());
}
@Test
public void getPropertyWithDefault() throws Exception {
assertThat(this.resolver.getProperty("my-string", "a"), equalTo("value"));
assertThat(this.resolver.getProperty("my-missing", "a"), equalTo("a"));
}
@Test
public void getPropertyWithType() throws Exception {
assertThat(this.resolver.getProperty("my-integer", Integer.class), equalTo(123));
assertThat(this.resolver.getProperty("my-missing", Integer.class), nullValue());
}
@Test
public void getPropertyWithTypeAndDefault() throws Exception {
assertThat(this.resolver.getProperty("my-integer", Integer.class, 345),
equalTo(123));
assertThat(this.resolver.getProperty("my-missing", Integer.class, 345),
equalTo(345));
}
@Test
public void getPropertyAsClass() throws Exception {
assertThat(this.resolver.getPropertyAsClass("my-class", String.class),
equalTo(String.class));
assertThat(this.resolver.getPropertyAsClass("my-missing", String.class),
nullValue());
}
@Test
public void containsProperty() throws Exception {
assertThat(this.resolver.containsProperty("my-string"), equalTo(true));
assertThat(this.resolver.containsProperty("myString"), equalTo(true));
assertThat(this.resolver.containsProperty("my_string"), equalTo(true));
assertThat(this.resolver.containsProperty("my-missing"), equalTo(false));
}
@Test
public void resolverPlaceholder() throws Exception {
this.thrown.expect(UnsupportedOperationException.class);
this.resolver.resolvePlaceholders("test");
}
@Test
public void resolveRequiredPlaceholders() throws Exception {
this.thrown.expect(UnsupportedOperationException.class);
this.resolver.resolveRequiredPlaceholders("test");
}
@Test
public void prefixed() throws Exception {
this.resolver = new RelaxedPropertyResolver(this.environment, "a.b.c.");
this.source.put("a.b.c.d", "test");
assertThat(this.resolver.containsProperty("d"), equalTo(true));
assertThat(this.resolver.getProperty("d"), equalTo("test"));
}
@Test
public void subProperties() throws Exception {
this.source.put("x.y.my-sub.a.b", "1");
this.source.put("x.y.mySub.a.c", "2");
this.source.put("x.y.MY_SUB.a.d", "3");
this.resolver = new RelaxedPropertyResolver(this.environment, "x.y.");
Map<String, Object> subProperties = this.resolver.getSubProperties("my-sub.");
assertThat(subProperties.size(), equalTo(3));
assertThat(subProperties.get("a.b"), equalTo((Object) "1"));
assertThat(subProperties.get("a.c"), equalTo((Object) "2"));
assertThat(subProperties.get("a.d"), equalTo((Object) "3"));
}
}
Loading…
Cancel
Save