Commit a5651b75 authored by Phillip Webb's avatar Phillip Webb

Create ConfigurationPropertySource abstraction

Add a new abstraction that represents a source for configuration
properties. The new source is similar to the `Environment` abstraction
provided by Spring Framework but follows a stricter set of rules.

The `ConfigurationPropertySource` provides a uniform view onto a source
and will help to move responsibility for accessing properties in a
"relaxed" way away from the caller.

The `ConfigurationPropertyName` class enforces strict naming rules
that callers must follow when accessing properties. Configuration
names are lowercase, dot separated and may contain dashes. In
addition "indexed" name elements may be defined by using square
brackets.

Mapping is provided to existing Spring PropertySources implemented with
the relaxed rules that users are used to. For example the configuration
property `server.local-host` can still be written in a property files
as `server.localHost` or in an environment variable as SERVER_LOCALHOST.

Closes gh-4910
parent dbc7e938
/*
* Copyright 2012-2014 the original author or authors.
* Copyright 2012-2017 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.
......@@ -15,8 +15,7 @@
*/
/**
* Support for external configuration binding via the {@code @ConfigurationProperties}
* annotation.
* Support for external configuration properties.
*
* @see org.springframework.boot.context.properties.ConfigurationProperties
* @see org.springframework.boot.context.properties.EnableConfigurationProperties
......
/*
* Copyright 2012-2017 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.context.properties.source;
import java.util.List;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
* A {@link ConfigurationPropertySource} supporting name aliases.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class AliasedConfigurationPropertySource implements ConfigurationPropertySource {
private final ConfigurationPropertySource source;
private final ConfigurationPropertyNameAliases aliases;
AliasedConfigurationPropertySource(ConfigurationPropertySource source,
ConfigurationPropertyNameAliases aliases) {
Assert.notNull(source, "Source must not be null");
Assert.notNull(aliases, "Aliases must not be null");
this.source = source;
this.aliases = aliases;
}
@Override
public Stream<ConfigurationPropertyName> stream() {
return StreamSupport.stream(this.source.spliterator(), false)
.flatMap(this::addAliases);
}
private Stream<ConfigurationPropertyName> addAliases(ConfigurationPropertyName name) {
Stream<ConfigurationPropertyName> names = Stream.of(name);
List<ConfigurationPropertyName> aliases = this.aliases.getAliases(name);
if (CollectionUtils.isEmpty(aliases)) {
return names;
}
return Stream.concat(names, aliases.stream());
}
@Override
public ConfigurationProperty getConfigurationProperty(
ConfigurationPropertyName name) {
Assert.notNull(name, "Name must not be null");
ConfigurationProperty result = this.source.getConfigurationProperty(name);
if (result == null) {
ConfigurationPropertyName aliasedName = this.aliases.getNameForAlias(name);
result = this.source.getConfigurationProperty(aliasedName);
}
return result;
}
}
/*
* Copyright 2012-2017 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.context.properties.source;
import org.springframework.boot.origin.Origin;
import org.springframework.boot.origin.OriginProvider;
import org.springframework.boot.origin.OriginTrackedValue;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* A single configuration property obtained from a {@link ConfigurationPropertySource}
* consisting of a {@link #getName() name}, {@link #getValue() value} and optional
* {@link #getOrigin() origin}.
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.0.0
*/
public final class ConfigurationProperty
implements OriginProvider, Comparable<ConfigurationProperty> {
private final ConfigurationPropertyName name;
private final Object value;
private final Origin origin;
public ConfigurationProperty(ConfigurationPropertyName name, Object value,
Origin origin) {
Assert.notNull(name, "Name must not be null");
Assert.notNull(value, "Value must not be null");
this.name = name;
this.value = value;
this.origin = origin;
}
public ConfigurationPropertyName getName() {
return this.name;
}
public Object getValue() {
return this.value;
}
@Override
public Origin getOrigin() {
return this.origin;
}
@Override
public int hashCode() {
int result = ObjectUtils.nullSafeHashCode(this.name);
result = 31 * result + ObjectUtils.nullSafeHashCode(this.value);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
ConfigurationProperty other = (ConfigurationProperty) obj;
boolean result = true;
result = result && ObjectUtils.nullSafeEquals(this.name, other.name);
result = result && ObjectUtils.nullSafeEquals(this.value, other.value);
return result;
}
@Override
public String toString() {
return new ToStringCreator(this).append("name", this.name)
.append("value", this.value).append("origin", this.origin).toString();
}
@Override
public int compareTo(ConfigurationProperty other) {
return this.name.compareTo(other.name);
}
static ConfigurationProperty of(ConfigurationPropertyName name,
OriginTrackedValue value) {
if (value == null) {
return null;
}
return new ConfigurationProperty(name, value.getValue(), value.getOrigin());
}
static ConfigurationProperty of(ConfigurationPropertyName name, Object value,
Origin origin) {
if (value == null) {
return null;
}
return new ConfigurationProperty(name, value, origin);
}
}
/*
* Copyright 2012-2017 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.context.properties.source;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* Maintains a mapping of {@link ConfigurationPropertyName} alaises.
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.0.0
* @see ConfigurationPropertySource#withAliases(ConfigurationPropertyNameAliases)
*/
public final class ConfigurationPropertyNameAliases {
private final MultiValueMap<ConfigurationPropertyName, ConfigurationPropertyName> aliases = new LinkedMultiValueMap<>();
public ConfigurationPropertyNameAliases() {
}
public ConfigurationPropertyNameAliases(String name, String... aliases) {
addAlaises(name, aliases);
}
public ConfigurationPropertyNameAliases(ConfigurationPropertyName name,
ConfigurationPropertyName... aliases) {
addAlaises(name, aliases);
}
public void addAlaises(String name, String... aliases) {
Assert.notNull(name, "Name must not be null");
Assert.notNull(aliases, "Aliases must not be null");
addAlaises(ConfigurationPropertyName.of(name),
Arrays.stream(aliases).map(ConfigurationPropertyName::of)
.toArray(ConfigurationPropertyName[]::new));
}
public void addAlaises(ConfigurationPropertyName name,
ConfigurationPropertyName... aliases) {
Assert.notNull(name, "Name must not be null");
Assert.notNull(aliases, "Aliases must not be null");
this.aliases.addAll(name, Arrays.asList(aliases));
}
public List<ConfigurationPropertyName> getAliases(ConfigurationPropertyName name) {
return this.aliases.getOrDefault(name, Collections.emptyList());
}
public ConfigurationPropertyName getNameForAlias(ConfigurationPropertyName alias) {
return this.aliases.entrySet().stream()
.filter((e) -> e.getValue().contains(alias)).map(Map.Entry::getKey)
.findFirst().orElse(null);
}
}
/*
* Copyright 2012-2017 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.context.properties.source;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.regex.Pattern;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName.Element;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName.Form;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
* Builder class that can be used to create {@link ConfigurationPropertyName
* ConfigurationPropertyNames}. This class is intended for use within custom
* {@link ConfigurationPropertySource} implementations. When accessing
* {@link ConfigurationProperty properties} from and existing
* {@link ConfigurationPropertySource source} the
* {@link ConfigurationPropertyName#of(String)} method should be used to obtain a
* {@link ConfigurationPropertyName name}.
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.0.0
* @see ConfigurationPropertyName
*/
public class ConfigurationPropertyNameBuilder {
private final ElementValueProcessor processor;
private final List<Element> elements;
/**
* Create a new {@link ConfigurationPropertyNameBuilder} instance.
*/
public ConfigurationPropertyNameBuilder() {
this(ElementValueProcessor.empty());
}
/**
* Create a new {@link ConfigurationPropertyNameBuilder} instance that enforces a
* {@link Pattern} on all element values.
* @param elementValuePattern the element value pattern to enforce
*/
public ConfigurationPropertyNameBuilder(Pattern elementValuePattern) {
this(ElementValueProcessor.empty().withPatternCheck(elementValuePattern));
}
/**
* Create a new {@link ConfigurationPropertyNameBuilder} with the specified
* {@link ElementValueProcessor}.
* @param processor the element value processor.
*/
public ConfigurationPropertyNameBuilder(ElementValueProcessor processor) {
Assert.notNull(processor, "Processor must not be null");
this.processor = processor;
this.elements = Collections.emptyList();
}
/**
* Internal constructor used to create new builders.
* @param processor the element value processor.
* @param elements the elements built so far
*/
private ConfigurationPropertyNameBuilder(ElementValueProcessor processor,
List<Element> elements) {
this.processor = processor;
this.elements = elements;
}
/**
* Start building using the specified name split up into elements using a known
* separator. For example {@code from("foo.bar", '.')} will return a new builder
* containing the elements "{@code foo}" and "{@code bar}". Any element in square
* brackets will be considered "indexed" and will not be considered for splitting.
* @param name the name build from
* @param separator the separator
* @return a builder with elements populated from the name
*/
public ConfigurationPropertyNameBuilder from(String name, char separator) {
Assert.notNull(name, "Name must not be null");
List<Element> elements = new ArrayList<>();
StringBuilder value = new StringBuilder(name.length());
boolean indexed = false;
for (int i = 0; i < name.length(); i++) {
char ch = name.charAt(i);
if (!indexed) {
if (ch == '[') {
addElement(elements, value);
value.append(ch);
indexed = true;
}
else if (ch == separator) {
addElement(elements, value);
}
else {
value.append(ch);
}
}
else {
value.append(ch);
if (ch == ']') {
addElement(elements, value);
indexed = false;
}
}
}
addElement(elements, value);
return from(elements.stream().filter(Objects::nonNull)
.filter((e) -> !e.getValue(Form.UNIFORM).isEmpty()).iterator());
}
private void addElement(List<Element> elements, StringBuilder value) {
if (value.length() > 0) {
elements.add(buildElement(value.toString()));
value.setLength(0);
}
}
/**
* Return a new {@link ConfigurationPropertyNameBuilder} starting with the specified
* elements.
* @param elements the elements that the new builder should contain
* @return a new initialized builder instance
*/
public ConfigurationPropertyNameBuilder from(Iterable<Element> elements) {
return from(elements.iterator());
}
/**
* Return a new {@link ConfigurationPropertyNameBuilder} starting with the specified
* elements.
* @param elements the elements that the new builder should contain
* @return a new initialized builder instance
*/
public ConfigurationPropertyNameBuilder from(Iterator<Element> elements) {
Assert.state(CollectionUtils.isEmpty(this.elements),
"Existing elements must not be present");
return new ConfigurationPropertyNameBuilder(this.processor, toList(elements));
}
private List<Element> toList(Iterator<Element> iterator) {
List<Element> list = new ArrayList<>();
while (iterator.hasNext()) {
list.add(iterator.next());
}
if (isRoot(list)) {
return Collections.emptyList();
}
return list;
}
private boolean isRoot(List<Element> list) {
return (list.size() == 1 && list.get(0).getValue(Form.ORIGINAL).isEmpty());
}
/**
* Return a new builder containing the elements built so far appended with the
* specified element value.
* @param elementValue the element value to append
* @return a new builder instance
*/
public ConfigurationPropertyNameBuilder append(String elementValue) {
Assert.notNull(elementValue, "ElementValue must not be null");
List<Element> elements = new ArrayList<>(this.elements);
elements.add(buildElement(elementValue));
return new ConfigurationPropertyNameBuilder(this.processor, elements);
}
private Element buildElement(String value) {
return new Element(this.processor.apply(value));
}
/**
* Build a new {@link ConfigurationPropertyName} from the elements contained in this
* builder.
* @return a new {@link ConfigurationPropertyName}.
*/
public ConfigurationPropertyName build() {
ConfigurationPropertyName name = null;
for (Element element : this.elements) {
name = new ConfigurationPropertyName(name, element);
}
Assert.state(name != null, "At least one element must be defined");
return name;
}
/**
* An processor that will be applied to element values. Can be used to manipulate or
* restrict the values that are used.
*/
@FunctionalInterface
public interface ElementValueProcessor {
/**
* Apply the processor to the specified value.
* @param value the value to process
* @return the processed value
* @throws RuntimeException if the value cannot be used
*/
String apply(String value) throws RuntimeException;
/**
* Extend this processor with a {@link Pattern} regular expression check.
* @param pattern the patter to check
* @return an element processor that additionally checks against the pattern
*/
default ElementValueProcessor withPatternCheck(Pattern pattern) {
Assert.notNull(pattern, "Pattern must not be null");
return (value) -> {
value = apply(value);
Element element = new Element(value);
Assert.isTrue(element.isIndexed() || pattern.matcher(value).matches(),
"Element value '" + value + "' is not valid (" + pattern
+ " does not match)");
return value;
};
}
/**
* Return an empty {@link ElementValueProcessor} that simply returns the original
* value unchanged.
* @return an empty {@link ElementValueProcessor}.
*/
static ElementValueProcessor empty() {
return (value) -> value;
}
}
}
/*
* Copyright 2012-2017 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.context.properties.source;
import java.util.Iterator;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.springframework.boot.origin.OriginTrackedValue;
import org.springframework.core.env.PropertySource;
/**
* A source of {@link ConfigurationProperty ConfigurationProperties}, usually backed by a
* Spring {@link PropertySource}.
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.0.0
* @see ConfigurationPropertyName
* @see OriginTrackedValue
* @see #getConfigurationProperty(ConfigurationPropertyName)
*/
public interface ConfigurationPropertySource extends Iterable<ConfigurationPropertyName> {
/**
* Return a single {@link ConfigurationProperty} from the source or {@code null} if no
* property can be found.
* @param name the name of the property (must not be {@code null})
* @return the associated object or {@code null}.
*/
ConfigurationProperty getConfigurationProperty(ConfigurationPropertyName name);
/**
* Return an iterator for the {@link ConfigurationPropertyName names} managed by this
* source. If it is not possible to determine the names an empty iterator may be
* returned.
* @return an iterator (never {@code null})
*/
@Override
default Iterator<ConfigurationPropertyName> iterator() {
return stream().iterator();
}
/**
* Returns a sequential {@code Stream} for the {@link ConfigurationPropertyName names}
* managed by this source. If it is not possible to determine the names an
* {@link Stream#empty() empty stream} may be returned.
* @return a stream of names (never {@code null})
*/
Stream<ConfigurationPropertyName> stream();
/**
* Return a filtered variant of this source, containing only names that match the
* given {@link Predicate}.
* @param filter the filter to apply
* @return a filtered {@link ConfigurationPropertySource} instance
*/
default ConfigurationPropertySource filter(
Predicate<ConfigurationPropertyName> filter) {
return new FilteredConfigurationPropertiesSource(this, filter);
}
/**
* Return a variant of this source that supports name aliases.
* @param aliases a function that returns a stream of aliases for any given name
* @return a {@link ConfigurationPropertySource} instance supporting name alaises
*/
default ConfigurationPropertySource withAliases(
ConfigurationPropertyNameAliases aliases) {
return new AliasedConfigurationPropertySource(this, aliases);
}
}
/*
* Copyright 2012-2017 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.context.properties.source;
import java.util.Iterator;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources;
import org.springframework.core.env.PropertySourcesPropertyResolver;
import org.springframework.core.env.SystemEnvironmentPropertySource;
import org.springframework.util.Assert;
/**
* A managed set of {@link ConfigurationPropertySource} instances, usually adapted from
* Spring's {@link PropertySources}.
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.0.0
* @see #attach(MutablePropertySources)
* @see #get(PropertySources)
*/
public class ConfigurationPropertySources
implements Iterable<ConfigurationPropertySource> {
/**
* The name of the {@link PropertySource} {@link #adapt adapter}.
*/
public static final String PROPERTY_SOURCE_NAME = "configurationPropertes";
private final PropertySources propertySources;
private final Map<PropertySource<?>, ConfigurationPropertySource> adapters = new WeakHashMap<>();
/**
* Create a new {@link ConfigurationPropertySources} instance.
* @param propertySources the property sources to expose
*/
ConfigurationPropertySources(PropertySources propertySources) {
Assert.notNull(propertySources, "PropertySources must not be null");
this.propertySources = propertySources;
}
@Override
public Iterator<ConfigurationPropertySource> iterator() {
return streamPropertySources(this.propertySources)
.filter(s -> !(s instanceof ConfigurationPropertySourcesPropertySource))
.map(this::adapt).collect(Collectors.toList()).iterator();
}
private Stream<PropertySource<?>> streamPropertySources(PropertySources sources) {
return StreamSupport.stream(sources.spliterator(), false).flatMap(this::flatten);
}
private Stream<PropertySource<?>> flatten(PropertySource<?> source) {
if (source.getSource() instanceof ConfigurableEnvironment) {
return streamPropertySources(
((ConfigurableEnvironment) source.getSource()).getPropertySources());
}
return Stream.of(source);
}
private ConfigurationPropertySource adapt(PropertySource<?> source) {
return this.adapters.computeIfAbsent(source, (k) -> {
return new PropertySourceConfigurationPropertySource(source,
getPropertyMapper(source));
});
}
private PropertyMapper getPropertyMapper(PropertySource<?> source) {
if (source instanceof SystemEnvironmentPropertySource) {
return SystemEnvironmentPropertyMapper.INSTANCE;
}
return DefaultPropertyMapper.INSTANCE;
}
/**
* Attach a {@link ConfigurationPropertySources} instance to the specified
* {@link ConfigurableEnvironment} so that classic
* {@link PropertySourcesPropertyResolver} calls will resolve using
* {@link ConfigurationPropertyName configuration property names}.
* @param environment the source environment
* @return the instance attached
*/
public static ConfigurationPropertySources attach(
ConfigurableEnvironment environment) {
return attach(environment.getPropertySources());
}
/**
* Attach a {@link ConfigurationPropertySources} instance to the specified
* {@link PropertySources} so that classic {@link PropertySourcesPropertyResolver}
* calls will resolve using using {@link ConfigurationPropertyName configuration
* property names}.
* @param propertySources the source property sources
* @return the instance attached
*/
public static ConfigurationPropertySources attach(
MutablePropertySources propertySources) {
ConfigurationPropertySources adapted = new ConfigurationPropertySources(
propertySources);
propertySources.addFirst(new ConfigurationPropertySourcesPropertySource(
PROPERTY_SOURCE_NAME, adapted));
return adapted;
}
/**
* Get a {@link ConfigurationPropertySources} instance for the specified
* {@link PropertySources} (either previously {@link #attach(MutablePropertySources)
* attached} or a new instance.
* @param propertySources the source property sources
* @return a {@link ConfigurationPropertySources} instance
*/
public static ConfigurationPropertySources get(PropertySources propertySources) {
if (propertySources == null) {
return null;
}
PropertySource<?> source = propertySources.get(PROPERTY_SOURCE_NAME);
if (source != null) {
return (ConfigurationPropertySources) source.getSource();
}
return new ConfigurationPropertySources(propertySources);
}
/**
* Get a {@link ConfigurationPropertySources} instance for the {@link PropertySources}
* from the specified {@link ConfigurableEnvironment}, (either previously
* {@link #attach(MutablePropertySources) attached} or a new instance.
* @param environment the configurable environment
* @return a {@link ConfigurationPropertySources} instance
*/
public static ConfigurationPropertySources get(ConfigurableEnvironment environment) {
MutablePropertySources propertySources = environment.getPropertySources();
PropertySource<?> source = propertySources.get(PROPERTY_SOURCE_NAME);
if (source != null) {
return (ConfigurationPropertySources) source.getSource();
}
return new ConfigurationPropertySources(propertySources);
}
}
/*
* Copyright 2012-2017 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.context.properties.source;
import java.util.Objects;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.springframework.boot.origin.Origin;
import org.springframework.boot.origin.OriginLookup;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertyResolver;
import org.springframework.core.env.PropertySource;
/**
* {@link PropertySource} that exposes {@link ConfigurationPropertySource} instances so
* that they can be used with a {@link PropertyResolver} or added to the
* {@link Environment}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class ConfigurationPropertySourcesPropertySource
extends PropertySource<Iterable<ConfigurationPropertySource>>
implements OriginLookup<String> {
ConfigurationPropertySourcesPropertySource(String name,
Iterable<ConfigurationPropertySource> source) {
super(name, source);
}
@Override
public Object getProperty(String name) {
ConfigurationProperty configurationProperty = findConfigurationProperty(name);
return (configurationProperty == null ? null : configurationProperty.getValue());
}
@Override
public Origin getOrigin(String name) {
return Origin.from(findConfigurationProperty(name));
}
private ConfigurationProperty findConfigurationProperty(String name) {
try {
return findConfigurationProperty(ConfigurationPropertyName.of(name));
}
catch (Exception ex) {
return null;
}
}
private ConfigurationProperty findConfigurationProperty(
ConfigurationPropertyName name) {
if (name == null) {
return null;
}
Stream<ConfigurationPropertySource> sources = StreamSupport
.stream(getSource().spliterator(), false);
return sources.map(source -> source.getConfigurationProperty(name))
.filter(Objects::nonNull).findFirst().orElse(null);
}
}
/*
* Copyright 2012-2017 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.context.properties.source;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.core.env.PropertySource;
/**
* Default {@link PropertyMapper} implementation. Names are mapped by removing invalid
* characters and converting to lower case. For example "{@code my.server_name.PORT}" is
* mapped to "{@code my.servername.port}".
*
* @author Phillip Webb
* @author Madhura Bhave
* @see PropertyMapper
* @see PropertySourceConfigurationPropertySource
*/
class DefaultPropertyMapper implements PropertyMapper {
public static final PropertyMapper INSTANCE = new DefaultPropertyMapper();
private Cache<ConfigurationPropertyName> configurationPropertySourceCache = new Cache<>();
private Cache<String> propertySourceCache = new Cache<>();
private final ConfigurationPropertyNameBuilder nameBuilder = new ConfigurationPropertyNameBuilder();
@Override
public List<PropertyMapping> map(PropertySource<?> propertySource,
ConfigurationPropertyName configurationPropertyName) {
List<PropertyMapping> mapping = this.configurationPropertySourceCache
.get(configurationPropertyName);
if (mapping == null) {
String convertedName = configurationPropertyName.toString();
mapping = Collections.singletonList(
new PropertyMapping(convertedName, configurationPropertyName));
}
return mapping;
}
@Override
public List<PropertyMapping> map(PropertySource<?> propertySource,
String propertySourceName) {
List<PropertyMapping> mapping = this.propertySourceCache.get(propertySourceName);
if (mapping == null) {
mapping = tryMap(propertySourceName);
this.propertySourceCache.put(propertySourceName, mapping);
}
return mapping;
}
private List<PropertyMapping> tryMap(String propertySourceName) {
try {
ConfigurationPropertyName convertedName = this.nameBuilder
.from(propertySourceName, '.').build();
PropertyMapping o = new PropertyMapping(propertySourceName, convertedName);
return Collections.singletonList(o);
}
catch (Exception ex) {
return Collections.emptyList();
}
}
private static class Cache<K> extends LinkedHashMap<K, List<PropertyMapping>> {
private final int capacity;
Cache() {
this(1);
}
Cache(int capacity) {
super(capacity, (float) 0.75, true);
this.capacity = capacity;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, List<PropertyMapping>> eldest) {
if (size() < this.capacity) {
return false;
}
return true;
}
}
}
/*
* Copyright 2012-2017 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.context.properties.source;
import java.util.function.Predicate;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.springframework.util.Assert;
/**
* A filtered {@link ConfigurationPropertySource}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class FilteredConfigurationPropertiesSource implements ConfigurationPropertySource {
private final ConfigurationPropertySource source;
private final Predicate<ConfigurationPropertyName> filter;
FilteredConfigurationPropertiesSource(ConfigurationPropertySource source,
Predicate<ConfigurationPropertyName> filter) {
Assert.notNull(source, "Source must not be null");
Assert.notNull(filter, "Filter must not be null");
this.source = source;
this.filter = filter;
}
@Override
public Stream<ConfigurationPropertyName> stream() {
return StreamSupport.stream(this.source.spliterator(), false).filter(this.filter);
}
@Override
public ConfigurationProperty getConfigurationProperty(
ConfigurationPropertyName name) {
return (this.filter.test(name) ? this.source.getConfigurationProperty(name)
: null);
}
}
/*
* Copyright 2012-2017 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.context.properties.source;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Stream;
import org.springframework.core.env.MapPropertySource;
import org.springframework.util.Assert;
/**
* An {@link ConfigurationPropertySource} backed by a {@link Map} and using standard name
* mapping rules.
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.0.0
*/
public class MapConfigurationPropertySource implements ConfigurationPropertySource {
private final Map<String, Object> source;
private final ConfigurationPropertySource delegate;
/**
* Create a new empty {@link MapConfigurationPropertySource} instance.
*/
public MapConfigurationPropertySource() {
this(Collections.emptyMap());
}
/**
* Create a new {@link MapConfigurationPropertySource} instance with entries copies
* from the specified map.
* @param map the source map
*/
public MapConfigurationPropertySource(Map<?, ?> map) {
this.source = new LinkedHashMap<>();
this.delegate = new PropertySourceConfigurationPropertySource(
new MapPropertySource("source", this.source),
new DefaultPropertyMapper());
putAll(map);
}
/**
* Add all enties from the specified map.
* @param map the source map
*/
public void putAll(Map<?, ?> map) {
Assert.notNull(map, "Map must not be null");
map.forEach(this::put);
}
/**
* Add an individual entry.
* @param name the name
* @param value the value
*/
public void put(Object name, Object value) {
this.source.put((name == null ? null : name.toString()), value);
}
@Override
public ConfigurationProperty getConfigurationProperty(
ConfigurationPropertyName name) {
return this.delegate.getConfigurationProperty(name);
}
@Override
public Iterator<ConfigurationPropertyName> iterator() {
return this.delegate.iterator();
}
@Override
public Stream<ConfigurationPropertyName> stream() {
return this.delegate.stream();
}
}
/*
* Copyright 2012-2017 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.context.properties.source;
import java.util.List;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.PropertySource;
/**
* Strategy used to provide a mapping between a {@link PropertySource} and a
* {@link ConfigurationPropertySource}.
* <P>
* Mappings should be provided for both {@link ConfigurationPropertyName
* ConfigurationPropertyName} types and {@code String} based names. This allows the
* {@link PropertySourceConfigurationPropertySource} to first attempt any direct mappings
* (i.e. map the {@link ConfigurationPropertyName} directly to the {@link PropertySource}
* name) before falling back to {@link EnumerablePropertySource enumerating} property
* names, mapping them to a {@link ConfigurationPropertyName} and checking for
* {@link PropertyMapping#isApplicable(ConfigurationPropertyName) applicability}. See
* {@link PropertySourceConfigurationPropertySource} for more details.
*
* @author Phillip Webb
* @author Madhura Bhave
* @see PropertySourceConfigurationPropertySource
*/
interface PropertyMapper {
/**
* Provide mappings from a {@link ConfigurationPropertySource}
* {@link ConfigurationPropertyName}.
* @param propertySource the property source
* @param configurationPropertyName the name to map
* @return a stream of mappings or {@code Stream#empty()}
*/
List<PropertyMapping> map(PropertySource<?> propertySource,
ConfigurationPropertyName configurationPropertyName);
/**
* Provide mappings from a {@link PropertySource} property name.
* @param propertySource the property source
* @param propertySourceName the name to map
* @return a stream of mappings or {@code Stream#empty()}
*/
List<PropertyMapping> map(PropertySource<?> propertySource,
String propertySourceName);
}
/*
* Copyright 2012-2017 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.context.properties.source;
import java.util.function.Function;
import org.springframework.core.env.PropertySource;
/**
* Details a mapping between a {@link PropertySource} item and a
* {@link ConfigurationPropertySource} item.
*
* @author Phillip Webb
* @author Madhura Bhave
* @see PropertySourceConfigurationPropertySource
*/
class PropertyMapping {
private final String propertySourceName;
private final ConfigurationPropertyName configurationPropertyName;
private final Function<Object, Object> valueExtractor;
/**
* Create a new {@link PropertyMapper} instance.
* @param propertySourceName the {@link PropertySource} name
* @param configurationPropertyName the {@link ConfigurationPropertySource}
* {@link ConfigurationPropertyName}
*/
PropertyMapping(String propertySourceName,
ConfigurationPropertyName configurationPropertyName) {
this(propertySourceName, configurationPropertyName, Function.identity());
}
/**
* Create a new {@link PropertyMapper} instance.
* @param propertySourceName the {@link PropertySource} name
* @param configurationPropertyName the {@link ConfigurationPropertySource}
* {@link ConfigurationPropertyName}
* @param valueExtractor the extractor used to obtain the value
*/
PropertyMapping(String propertySourceName,
ConfigurationPropertyName configurationPropertyName,
Function<Object, Object> valueExtractor) {
this.propertySourceName = propertySourceName;
this.configurationPropertyName = configurationPropertyName;
this.valueExtractor = valueExtractor;
}
/**
* Return the mapped {@link PropertySource} name.
* @return the property source name (never {@code null})
*/
public String getPropertySourceName() {
return this.propertySourceName;
}
/**
* Return the mapped {@link ConfigurationPropertySource}
* {@link ConfigurationPropertyName}.
* @return the configuration property source name (never {@code null})
*/
public ConfigurationPropertyName getConfigurationPropertyName() {
return this.configurationPropertyName;
}
/**
* Return a function that can be used to extract the {@link PropertySource} value.
* @return the value extractor (never {@code null})
*/
public Function<Object, Object> getValueExtractor() {
return this.valueExtractor;
}
/**
* Return if this mapping is applicable for the given
* {@link ConfigurationPropertyName}.
* @param name the name to check
* @return if the mapping is applicable
*/
public boolean isApplicable(ConfigurationPropertyName name) {
return this.configurationPropertyName.equals(name);
}
}
/*
* Copyright 2012-2017 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.context.properties.source;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Stream;
import org.springframework.boot.origin.Origin;
import org.springframework.boot.origin.PropertySourceOrigin;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* {@link ConfigurationPropertySource} backed by a Spring {@link PropertySource}. Provides
* support for {@link EnumerablePropertySource} when possible but can also be used to
* non-enumerable property sources or restricted {@link EnumerablePropertySource}
* implementation (such as a security restricted {@code systemEnvironment} source). A
* {@link PropertySource} is adapted with the help of a {@link PropertyMapper} which
* provides the mapping rules for individual properties.
* <p>
* Each
* {@link ConfigurationPropertySource#getConfigurationProperty(ConfigurationPropertyName)
* getValue} call initially attempts to
* {@link PropertyMapper#map(PropertySource, ConfigurationPropertyName) map} the
* {@link ConfigurationPropertyName} to one or more {@code String} based names. This
* allows fast property resolution for well formed property sources and allows the adapter
* to work with non {@link EnumerablePropertySource enumerable property sources}.
* <p>
* If direct {@link ConfigurationPropertyName} to {@code String} mapping is unsuccessful a
* brute force approach is taken by {@link EnumerablePropertySource#getPropertyNames()
* enumerating} known {@code String} {@link PropertySource} names, mapping them to one or
* more {@link ConfigurationPropertyName} and checking for
* {@link PropertyMapping#isApplicable(ConfigurationPropertyName) applicability}. The
* enumeration approach supports property sources where it isn't practical to guess all
* direct mapping combinations.
*
* @author Phillip Webb
* @author Madhura Bhave
* @see PropertyMapper
*/
class PropertySourceConfigurationPropertySource implements ConfigurationPropertySource {
private final PropertySource<?> propertySource;
private final PropertyMapper mapper;
private volatile Object cacheKey;
private volatile Cache cache;
/**
* Create a new {@link PropertySourceConfigurationPropertySource} implementation.
* @param propertySource the source property source
* @param mapper the property mapper
*/
PropertySourceConfigurationPropertySource(PropertySource<?> propertySource,
PropertyMapper mapper) {
Assert.notNull(propertySource, "PropertySource must not be null");
Assert.notNull(mapper, "Mapper must not be null");
this.propertySource = propertySource;
this.mapper = new ExceptionSwallowingPropertyMapper(mapper);
}
@Override
public ConfigurationProperty getConfigurationProperty(
ConfigurationPropertyName name) {
ConfigurationProperty configurationProperty = findDirectly(name);
if (configurationProperty == null) {
configurationProperty = findByEnumeration(name);
}
return configurationProperty;
}
private ConfigurationProperty findDirectly(ConfigurationPropertyName name) {
List<PropertyMapping> mappings = this.mapper.map(this.propertySource, name);
return find(mappings, name);
}
private ConfigurationProperty findByEnumeration(ConfigurationPropertyName name) {
List<PropertyMapping> mappings = getPropertyMappings();
return find(mappings, name);
}
private ConfigurationProperty find(List<PropertyMapping> mappings,
ConfigurationPropertyName name) {
// Use for-loops rather than streams since this method is called often
for (PropertyMapping mapping : mappings) {
if (mapping.isApplicable(name)) {
ConfigurationProperty property = find(mapping);
if (property != null) {
return property;
}
}
}
return null;
}
private ConfigurationProperty find(PropertyMapping mapping) {
String propertySourceName = mapping.getPropertySourceName();
Object value = this.propertySource.getProperty(propertySourceName);
if (value == null) {
return null;
}
value = mapping.getValueExtractor().apply(value);
ConfigurationPropertyName configurationPropertyName = mapping
.getConfigurationPropertyName();
Origin origin = PropertySourceOrigin.get(this.propertySource, propertySourceName);
return ConfigurationProperty.of(configurationPropertyName, value, origin);
}
@Override
public Stream<ConfigurationPropertyName> stream() {
return getConfigurationPropertyNames().stream();
}
@Override
public Iterator<ConfigurationPropertyName> iterator() {
return getConfigurationPropertyNames().iterator();
}
private List<ConfigurationPropertyName> getConfigurationPropertyNames() {
Cache cache = getCache();
List<ConfigurationPropertyName> names = (cache != null ? cache.getNames() : null);
if (names != null) {
return names;
}
List<PropertyMapping> mappings = getPropertyMappings();
names = new ArrayList<ConfigurationPropertyName>(mappings.size());
for (PropertyMapping mapping : mappings) {
names.add(mapping.getConfigurationPropertyName());
}
names = Collections.unmodifiableList(names);
if (cache != null) {
cache.setNames(names);
}
return names;
}
private List<PropertyMapping> getPropertyMappings() {
if (!(this.propertySource instanceof EnumerablePropertySource)) {
return Collections.emptyList();
}
Cache cache = getCache();
List<PropertyMapping> mappings = (cache != null ? cache.getMappings() : null);
if (mappings != null) {
return mappings;
}
String[] names = ((EnumerablePropertySource<?>) this.propertySource)
.getPropertyNames();
mappings = new ArrayList<PropertyMapping>(names.length);
for (String name : names) {
mappings.addAll(this.mapper.map(this.propertySource, name));
}
mappings = Collections.unmodifiableList(mappings);
if (cache != null) {
cache.setMappings(mappings);
}
return mappings;
}
private Cache getCache() {
Object cacheKey = getCacheKey();
if (cacheKey == null) {
return null;
}
if (ObjectUtils.nullSafeEquals(cacheKey, this.cacheKey)) {
return this.cache;
}
this.cache = new Cache();
this.cacheKey = cacheKey;
return this.cache;
}
private Object getCacheKey() {
if (this.propertySource instanceof MapPropertySource) {
return ((MapPropertySource) this.propertySource).getSource().keySet();
}
if (this.propertySource instanceof EnumerablePropertySource) {
return ((EnumerablePropertySource<?>) this.propertySource).getPropertyNames();
}
return null;
}
/**
* {@link PropertyMapper} that swallows exceptions when the mapping fails.
*/
private static class ExceptionSwallowingPropertyMapper implements PropertyMapper {
private final PropertyMapper mapper;
ExceptionSwallowingPropertyMapper(PropertyMapper mapper) {
this.mapper = mapper;
}
@Override
public List<PropertyMapping> map(PropertySource<?> propertySource,
ConfigurationPropertyName configurationPropertyName) {
try {
return this.mapper.map(propertySource, configurationPropertyName);
}
catch (Exception ex) {
return Collections.emptyList();
}
}
@Override
public List<PropertyMapping> map(PropertySource<?> propertySource,
String propertySourceName) {
try {
return this.mapper.map(propertySource, propertySourceName);
}
catch (Exception ex) {
return Collections.emptyList();
}
}
}
private static class Cache {
private ConfigurationPropertyName knownMissingName;
private List<ConfigurationPropertyName> names;
private List<PropertyMapping> mappings;
public List<ConfigurationPropertyName> getNames() {
return this.names;
}
public void setNames(List<ConfigurationPropertyName> names) {
this.names = names;
}
public List<PropertyMapping> getMappings() {
return this.mappings;
}
public void setMappings(List<PropertyMapping> mappings) {
this.mappings = mappings;
}
}
}
/*
* Copyright 2012-2017 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.context.properties.source;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName.Form;
import org.springframework.core.env.PropertySource;
import org.springframework.util.StringUtils;
/**
* {@link PropertyMapper} for system environment variables. Names are mapped by removing
* invalid characters, converting to lower case and replacing "{@code _}" with
* "{@code .}". For example, "{@code SERVER_PORT}" is mapped to "{@code server.port}". In
* addition, numeric elements are mapped to indexes (e.g. "{@code HOST_0}" is mapped to
* "{@code host[0]}").
* <p>
* List shortcuts (names that end with double underscore) are also supported by this
* mapper. For example, "{@code MY_LIST__=a,b,c}" is mapped to "{@code my.list[0]=a}",
* "{@code my.list[1]=b}" ,"{@code my.list[2]=c}".
*
* @author Phillip Webb
* @author Madhura Bhave
* @see PropertyMapper
* @see PropertySourceConfigurationPropertySource
*/
class SystemEnvironmentPropertyMapper implements PropertyMapper {
public static PropertyMapper INSTANCE = new SystemEnvironmentPropertyMapper();
private final ConfigurationPropertyNameBuilder nameBuilder = new ConfigurationPropertyNameBuilder(
this::createElement);
@Override
public List<PropertyMapping> map(PropertySource<?> propertySource,
String propertySourceName) {
ConfigurationPropertyName name = convertName(propertySourceName);
if (name == null) {
return Collections.emptyList();
}
if (propertySourceName.endsWith("__")) {
return expandListShortcut(propertySourceName, name,
propertySource.getProperty(propertySourceName));
}
return Collections.singletonList(new PropertyMapping(propertySourceName, name));
}
private ConfigurationPropertyName convertName(String propertySourceName) {
try {
return this.nameBuilder.from(propertySourceName, '_').build();
}
catch (Exception ex) {
return null;
}
}
private List<PropertyMapping> expandListShortcut(String propertySourceName,
ConfigurationPropertyName rootName, Object value) {
if (value == null) {
return Collections.emptyList();
}
List<PropertyMapping> mappings = new ArrayList<>();
String[] elements = StringUtils
.commaDelimitedListToStringArray(String.valueOf(value));
for (int i = 0; i < elements.length; i++) {
ConfigurationPropertyName name = ConfigurationPropertyName
.of(rootName.toString() + "[" + i + "]");
mappings.add(new PropertyMapping(propertySourceName, name,
new ElementExtractor(i)));
}
return mappings;
}
@Override
public List<PropertyMapping> map(PropertySource<?> propertySource,
ConfigurationPropertyName configurationPropertyName) {
String name = convertName(configurationPropertyName);
List<PropertyMapping> result = Collections
.singletonList(new PropertyMapping(name, configurationPropertyName));
if (isListShortcutPossible(configurationPropertyName)) {
result = new ArrayList<>(result);
result.addAll(mapListShortcut(propertySource, configurationPropertyName));
}
return result;
}
private String convertName(ConfigurationPropertyName configurationPropertyName) {
String propertyName = configurationPropertyName.stream()
.map(name -> name.getValue(Form.UNIFORM).toUpperCase())
.collect(Collectors.joining("_"));
return propertyName;
}
private boolean isListShortcutPossible(ConfigurationPropertyName name) {
return (name.getElement().isIndexed()
&& isNumber(name.getElement().getValue(Form.UNIFORM))
&& name.getParent() != null);
}
private List<PropertyMapping> mapListShortcut(PropertySource<?> propertySource,
ConfigurationPropertyName configurationPropertyName) {
String propertyName = convertName(configurationPropertyName.getParent()) + "__";
if (propertySource.containsProperty(propertyName)) {
int index = Integer.parseInt(
configurationPropertyName.getElement().getValue(Form.UNIFORM));
return Collections.singletonList(new PropertyMapping(propertyName,
configurationPropertyName, new ElementExtractor(index)));
}
return Collections.emptyList();
}
private String createElement(String value) {
value = value.toLowerCase();
return (isNumber(value) ? "[" + value + "]" : value);
}
private static boolean isNumber(String string) {
IntStream nonDigits = string.chars().filter((c) -> !Character.isDigit(c));
boolean hasNonDigit = nonDigits.findFirst().isPresent();
return !hasNonDigit;
}
/**
* Function used to extract an element from a comma list.
*/
private static class ElementExtractor implements Function<Object, Object> {
private final int index;
ElementExtractor(int index) {
this.index = index;
}
@Override
public Object apply(Object value) {
if (value == null) {
return null;
}
return StringUtils
.commaDelimitedListToStringArray(value.toString())[this.index];
}
}
}
/*
* Copyright 2012-2017 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.
*/
/**
* Sources for external configuration properties.
*
* @see org.springframework.boot.context.properties.source.ConfigurationPropertySource
*/
package org.springframework.boot.context.properties.source;
/*
* Copyright 2012-2017 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.context.properties.source;
import java.util.Collections;
import java.util.Iterator;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
/**
* Abstract base class for {@link PropertyMapper} tests.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
public abstract class AbstractPropertyMapperTests {
protected abstract PropertyMapper getMapper();
protected final Iterator<String> namesFromString(String name) {
return namesFromString(name, "value");
}
protected final Iterator<String> namesFromString(String name, Object value) {
PropertySource<?> propertySource = new MapPropertySource("test",
Collections.singletonMap(name, value));
return getMapper().map(propertySource, name).stream()
.map((mapping) -> mapping.getConfigurationPropertyName().toString())
.iterator();
}
protected final Iterator<String> namesFromConfiguration(String name) {
return namesFromConfiguration(name, "value");
}
protected final Iterator<String> namesFromConfiguration(String name, String value) {
PropertySource<?> propertySource = new MapPropertySource("test",
Collections.singletonMap(name, value));
return getMapper().map(propertySource, ConfigurationPropertyName.of(name))
.stream().map((mapping) -> mapping.getPropertySourceName()).iterator();
}
}
/*
* Copyright 2012-2017 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.context.properties.source;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link AliasedConfigurationPropertySource}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
public class AliasedConfigurationPropertySourceTests {
@Test
public void streamShouldInclueAliases() throws Exception {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("foo.bar", "bing");
source.put("foo.baz", "biff");
ConfigurationPropertySource aliased = source
.withAliases(new ConfigurationPropertyNameAliases("foo.bar", "foo.bar1"));
assertThat(aliased.stream()).containsExactly(
ConfigurationPropertyName.of("foo.bar"),
ConfigurationPropertyName.of("foo.bar1"),
ConfigurationPropertyName.of("foo.baz"));
}
@Test
public void getConfigurationPropertyShouldConsiderAliases() throws Exception {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("foo.bar", "bing");
source.put("foo.baz", "biff");
ConfigurationPropertySource aliased = source
.withAliases(new ConfigurationPropertyNameAliases("foo.bar", "foo.bar1"));
assertThat(getValue(aliased, "foo.bar")).isEqualTo("bing");
assertThat(getValue(aliased, "foo.bar1")).isEqualTo("bing");
}
@Test
public void getConfigurationPropertyWhenNotAliasesShouldReturnValue()
throws Exception {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("foo.bar", "bing");
source.put("foo.baz", "biff");
ConfigurationPropertySource aliased = source
.withAliases(new ConfigurationPropertyNameAliases("foo.bar", "foo.bar1"));
assertThat(getValue(aliased, "foo.baz")).isEqualTo("biff");
}
private Object getValue(ConfigurationPropertySource source, String name) {
ConfigurationProperty property = source
.getConfigurationProperty(ConfigurationPropertyName.of(name));
return (property == null ? null : property.getValue());
}
}
/*
* Copyright 2012-2017 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.context.properties.source;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ConfigurationPropertyNameAliases}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
public class ConfigurationPropertyNameAliasesTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void createWithStringWhenNullNameShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Name must not be null");
new ConfigurationPropertyNameAliases((String) null);
}
@Test
public void createWithStringShouldAddMapping() throws Exception {
ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases(
"foo", "bar", "baz");
assertThat(aliases.getAliases(ConfigurationPropertyName.of("foo")))
.containsExactly(ConfigurationPropertyName.of("bar"),
ConfigurationPropertyName.of("baz"));
}
@Test
public void createWithNameShouldAddMapping() throws Exception {
ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases(
ConfigurationPropertyName.of("foo"), ConfigurationPropertyName.of("bar"),
ConfigurationPropertyName.of("baz"));
assertThat(aliases.getAliases(ConfigurationPropertyName.of("foo")))
.containsExactly(ConfigurationPropertyName.of("bar"),
ConfigurationPropertyName.of("baz"));
}
@Test
public void addAliasesFromStringShouldAddMapping() throws Exception {
ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();
aliases.addAlaises("foo", "bar", "baz");
assertThat(aliases.getAliases(ConfigurationPropertyName.of("foo")))
.containsExactly(ConfigurationPropertyName.of("bar"),
ConfigurationPropertyName.of("baz"));
}
@Test
public void addAlaisesFromNameShouldAddMapping() throws Exception {
ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();
aliases.addAlaises(ConfigurationPropertyName.of("foo"),
ConfigurationPropertyName.of("bar"), ConfigurationPropertyName.of("baz"));
assertThat(aliases.getAliases(ConfigurationPropertyName.of("foo")))
.containsExactly(ConfigurationPropertyName.of("bar"),
ConfigurationPropertyName.of("baz"));
}
@Test
public void addWhenHasExistingShouldAddAdditionalMappings() throws Exception {
ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();
aliases.addAlaises("foo", "bar");
aliases.addAlaises("foo", "baz");
assertThat(aliases.getAliases(ConfigurationPropertyName.of("foo")))
.containsExactly(ConfigurationPropertyName.of("bar"),
ConfigurationPropertyName.of("baz"));
}
@Test
public void getAliasesWhenNotMappedShouldReturnEmptyList() throws Exception {
ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();
assertThat(aliases.getAliases(ConfigurationPropertyName.of("foo"))).isEmpty();
}
@Test
public void getAliasesWhenMappedShouldReturnMapping() throws Exception {
ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();
aliases.addAlaises("foo", "bar");
assertThat(aliases.getAliases(ConfigurationPropertyName.of("foo")))
.containsExactly(ConfigurationPropertyName.of("bar"));
}
@Test
public void getNameForAliasWhenHasMappingShouldReturnName() throws Exception {
ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();
aliases.addAlaises("foo", "bar");
aliases.addAlaises("foo", "baz");
assertThat((Object) aliases.getNameForAlias(ConfigurationPropertyName.of("bar")))
.isEqualTo(ConfigurationPropertyName.of("foo"));
assertThat((Object) aliases.getNameForAlias(ConfigurationPropertyName.of("baz")))
.isEqualTo(ConfigurationPropertyName.of("foo"));
}
@Test
public void getNameForAliasWhenNotMappedShouldReturnNull() throws Exception {
ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();
aliases.addAlaises("foo", "bar");
assertThat((Object) aliases.getNameForAlias(ConfigurationPropertyName.of("baz")))
.isNull();
}
}
/*
* Copyright 2012-2017 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.context.properties.source;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName.Element;
import org.springframework.boot.context.properties.source.ConfigurationPropertyNameBuilder.ElementValueProcessor;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ConfigurationPropertyNameBuilder}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
public class ConfigurationPropertyNameBuilderTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
private ConfigurationPropertyNameBuilder builder;
@Test
public void createWhenPatternIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Pattern must not be null");
this.builder = new ConfigurationPropertyNameBuilder((Pattern) null);
}
@Test
public void createWhenElementProcessorIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Processor must not be null");
this.builder = new ConfigurationPropertyNameBuilder((ElementValueProcessor) null);
}
@Test
public void buildShouldCreateName() throws Exception {
this.builder = new ConfigurationPropertyNameBuilder();
ConfigurationPropertyName expected = ConfigurationPropertyName.of("foo.bar.baz");
ConfigurationPropertyName name = this.builder.from("foo.bar.baz", '.').build();
assertThat(name.toString()).isEqualTo(expected.toString());
}
@Test
public void buildShouldValidateUsingPattern() {
Pattern pattern = Pattern.compile("[a-z]([a-z0-9\\-])*");
this.builder = new ConfigurationPropertyNameBuilder(pattern);
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Element value 'foo@!' is not valid");
this.builder.from("foo@!.bar", '.').build();
}
@Test
public void buildWhenHasNoElementsShouldThrowException() throws Exception {
this.builder = new ConfigurationPropertyNameBuilder();
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("At least one element must be defined");
this.builder.build();
}
@Test
public void buildShouldUseElementProcessor() throws Exception {
this.builder = new ConfigurationPropertyNameBuilder(
value -> value.replace("-", ""));
ConfigurationPropertyName name = this.builder.from("FOO_THE-BAR", '_').build();
assertThat(name.toString()).isEqualTo("foo.thebar");
}
@Test
public void fromNameShouldSetElements() throws Exception {
this.builder = new ConfigurationPropertyNameBuilder();
ConfigurationPropertyName name = this.builder.from("foo.bar", '.').build();
assertThat(name.toString()).isEqualTo("foo.bar");
}
@Test
public void fromNameShouldSetIndexedElements() throws Exception {
this.builder = new ConfigurationPropertyNameBuilder();
assertThat(getElements("foo")).isEqualTo(elements("foo"));
assertThat(getElements("[foo]")).isEqualTo(elements("[foo]"));
assertThat(getElements("foo.bar")).isEqualTo(elements("foo", "bar"));
assertThat(getElements("foo[foo.bar]")).isEqualTo(elements("foo", "[foo.bar]"));
assertThat(getElements("foo.[bar].baz"))
.isEqualTo(elements("foo", "[bar]", "baz"));
}
@Test
public void fromNameWhenHasExistingShouldSetNewElements() throws Exception {
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Existing elements must not be present");
new ConfigurationPropertyNameBuilder().from("foo.bar", '.').from("baz", '.')
.build();
}
@Test
public void appendShouldAppendElement() throws Exception {
this.builder = new ConfigurationPropertyNameBuilder();
ConfigurationPropertyName name = this.builder.from("foo.bar", '.').append("baz")
.build();
assertThat(name.toString()).isEqualTo("foo.bar.baz");
}
private List<Element> elements(String... elements) {
return Arrays.stream(elements).map(Element::new).collect(Collectors.toList());
}
@SuppressWarnings("unchecked")
private List<Element> getElements(String name) {
ConfigurationPropertyNameBuilder builder = this.builder.from(name, '.');
return (List<Element>) ReflectionTestUtils.getField(builder, "elements");
}
}
/*
* Copyright 2012-2017 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.context.properties.source;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ConfigurationPropertySourcesPropertySource}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
public class ConfigurationPropertySourcesPropertySourceTests {
private List<ConfigurationPropertySource> configurationSources = new ArrayList<>();
private ConfigurationPropertySourcesPropertySource propertySource = new ConfigurationPropertySourcesPropertySource(
"test", this.configurationSources);
@Test
public void getPropertyShouldReturnValue() throws Exception {
this.configurationSources
.add(new MockConfigurationPropertySource("foo.bar", "baz"));
assertThat(this.propertySource.getProperty("foo.bar")).isEqualTo("baz");
}
@Test
public void getPropertyWhenNameIsNotValidShouldReturnNull() throws Exception {
this.configurationSources
.add(new MockConfigurationPropertySource("foo.bar", "baz"));
assertThat(this.propertySource.getProperty("FOO.B-A-R")).isNull();
assertThat(this.propertySource.getProperty("FOO.B A R")).isNull();
assertThat(this.propertySource.getProperty(".foo.bar")).isNull();
}
@Test
public void getPropertyWhenMultipleShouldReturnFirst() throws Exception {
this.configurationSources
.add(new MockConfigurationPropertySource("foo.bar", "baz"));
this.configurationSources
.add(new MockConfigurationPropertySource("foo.bar", "bill"));
assertThat(this.propertySource.getProperty("foo.bar")).isEqualTo("baz");
}
@Test
public void getPropertyWhenNoneShouldReturnFirst() throws Exception {
this.configurationSources
.add(new MockConfigurationPropertySource("foo.bar", "baz"));
assertThat(this.propertySource.getProperty("foo.foo")).isNull();
}
@Test
public void getPropertyOriginShouldReturnOrigin() throws Exception {
this.configurationSources
.add(new MockConfigurationPropertySource("foo.bar", "baz", "line1"));
assertThat(this.propertySource.getOrigin("foo.bar").toString())
.isEqualTo("line1");
}
@Test
public void getPropertyOriginWhenMissingShouldReturnNull() throws Exception {
this.configurationSources
.add(new MockConfigurationPropertySource("foo.bar", "baz", "line1"));
assertThat(this.propertySource.getOrigin("foo.foo")).isNull();
}
@Test
public void getNameShouldReturnName() throws Exception {
assertThat(this.propertySource.getName()).isEqualTo("test");
}
@Test
public void getSourceShouldReturnSource() throws Exception {
assertThat(this.propertySource.getSource()).isSameAs(this.configurationSources);
}
}
/*
* Copyright 2012-2017 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.context.properties.source;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertyResolver;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySourcesPropertyResolver;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.env.SystemEnvironmentPropertySource;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ConfigurationPropertySources}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
public class ConfigurationPropertySourcesTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void createWhenPropertySourcesIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("PropertySources must not be null");
new ConfigurationPropertySources(null);
}
@Test
public void iteratorShouldAdaptPropertySource() throws Exception {
MutablePropertySources sources = new MutablePropertySources();
sources.addFirst(new MapPropertySource("test",
Collections.<String, Object>singletonMap("a", "b")));
Iterator<ConfigurationPropertySource> iterator = new ConfigurationPropertySources(
sources).iterator();
assertThat(iterator.next()
.getConfigurationProperty(ConfigurationPropertyName.of("a")).getValue())
.isEqualTo("b");
assertThat(iterator.hasNext()).isFalse();
}
@Test
public void iteratorShouldAdaptSystemEnvironmentPropertySource() throws Exception {
MutablePropertySources sources = new MutablePropertySources();
sources.addLast(new SystemEnvironmentPropertySource("system",
Collections.<String, Object>singletonMap("SERVER_PORT", "1234")));
Iterator<ConfigurationPropertySource> iterator = new ConfigurationPropertySources(
sources).iterator();
assertThat(
iterator.next()
.getConfigurationProperty(
ConfigurationPropertyName.of("server.port"))
.getValue()).isEqualTo("1234");
assertThat(iterator.hasNext()).isFalse();
}
@Test
public void iteratorShouldAdaptMultiplePropertySources() throws Exception {
MutablePropertySources sources = new MutablePropertySources();
sources.addLast(new SystemEnvironmentPropertySource("system",
Collections.<String, Object>singletonMap("SERVER_PORT", "1234")));
sources.addLast(new MapPropertySource("test1",
Collections.<String, Object>singletonMap("server.po-rt", "4567")));
sources.addLast(new MapPropertySource("test2",
Collections.<String, Object>singletonMap("a", "b")));
Iterator<ConfigurationPropertySource> iterator = new ConfigurationPropertySources(
sources).iterator();
assertThat(
iterator.next()
.getConfigurationProperty(
ConfigurationPropertyName.of("server.port"))
.getValue()).isEqualTo("1234");
assertThat(
iterator.next()
.getConfigurationProperty(
ConfigurationPropertyName.of("server.port"))
.getValue()).isEqualTo("4567");
assertThat(iterator.next()
.getConfigurationProperty(ConfigurationPropertyName.of("a")).getValue())
.isEqualTo("b");
assertThat(iterator.hasNext()).isFalse();
}
@Test
public void attachShouldAddAdapterAtBegining() throws Exception {
MutablePropertySources sources = new MutablePropertySources();
sources.addLast(new SystemEnvironmentPropertySource("system",
Collections.<String, Object>singletonMap("SERVER_PORT", "1234")));
sources.addLast(new MapPropertySource("config",
Collections.<String, Object>singletonMap("server.port", "4568")));
assertThat(sources.size()).isEqualTo(2);
ConfigurationPropertySources.attach(sources);
PropertyResolver resolver = new PropertySourcesPropertyResolver(sources);
assertThat(resolver.getProperty("server.port")).isEqualTo("1234");
assertThat(sources.size()).isEqualTo(3);
}
@Test
public void getWhenAttachedShouldReturnAttached() throws Exception {
MutablePropertySources sources = new MutablePropertySources();
sources.addFirst(new MapPropertySource("test",
Collections.<String, Object>singletonMap("a", "b")));
ConfigurationPropertySources attached = ConfigurationPropertySources
.attach(sources);
assertThat(ConfigurationPropertySources.get(sources)).isSameAs(attached);
}
@Test
public void getWhenNotAttachedShouldReturnNew() throws Exception {
MutablePropertySources sources = new MutablePropertySources();
sources.addFirst(new MapPropertySource("test",
Collections.<String, Object>singletonMap("a", "b")));
assertThat(ConfigurationPropertySources.get(sources)).isNotNull();
assertThat(sources.size()).isEqualTo(1);
}
@Test
public void environmentProperyExpansionShouldWorkWhenAttached() throws Exception {
StandardEnvironment environment = new StandardEnvironment();
Map<String, Object> source = new LinkedHashMap<>();
source.put("fooBar", "Spring ${barBaz} ${bar-baz}");
source.put("barBaz", "Boot");
PropertySource<?> propertySource = new MapPropertySource("test", source);
environment.getPropertySources().addFirst(propertySource);
ConfigurationPropertySources.attach(environment);
assertThat(environment.getProperty("foo-bar")).isEqualTo("Spring Boot Boot");
}
@Test
public void environmentSourceShouldBeFlattened() throws Exception {
StandardEnvironment environment = new StandardEnvironment();
environment.getPropertySources().addFirst(new MapPropertySource("foo",
Collections.<String, Object>singletonMap("foo", "bar")));
environment.getPropertySources().addFirst(new MapPropertySource("far",
Collections.<String, Object>singletonMap("far", "far")));
MutablePropertySources sources = new MutablePropertySources();
sources.addFirst(new PropertySource<Environment>("env", environment) {
@Override
public String getProperty(String key) {
return this.source.getProperty(key);
}
});
sources.addLast(new MapPropertySource("baz",
Collections.<String, Object>singletonMap("baz", "barf")));
ConfigurationPropertySources configurationSources = ConfigurationPropertySources
.get(sources);
assertThat(configurationSources.iterator()).hasSize(5);
}
}
/*
* Copyright 2012-2017 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.context.properties.source;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.origin.Origin;
import org.springframework.boot.origin.OriginProvider;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link ConfigurationProperty}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
public class ConfigurationPropertyTests {
private static final ConfigurationPropertyName NAME = ConfigurationPropertyName
.of("foo");
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void createWhenNameIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Name must not be null");
new ConfigurationProperty(null, "bar", null);
}
@Test
public void createWhenValueIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Value must not be null");
new ConfigurationProperty(NAME, null, null);
}
@Test
public void getNameShouldReturnName() throws Exception {
ConfigurationProperty property = ConfigurationProperty.of(NAME, "foo", null);
assertThat((Object) property.getName()).isEqualTo(NAME);
}
@Test
public void getValueShouldReturnValue() throws Exception {
ConfigurationProperty property = ConfigurationProperty.of(NAME, "foo", null);
assertThat(property.getValue()).isEqualTo("foo");
}
@Test
public void getPropertyOrginShouldReturnValuePropertyOrigin() throws Exception {
Origin origin = mock(Origin.class);
OriginProvider property = ConfigurationProperty.of(NAME, "foo", origin);
assertThat(property.getOrigin()).isEqualTo(origin);
}
@Test
public void equalsAndHashCode() throws Exception {
ConfigurationProperty property1 = new ConfigurationProperty(
ConfigurationPropertyName.of("foo"), "bar", null);
ConfigurationProperty property2 = new ConfigurationProperty(
ConfigurationPropertyName.of("foo"), "bar", null);
ConfigurationProperty property3 = new ConfigurationProperty(
ConfigurationPropertyName.of("foo"), "baz", null);
ConfigurationProperty property4 = new ConfigurationProperty(
ConfigurationPropertyName.of("baz"), "bar", null);
assertThat(property1.hashCode()).isEqualTo(property2.hashCode());
assertThat(property1).isEqualTo(property2).isNotEqualTo(property3)
.isNotEqualTo(property4);
}
@Test
public void toStringShouldReturnValue() throws Exception {
ConfigurationProperty property = ConfigurationProperty.of(NAME, "foo", null);
assertThat(property.toString()).contains("name").contains("value");
}
}
/*
* Copyright 2012-2017 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.context.properties.source;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link DefaultPropertyMapper}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
public class DefaultPropertyMapperTests extends AbstractPropertyMapperTests {
private DefaultPropertyMapper mapper = new DefaultPropertyMapper();
@Override
protected PropertyMapper getMapper() {
return this.mapper;
}
@Test
public void mapFromStringShouldReturnBestGuess() throws Exception {
assertThat(namesFromString("server")).containsExactly("server");
assertThat(namesFromString("server.port")).containsExactly("server.port");
assertThat(namesFromString("host[0]")).containsExactly("host[0]");
assertThat(namesFromString("host[0][1]")).containsExactly("host[0][1]");
assertThat(namesFromString("host[0].name")).containsExactly("host[0].name");
assertThat(namesFromString("host.f00.name")).containsExactly("host.f00.name");
assertThat(namesFromString("my.host-name")).containsExactly("my.host-name");
assertThat(namesFromString("my.hostName")).containsExactly("my.hostname");
assertThat(namesFromString("my.HOST_NAME")).containsExactly("my.hostname");
assertThat(namesFromString("s[!@#$%^&*()=+]e-rVeR"))
.containsExactly("s[!@#$%^&*()=+].e-rver");
assertThat(namesFromString("host[FOO].name")).containsExactly("host[FOO].name");
}
@Test
public void mapFromConfigurationShouldReturnBestGuess() throws Exception {
assertThat(namesFromConfiguration("server")).containsExactly("server");
assertThat(namesFromConfiguration("server.port")).containsExactly("server.port");
assertThat(namesFromConfiguration("host[0]")).containsExactly("host[0]");
assertThat(namesFromConfiguration("host[0][1]")).containsExactly("host[0][1]");
assertThat(namesFromConfiguration("host[0].name"))
.containsExactly("host[0].name");
assertThat(namesFromConfiguration("host.f00.name"))
.containsExactly("host.f00.name");
assertThat(namesFromConfiguration("my.host-name"))
.containsExactly("my.host-name");
assertThat(namesFromConfiguration("host[FOO].name"))
.containsExactly("host[FOO].name");
}
}
/*
* Copyright 2012-2017 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.context.properties.source;
import java.util.Objects;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link FilteredConfigurationPropertiesSource}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
public class FilteredConfigurationPropertiesSourceTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void createWhenSourceIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Source must not be null");
new FilteredConfigurationPropertiesSource(null, Objects::nonNull);
}
@Test
public void createWhenFilterIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Filter must not be null");
new FilteredConfigurationPropertiesSource(new MockConfigurationPropertySource(),
null);
}
@Test
public void iteratorShouldFilterNames() throws Exception {
MockConfigurationPropertySource source = createTestSource();
ConfigurationPropertySource filtered = source.filter(this::noBrackets);
assertThat(filtered.iterator()).extracting(ConfigurationPropertyName::toString)
.containsExactly("a", "b", "c");
}
@Test
public void getValueShouldFilterNames() throws Exception {
MockConfigurationPropertySource source = createTestSource();
ConfigurationPropertySource filtered = source.filter(this::noBrackets);
ConfigurationPropertyName name = ConfigurationPropertyName.of("a");
assertThat(source.getConfigurationProperty(name).getValue()).isEqualTo("1");
assertThat(filtered.getConfigurationProperty(name).getValue()).isEqualTo("1");
ConfigurationPropertyName bracketName = ConfigurationPropertyName.of("a[1]");
assertThat(source.getConfigurationProperty(bracketName).getValue())
.isEqualTo("2");
assertThat(filtered.getConfigurationProperty(bracketName)).isNull();
}
private MockConfigurationPropertySource createTestSource() {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("a", "1");
source.put("a[1]", "2");
source.put("b", "3");
source.put("b[1]", "4");
source.put("c", "5");
return source;
}
private boolean noBrackets(ConfigurationPropertyName name) {
return name.toString().indexOf("[") == -1;
}
}
/*
* Copyright 2012-2017 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.context.properties.source;
import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link MapConfigurationPropertySource}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
public class MapConfigurationPropertySourceTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void createWhenMapIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Map must not be null");
new MapConfigurationPropertySource(null);
}
@Test
public void createWhenMapHasEntriesShouldAdaptMap() throws Exception {
Map<Object, Object> map = new LinkedHashMap<>();
map.put("foo.BAR", "spring");
map.put(ConfigurationPropertyName.of("foo.baz"), "boot");
MapConfigurationPropertySource source = new MapConfigurationPropertySource(map);
assertThat(getValue(source, "foo.bar")).isEqualTo("spring");
assertThat(getValue(source, "foo.baz")).isEqualTo("boot");
}
@Test
public void putAllWhenMapIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Map must not be null");
MapConfigurationPropertySource source = new MapConfigurationPropertySource();
source.putAll(null);
}
@Test
public void putAllShouldPutEntries() throws Exception {
Map<Object, Object> map = new LinkedHashMap<>();
map.put("foo.BAR", "spring");
map.put("foo.baz", "boot");
MapConfigurationPropertySource source = new MapConfigurationPropertySource();
source.putAll(map);
assertThat(getValue(source, "foo.bar")).isEqualTo("spring");
assertThat(getValue(source, "foo.baz")).isEqualTo("boot");
}
@Test
public void putShouldPutEntry() throws Exception {
MapConfigurationPropertySource source = new MapConfigurationPropertySource();
source.put("foo.bar", "baz");
assertThat(getValue(source, "foo.bar")).isEqualTo("baz");
}
@Test
public void getConfigurationPropertyShouldGetFromMemory() throws Exception {
MapConfigurationPropertySource source = new MapConfigurationPropertySource();
source.put("foo.bar", "baz");
assertThat(getValue(source, "foo.bar")).isEqualTo("baz");
source.put("foo.bar", "big");
assertThat(getValue(source, "foo.bar")).isEqualTo("big");
}
@Test
public void iteratorShouldGetFromMemory() throws Exception {
MapConfigurationPropertySource source = new MapConfigurationPropertySource();
source.put("foo.BAR", "spring");
source.put("foo.baz", "boot");
assertThat(source.iterator()).containsExactly(
ConfigurationPropertyName.of("foo.bar"),
ConfigurationPropertyName.of("foo.baz"));
}
@Test
public void streamShouldGetFromMemory() throws Exception {
MapConfigurationPropertySource source = new MapConfigurationPropertySource();
source.put("foo.BAR", "spring");
source.put("foo.baz", "boot");
assertThat(source.stream()).containsExactly(
ConfigurationPropertyName.of("foo.bar"),
ConfigurationPropertyName.of("foo.baz"));
}
private Object getValue(ConfigurationPropertySource source, String name) {
ConfigurationProperty property = source
.getConfigurationProperty(ConfigurationPropertyName.of(name));
return (property == null ? null : property.getValue());
};
}
/*
* Copyright 2012-2017 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.context.properties.source;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Stream;
import org.springframework.boot.origin.MockOrigin;
import org.springframework.boot.origin.OriginTrackedValue;
/**
* Mock {@link ConfigurationPropertySource} implementation used for testing.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
public class MockConfigurationPropertySource implements ConfigurationPropertySource {
private final Map<ConfigurationPropertyName, OriginTrackedValue> map = new LinkedHashMap<>();
private boolean nonIterable;
public MockConfigurationPropertySource() {
}
public MockConfigurationPropertySource(String configurationPropertyName,
Object value) {
this(configurationPropertyName, value, null);
}
public MockConfigurationPropertySource(String configurationPropertyName, Object value,
String origin) {
put(ConfigurationPropertyName.of(configurationPropertyName),
OriginTrackedValue.of(value, MockOrigin.of(origin)));
}
public void put(String name, String value) {
put(ConfigurationPropertyName.of(name), value);
}
public void put(ConfigurationPropertyName name, String value) {
put(name, OriginTrackedValue.of(value));
}
private void put(ConfigurationPropertyName name, OriginTrackedValue value) {
this.map.put(name, value);
}
public void setNonIterable(boolean nonIterable) {
this.nonIterable = nonIterable;
}
@Override
public Iterator<ConfigurationPropertyName> iterator() {
if (this.nonIterable) {
return Collections.<ConfigurationPropertyName>emptyList().iterator();
}
return this.map.keySet().iterator();
}
@Override
public Stream<ConfigurationPropertyName> stream() {
if (this.nonIterable) {
return Collections.<ConfigurationPropertyName>emptyList().stream();
}
return this.map.keySet().stream();
}
@Override
public ConfigurationProperty getConfigurationProperty(
ConfigurationPropertyName name) {
OriginTrackedValue result = this.map.get(name);
if (result == null) {
result = findValue(name);
}
return ConfigurationProperty.of(name, result);
}
private OriginTrackedValue findValue(ConfigurationPropertyName name) {
return this.map.get(name);
}
}
/*
* Copyright 2012-2017 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.context.properties.source;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.origin.Origin;
import org.springframework.boot.origin.OriginLookup;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link PropertySourceConfigurationPropertySource}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
public class PropertySourceConfigurationPropertySourceTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void createWhenPropertySourceIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("PropertySource must not be null");
new PropertySourceConfigurationPropertySource(null, mock(PropertyMapper.class));
}
@Test
public void createWhenMapperIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Mapper must not be null");
new PropertySourceConfigurationPropertySource(mock(PropertySource.class), null);
}
@Test
public void iteratorWhenNonEnumerbleShouldReturnEmptyIterator() throws Exception {
Map<String, Object> source = new LinkedHashMap<>();
PropertySource<?> propertySource = new NonEnumerablePropertySource<>(
new MapPropertySource("test", source));
TestPropertyMapper mapper = new TestPropertyMapper();
PropertySourceConfigurationPropertySource adapter = new PropertySourceConfigurationPropertySource(
propertySource, mapper);
assertThat(adapter.iterator()).isEmpty();
}
@Test
public void iteratorShouldAdaptNames() throws Exception {
Map<String, Object> source = new LinkedHashMap<>();
source.put("key1", "value1");
source.put("key2", "value2");
source.put("key3", "value3");
source.put("key4", "value4");
PropertySource<?> propertySource = new MapPropertySource("test", source);
TestPropertyMapper mapper = new TestPropertyMapper();
mapper.addFromProperySource("key1", "my.key1");
mapper.addFromProperySource("key2", "my.key2a", "my.key2b");
mapper.addFromProperySource("key4", "my.key4");
PropertySourceConfigurationPropertySource adapter = new PropertySourceConfigurationPropertySource(
propertySource, mapper);
assertThat(adapter.iterator()).extracting(Object::toString)
.containsExactly("my.key1", "my.key2a", "my.key2b", "my.key4");
}
@Test
public void getValueShouldUseDirectMapping() throws Exception {
Map<String, Object> source = new LinkedHashMap<>();
source.put("key1", "value1");
source.put("key2", "value2");
source.put("key3", "value3");
PropertySource<?> propertySource = new NonEnumerablePropertySource<>(
new MapPropertySource("test", source));
TestPropertyMapper mapper = new TestPropertyMapper();
ConfigurationPropertyName name = ConfigurationPropertyName.of("my.key");
mapper.addFromConfigurationProperty(name, "key2");
PropertySourceConfigurationPropertySource adapter = new PropertySourceConfigurationPropertySource(
propertySource, mapper);
assertThat(adapter.getConfigurationProperty(name).getValue()).isEqualTo("value2");
}
@Test
public void getValueShouldFallbackToEnumerableMapping() throws Exception {
Map<String, Object> source = new LinkedHashMap<>();
source.put("key1", "value1");
source.put("key2", "value2");
source.put("key3", "value3");
PropertySource<?> propertySource = new MapPropertySource("test", source);
TestPropertyMapper mapper = new TestPropertyMapper();
mapper.addFromProperySource("key1", "my.missing");
mapper.addFromProperySource("key2", "my.k-e-y");
PropertySourceConfigurationPropertySource adapter = new PropertySourceConfigurationPropertySource(
propertySource, mapper);
ConfigurationPropertyName name = ConfigurationPropertyName.of("my.key");
assertThat(adapter.getConfigurationProperty(name).getValue()).isEqualTo("value2");
}
@Test
public void getValueShouldUseExtractor() throws Exception {
Map<String, Object> source = new LinkedHashMap<>();
source.put("key", "value");
PropertySource<?> propertySource = new NonEnumerablePropertySource<>(
new MapPropertySource("test", source));
TestPropertyMapper mapper = new TestPropertyMapper();
ConfigurationPropertyName name = ConfigurationPropertyName.of("my.key");
mapper.addFromConfigurationProperty(name, "key",
(value) -> value.toString().replace("ue", "let"));
PropertySourceConfigurationPropertySource adapter = new PropertySourceConfigurationPropertySource(
propertySource, mapper);
assertThat(adapter.getConfigurationProperty(name).getValue()).isEqualTo("vallet");
}
@Test
public void getValueOrigin() throws Exception {
Map<String, Object> source = new LinkedHashMap<>();
source.put("key", "value");
PropertySource<?> propertySource = new MapPropertySource("test", source);
TestPropertyMapper mapper = new TestPropertyMapper();
ConfigurationPropertyName name = ConfigurationPropertyName.of("my.key");
mapper.addFromConfigurationProperty(name, "key");
PropertySourceConfigurationPropertySource adapter = new PropertySourceConfigurationPropertySource(
propertySource, mapper);
assertThat(adapter.getConfigurationProperty(name).getOrigin().toString())
.isEqualTo("\"key\" from property source \"test\"");
}
@Test
public void getValueWhenOriginCapableShouldIncludeSourceOrigin() throws Exception {
Map<String, Object> source = new LinkedHashMap<>();
source.put("key", "value");
PropertySource<?> propertySource = new OriginCapablePropertySource<>(
new MapPropertySource("test", source));
TestPropertyMapper mapper = new TestPropertyMapper();
ConfigurationPropertyName name = ConfigurationPropertyName.of("my.key");
mapper.addFromConfigurationProperty(name, "key");
PropertySourceConfigurationPropertySource adapter = new PropertySourceConfigurationPropertySource(
propertySource, mapper);
assertThat(adapter.getConfigurationProperty(name).getOrigin().toString())
.isEqualTo("TestOrigin key");
}
/**
* Test {@link PropertySource} that doesn't extend {@link EnumerablePropertySource}.
*/
private static class NonEnumerablePropertySource<T> extends PropertySource<T> {
private final PropertySource<T> propertySource;
NonEnumerablePropertySource(PropertySource<T> propertySource) {
super(propertySource.getName(), propertySource.getSource());
this.propertySource = propertySource;
}
@Override
public Object getProperty(String name) {
return this.propertySource.getProperty(name);
}
}
/**
* Test {@link PropertySource} that's also a {@link OriginLookup}.
*/
private static class OriginCapablePropertySource<T> extends PropertySource<T>
implements OriginLookup<String> {
private final PropertySource<T> propertySource;
OriginCapablePropertySource(PropertySource<T> propertySource) {
super(propertySource.getName(), propertySource.getSource());
this.propertySource = propertySource;
}
@Override
public Object getProperty(String name) {
return this.propertySource.getProperty(name);
}
@Override
public Origin getOrigin(String name) {
return new Origin() {
@Override
public String toString() {
return "TestOrigin " + name;
}
};
}
}
/**
* Test {@link PropertyMapper} implementation.
*/
private static class TestPropertyMapper implements PropertyMapper {
private MultiValueMap<String, PropertyMapping> fromSource = new LinkedMultiValueMap<>();
private MultiValueMap<ConfigurationPropertyName, PropertyMapping> fromConfig = new LinkedMultiValueMap<>();
public void addFromProperySource(String from, String... to) {
for (String configurationPropertyName : to) {
this.fromSource.add(from, new PropertyMapping(from,
ConfigurationPropertyName.of(configurationPropertyName)));
}
}
public void addFromConfigurationProperty(ConfigurationPropertyName from,
String... to) {
for (String propertySourceName : to) {
this.fromConfig.add(from, new PropertyMapping(propertySourceName, from));
}
}
public void addFromConfigurationProperty(ConfigurationPropertyName from,
String to, Function<Object, Object> extractor) {
this.fromConfig.add(from, new PropertyMapping(to, from, extractor));
}
@Override
public List<PropertyMapping> map(PropertySource<?> propertySource,
String propertySourceName) {
return this.fromSource.getOrDefault(propertySourceName,
Collections.emptyList());
}
@Override
public List<PropertyMapping> map(PropertySource<?> propertySource,
ConfigurationPropertyName configurationPropertyName) {
return this.fromConfig.getOrDefault(configurationPropertyName,
Collections.emptyList());
}
}
}
/*
* Copyright 2012-2017 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.context.properties.source;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link SystemEnvironmentPropertyMapper}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
public class SystemEnvironmentPropertyMapperTests extends AbstractPropertyMapperTests {
private SystemEnvironmentPropertyMapper mapper = new SystemEnvironmentPropertyMapper();
@Override
protected PropertyMapper getMapper() {
return this.mapper;
}
@Test
public void mapFromStringShouldReturnBestGuess() throws Exception {
assertThat(namesFromString("SERVER")).containsExactly("server");
assertThat(namesFromString("SERVER_PORT")).containsExactly("server.port");
assertThat(namesFromString("HOST_0")).containsExactly("host[0]");
assertThat(namesFromString("HOST_0_1")).containsExactly("host[0][1]");
assertThat(namesFromString("HOST_0_NAME")).containsExactly("host[0].name");
assertThat(namesFromString("HOST_F00_NAME")).containsExactly("host.f00.name");
assertThat(namesFromString("S-ERVER")).containsExactly("s-erver");
assertThat(namesFromString("SERVERS__", "1,2,3")).containsExactly("servers[0]",
"servers[1]", "servers[2]");
assertThat(namesFromString("SERVERS_0__", "1,2,3"))
.containsExactly("servers[0][0]", "servers[0][1]", "servers[0][2]");
}
@Test
public void mapFromConfigurationShouldReturnBestGuess() throws Exception {
assertThat(namesFromConfiguration("server")).containsExactly("SERVER");
assertThat(namesFromConfiguration("server.port")).containsExactly("SERVER_PORT");
assertThat(namesFromConfiguration("host[0]")).containsExactly("HOST_0");
assertThat(namesFromConfiguration("host[0][1]")).containsExactly("HOST_0_1");
assertThat(namesFromConfiguration("host[0].name")).containsExactly("HOST_0_NAME");
assertThat(namesFromConfiguration("host.f00.name"))
.containsExactly("HOST_F00_NAME");
assertThat(namesFromConfiguration("foo.the-bar")).containsExactly("FOO_THEBAR");
}
@Test
public void mapFromStringWhenListShortcutShouldExtractValues() throws Exception {
Map<String, Object> source = new LinkedHashMap<>();
source.put("SERVER__", "foo,bar,baz");
PropertySource<?> propertySource = new MapPropertySource("test", source);
List<PropertyMapping> mappings = this.mapper.map(propertySource, "SERVER__");
List<Object> result = new ArrayList<>();
for (PropertyMapping mapping : mappings) {
Object value = propertySource.getProperty(mapping.getPropertySourceName());
value = mapping.getValueExtractor().apply(value);
result.add(value);
}
assertThat(result).containsExactly("foo", "bar", "baz");
}
@Test
public void mapFromConfigurationShouldIncludeShortcutAndExtractValues()
throws Exception {
Map<String, Object> source = new LinkedHashMap<>();
source.put("SERVER__", "foo,bar,baz");
PropertySource<?> propertySource = new MapPropertySource("test", source);
List<PropertyMapping> mappings = this.mapper.map(propertySource,
ConfigurationPropertyName.of("server[1]"));
List<Object> result = new ArrayList<>();
for (PropertyMapping mapping : mappings) {
Object value = propertySource.getProperty(mapping.getPropertySourceName());
value = mapping.getValueExtractor().apply(value);
if (value != null) {
result.add(value);
}
}
assertThat(result).containsExactly("bar");
}
@Test
public void underscoreShouldNotMapToEmptyString() throws Exception {
Map<String, Object> source = new LinkedHashMap<>();
PropertySource<?> propertySource = new MapPropertySource("test", source);
List<PropertyMapping> mappings = this.mapper.map(propertySource, "_");
boolean applicable = false;
for (PropertyMapping mapping : mappings) {
applicable = mapping.isApplicable(ConfigurationPropertyName.of(""));
}
assertThat(applicable).isFalse();
}
@Test
public void underscoreWithWhitespaceShouldNotMapToEmptyString() throws Exception {
Map<String, Object> source = new LinkedHashMap<>();
PropertySource<?> propertySource = new MapPropertySource("test", source);
List<PropertyMapping> mappings = this.mapper.map(propertySource, " _");
boolean applicable = false;
for (PropertyMapping mapping : mappings) {
applicable = mapping.isApplicable(ConfigurationPropertyName.of(""));
}
assertThat(applicable).isFalse();
}
}
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