Split ConfigurationPropertySource for iteration

Create separate `IterableConfigurationPropertySource` and
`ConfigurationPropertySource` interfaces so that it's possible to
work out if a source can truly iterate the values that it contains.

Prior to this commit there was only a single
`ConfigurationPropertySource` interface, which returned an empty
Iterator when values could not be iterated. This design made it
impossible to tell the difference between a source that was empty, and
a source that could not be iterated.

The `ConfigurationPropertySources` class has been updated to adapt
non-enumerable and enumerable Spring PropertySources to the correct
`ConfigurationPropertySource` interface. It also deals with the edge
case of the `SystemPropertySource` running in a security restricted
environment.

Fixes gh-9057
pull/9050/merge
Phillip Webb 8 years ago
parent 53fd1f7f2e
commit 10b8eb3109

@ -36,6 +36,7 @@ import org.springframework.boot.context.properties.source.ConfigurationProperty;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.boot.context.properties.source.IterableConfigurationPropertySource;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
@ -288,9 +289,7 @@ public class Binder {
private Object bindBean(ConfigurationPropertyName name, Bindable<?> target,
BindHandler handler, Context context) {
boolean hasKnownBindableProperties = context.streamSources()
.flatMap((s) -> s.filter(name::isAncestorOf).stream()).findAny()
.isPresent();
boolean hasKnownBindableProperties = hasKnownBindableProperties(name, context);
if (!hasKnownBindableProperties && isUnbindableBean(target)) {
return null;
}
@ -307,6 +306,15 @@ public class Binder {
});
}
private boolean hasKnownBindableProperties(ConfigurationPropertyName name,
Context context) {
Stream<IterableConfigurationPropertySource> sources = context.streamSources()
.filter(IterableConfigurationPropertySource.class::isInstance)
.map(IterableConfigurationPropertySource.class::cast);
return sources.flatMap((s) -> s.filter(name::isAncestorOf).stream()).findAny()
.isPresent();
}
private boolean isUnbindableBean(Bindable<?> target) {
Class<?> resolved = target.getType().resolve();
if (resolved.isPrimitive() || NON_BEAN_CLASSES.contains(resolved)) {

@ -27,6 +27,7 @@ import org.springframework.boot.context.properties.source.ConfigurationProperty;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName.Form;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.IterableConfigurationPropertySource;
import org.springframework.core.ResolvableType;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
@ -94,7 +95,11 @@ abstract class IndexedElementsBinder<T> extends AggregateBinder<T> {
private MultiValueMap<String, ConfigurationProperty> getKnownIndexedChildren(
ConfigurationPropertySource source, ConfigurationPropertyName root) {
MultiValueMap<String, ConfigurationProperty> children = new LinkedMultiValueMap<>();
for (ConfigurationPropertyName name : source.filter(root::isAncestorOf)) {
if (!(source instanceof IterableConfigurationPropertySource)) {
return children;
}
for (ConfigurationPropertyName name : (IterableConfigurationPropertySource) source
.filter(root::isAncestorOf)) {
name = rollUp(name, root);
if (name.getElement().isIndexed()) {
String key = name.getElement().getValue(Form.UNIFORM);

@ -25,6 +25,7 @@ import org.springframework.boot.context.properties.source.ConfigurationProperty;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName.Form;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.IterableConfigurationPropertySource;
import org.springframework.core.CollectionFactory;
import org.springframework.core.ResolvableType;
@ -83,13 +84,15 @@ class MapBinder extends AggregateBinder<Map<Object, Object>> {
public void bindEntries(ConfigurationPropertySource source,
Map<Object, Object> map) {
for (ConfigurationPropertyName name : source) {
Bindable<?> valueBindable = getValueBindable(name);
ConfigurationPropertyName entryName = getEntryName(source, name);
Object key = getContext().getConversionService()
.convert(getKeyName(entryName), this.keyType);
Object value = this.elementBinder.bind(entryName, valueBindable);
map.putIfAbsent(key, value);
if (source instanceof IterableConfigurationPropertySource) {
for (ConfigurationPropertyName name : (IterableConfigurationPropertySource) source) {
Bindable<?> valueBindable = getValueBindable(name);
ConfigurationPropertyName entryName = getEntryName(source, name);
Object key = getContext().getConversionService()
.convert(getKeyName(entryName), this.keyType);
Object value = this.elementBinder.bind(entryName, valueBindable);
map.putIfAbsent(key, value);
}
}
}

@ -28,6 +28,7 @@ import org.springframework.boot.context.properties.bind.UnboundConfigurationProp
import org.springframework.boot.context.properties.source.ConfigurationProperty;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.IterableConfigurationPropertySource;
/**
* {@link BindHandler} to enforce that all configuration properties under the root name
@ -68,14 +69,9 @@ public class NoUnboundElementsBindHandler extends AbstractBindHandler {
BindContext context) {
Set<ConfigurationProperty> unbound = new TreeSet<>();
for (ConfigurationPropertySource source : context.getSources()) {
ConfigurationPropertySource filtered = source
.filter((candidate) -> isUnbound(name, candidate));
for (ConfigurationPropertyName unboundName : filtered) {
try {
unbound.add(filtered.getConfigurationProperty(unboundName));
}
catch (Exception ex) {
}
if (source instanceof IterableConfigurationPropertySource) {
collectUnbound(name, unbound,
(IterableConfigurationPropertySource) source);
}
}
if (!unbound.isEmpty()) {
@ -83,6 +79,21 @@ public class NoUnboundElementsBindHandler extends AbstractBindHandler {
}
}
private void collectUnbound(ConfigurationPropertyName name,
Set<ConfigurationProperty> unbound,
IterableConfigurationPropertySource source) {
IterableConfigurationPropertySource filtered = source
.filter((candidate) -> isUnbound(name, candidate));
for (ConfigurationPropertyName unboundName : filtered) {
try {
unbound.add(source.filter((candidate) -> isUnbound(name, candidate))
.getConfigurationProperty(unboundName));
}
catch (Exception ex) {
}
}
}
private boolean isUnbound(ConfigurationPropertyName name,
ConfigurationPropertyName candidate) {
return name.isAncestorOf(candidate) && !this.boundNames.contains(candidate);

@ -16,12 +16,7 @@
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.
@ -43,31 +38,24 @@ class AliasedConfigurationPropertySource implements ConfigurationPropertySource
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);
ConfigurationProperty result = getSource().getConfigurationProperty(name);
if (result == null) {
ConfigurationPropertyName aliasedName = this.aliases.getNameForAlias(name);
result = this.source.getConfigurationProperty(aliasedName);
ConfigurationPropertyName aliasedName = getAliases().getNameForAlias(name);
result = getSource().getConfigurationProperty(aliasedName);
}
return result;
}
protected ConfigurationPropertySource getSource() {
return this.source;
}
protected ConfigurationPropertyNameAliases getAliases() {
return this.aliases;
}
}

@ -0,0 +1,60 @@
/*
* 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.CollectionUtils;
/**
* A {@link IterableConfigurationPropertySource} supporting name aliases.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class AliasedIterableConfigurationPropertySource
extends AliasedConfigurationPropertySource
implements IterableConfigurationPropertySource {
AliasedIterableConfigurationPropertySource(IterableConfigurationPropertySource source,
ConfigurationPropertyNameAliases aliases) {
super(source, aliases);
}
@Override
public Stream<ConfigurationPropertyName> stream() {
return StreamSupport.stream(getSource().spliterator(), false)
.flatMap(this::addAliases);
}
private Stream<ConfigurationPropertyName> addAliases(ConfigurationPropertyName name) {
Stream<ConfigurationPropertyName> names = Stream.of(name);
List<ConfigurationPropertyName> aliases = getAliases().getAliases(name);
if (CollectionUtils.isEmpty(aliases)) {
return names;
}
return Stream.concat(names, aliases.stream());
}
@Override
protected IterableConfigurationPropertySource getSource() {
return (IterableConfigurationPropertySource) super.getSource();
}
}

@ -16,16 +16,12 @@
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}.
* A source of {@link ConfigurationProperty ConfigurationProperties}.
*
* @author Phillip Webb
* @author Madhura Bhave
@ -34,7 +30,7 @@ import org.springframework.core.env.PropertySource;
* @see OriginTrackedValue
* @see #getConfigurationProperty(ConfigurationPropertyName)
*/
public interface ConfigurationPropertySource extends Iterable<ConfigurationPropertyName> {
public interface ConfigurationPropertySource {
/**
* Return a single {@link ConfigurationProperty} from the source or {@code null} if no
@ -44,29 +40,10 @@ public interface ConfigurationPropertySource extends Iterable<ConfigurationPrope
*/
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
* @param filter the filter to match
* @return a filtered {@link ConfigurationPropertySource} instance
*/
default ConfigurationPropertySource filter(

@ -24,6 +24,7 @@ import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources;
@ -82,10 +83,16 @@ public class ConfigurationPropertySources
}
private ConfigurationPropertySource adapt(PropertySource<?> source) {
return this.adapters.computeIfAbsent(source, (k) -> {
return new PropertySourceConfigurationPropertySource(source,
getPropertyMapper(source));
});
return this.adapters.computeIfAbsent(source, this::createAdapter);
}
private ConfigurationPropertySource createAdapter(PropertySource<?> source) {
PropertyMapper mapper = getPropertyMapper(source);
if (isFullEnumerable(source)) {
return new PropertySourceIterableConfigurationPropertySource(
(EnumerablePropertySource<?>) source, mapper);
}
return new PropertySourceConfigurationPropertySource(source, mapper);
}
private PropertyMapper getPropertyMapper(PropertySource<?> source) {
@ -95,6 +102,28 @@ public class ConfigurationPropertySources
return DefaultPropertyMapper.INSTANCE;
}
private boolean isFullEnumerable(PropertySource<?> source) {
PropertySource<?> rootSource = getRootSource(source);
if (rootSource.getSource() instanceof Map) {
// Check we're not security restricted
try {
((Map<?, ?>) rootSource.getSource()).size();
}
catch (UnsupportedOperationException ex) {
return false;
}
}
return (source instanceof EnumerablePropertySource);
}
private PropertySource<?> getRootSource(PropertySource<?> source) {
while (source.getSource() != null
&& source.getSource() instanceof PropertySource) {
source = (PropertySource<?>) source.getSource();
}
return source;
}
/**
* Attach a {@link ConfigurationPropertySources} instance to the specified
* {@link ConfigurableEnvironment} so that classic

@ -17,8 +17,6 @@
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;
@ -42,16 +40,19 @@ class FilteredConfigurationPropertiesSource implements ConfigurationPropertySour
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);
boolean filtered = getFilter().test(name);
return (filtered ? getSource().getConfigurationProperty(name) : null);
}
protected ConfigurationPropertySource getSource() {
return this.source;
}
protected Predicate<ConfigurationPropertyName> getFilter() {
return this.filter;
}
}

@ -0,0 +1,49 @@
/*
* 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;
/**
* A filtered {@link IterableConfigurationPropertySource}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class FilteredIterableConfigurationPropertiesSource
extends FilteredConfigurationPropertiesSource
implements IterableConfigurationPropertySource {
FilteredIterableConfigurationPropertiesSource(
IterableConfigurationPropertySource source,
Predicate<ConfigurationPropertyName> filter) {
super(source, filter);
}
@Override
public Stream<ConfigurationPropertyName> stream() {
return StreamSupport.stream(getSource().spliterator(), false).filter(getFilter());
}
@Override
protected IterableConfigurationPropertySource getSource() {
return (IterableConfigurationPropertySource) super.getSource();
}
}

@ -0,0 +1,75 @@
/*
* 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;
/**
* A {@link ConfigurationPropertySource} with a fully {@link Iterable} set of entries.
* Implementations of this interface <strong>must</strong> be able to iterate over all
* contained configuration properties. Any {@code non-null} result from
* {@link #getConfigurationProperty(ConfigurationPropertyName)} must also have an
* equivalent entry in the {@link #iterator() iterator}.
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.0.0
* @see ConfigurationPropertyName
* @see OriginTrackedValue
* @see #getConfigurationProperty(ConfigurationPropertyName)
* @see #iterator()
* @see #stream()
*/
public interface IterableConfigurationPropertySource
extends ConfigurationPropertySource, Iterable<ConfigurationPropertyName> {
/**
* 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();
@Override
default IterableConfigurationPropertySource filter(
Predicate<ConfigurationPropertyName> filter) {
return new FilteredIterableConfigurationPropertiesSource(this, filter);
}
@Override
default IterableConfigurationPropertySource withAliases(
ConfigurationPropertyNameAliases aliases) {
return new AliasedIterableConfigurationPropertySource(this, aliases);
}
}

@ -33,11 +33,12 @@ import org.springframework.util.Assert;
* @author Madhura Bhave
* @since 2.0.0
*/
public class MapConfigurationPropertySource implements ConfigurationPropertySource {
public class MapConfigurationPropertySource
implements IterableConfigurationPropertySource {
private final Map<String, Object> source;
private final ConfigurationPropertySource delegate;
private final IterableConfigurationPropertySource delegate;
/**
* Create a new empty {@link MapConfigurationPropertySource} instance.
@ -53,7 +54,7 @@ public class MapConfigurationPropertySource implements ConfigurationPropertySour
*/
public MapConfigurationPropertySource(Map<?, ?> map) {
this.source = new LinkedHashMap<>();
this.delegate = new PropertySourceConfigurationPropertySource(
this.delegate = new PropertySourceIterableConfigurationPropertySource(
new MapPropertySource("source", this.source),
new DefaultPropertyMapper());
putAll(map);
@ -65,6 +66,7 @@ public class MapConfigurationPropertySource implements ConfigurationPropertySour
*/
public void putAll(Map<?, ?> map) {
Assert.notNull(map, "Map must not be null");
assertNotReadOnlySystemAttributesMap(map);
map.forEach(this::put);
}
@ -93,4 +95,14 @@ public class MapConfigurationPropertySource implements ConfigurationPropertySour
return this.delegate.stream();
}
private void assertNotReadOnlySystemAttributesMap(Map<?, ?> map) {
try {
map.size();
}
catch (UnsupportedOperationException ex) {
throw new IllegalArgumentException(
"Security restricted maps are not supported", ex);
}
}
}

@ -16,47 +16,37 @@
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 java.util.Objects;
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 ConfigurationPropertySource} backed by a non-enumerable Spring
* {@link PropertySource} or a 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
* Each {@link ConfigurationPropertySource#getConfigurationProperty
* getConfigurationProperty} call 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}.
* allows fast property resolution for well formed 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.
* If at all possible the {@link PropertySourceIterableConfigurationPropertySource} should
* be used in preference to this implementation since it supports "relaxed" style
* resolution.
*
* @author Phillip Webb
* @author Madhura Bhave
* @see PropertyMapper
* @see PropertySourceIterableConfigurationPropertySource
*/
class PropertySourceConfigurationPropertySource implements ConfigurationPropertySource {
@ -64,10 +54,6 @@ class PropertySourceConfigurationPropertySource implements ConfigurationProperty
private final PropertyMapper mapper;
private volatile Object cacheKey;
private volatile Cache cache;
/**
* Create a new {@link PropertySourceConfigurationPropertySource} implementation.
* @param propertySource the source property source
@ -84,40 +70,19 @@ class PropertySourceConfigurationPropertySource implements ConfigurationProperty
@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();
List<PropertyMapping> mappings = getMapper().map(getPropertySource(), name);
return find(mappings, name);
}
private ConfigurationProperty find(List<PropertyMapping> mappings,
protected final 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;
return mappings.stream().filter((m) -> m.isApplicable(name)).map(this::find)
.filter(Objects::nonNull).findFirst().orElse(null);
}
private ConfigurationProperty find(PropertyMapping mapping) {
String propertySourceName = mapping.getPropertySourceName();
Object value = this.propertySource.getProperty(propertySourceName);
Object value = getPropertySource().getProperty(propertySourceName);
if (value == null) {
return null;
}
@ -128,77 +93,12 @@ class PropertySourceConfigurationPropertySource implements ConfigurationProperty
return ConfigurationProperty.of(configurationPropertyName, value, origin);
}
@Override
public Stream<ConfigurationPropertyName> stream() {
return getConfigurationPropertyNames().stream();
protected PropertySource<?> getPropertySource() {
return this.propertySource;
}
@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<>(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<>(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;
protected final PropertyMapper getMapper() {
return this.mapper;
}
/**
@ -236,28 +136,4 @@ class PropertySourceConfigurationPropertySource implements ConfigurationProperty
}
private static class Cache {
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;
}
}
}

@ -0,0 +1,175 @@
/*
* 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.core.env.EnumerablePropertySource;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.SystemEnvironmentPropertySource;
import org.springframework.util.ObjectUtils;
/**
* {@link ConfigurationPropertySource} backed by a {@link EnumerablePropertySource}.
* Extends {@link PropertySourceConfigurationPropertySource} with full "relaxed" mapping
* support. In order to use this adapter the underlying {@link PropertySource} must be
* fully enumerable. A security restricted {@link SystemEnvironmentPropertySource} cannot
* be adapted.
*
* @author Phillip Webb
* @author Madhura Bhave
* @see PropertyMapper
*/
class PropertySourceIterableConfigurationPropertySource
extends PropertySourceConfigurationPropertySource
implements IterableConfigurationPropertySource {
PropertySourceIterableConfigurationPropertySource(
EnumerablePropertySource<?> propertySource, PropertyMapper mapper) {
super(propertySource, mapper);
assertEnumerablePropertySource(propertySource);
}
private void assertEnumerablePropertySource(
EnumerablePropertySource<?> propertySource) {
if (getPropertySource() instanceof MapPropertySource) {
try {
((MapPropertySource) getPropertySource()).getSource().size();
}
catch (UnsupportedOperationException ex) {
throw new IllegalArgumentException(
"PropertySource must be fully enumerable");
}
}
}
private volatile Object cacheKey;
private volatile Cache cache;
@Override
public ConfigurationProperty getConfigurationProperty(
ConfigurationPropertyName name) {
ConfigurationProperty configurationProperty = super.getConfigurationProperty(
name);
if (configurationProperty == null) {
configurationProperty = find(getPropertyMappings(), name);
}
return configurationProperty;
}
@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<>(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() {
Cache cache = getCache();
List<PropertyMapping> mappings = (cache != null ? cache.getMappings() : null);
if (mappings != null) {
return mappings;
}
String[] names = getPropertySource().getPropertyNames();
mappings = new ArrayList<>(names.length);
for (String name : names) {
mappings.addAll(getMapper().map(getPropertySource(), 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 (getPropertySource() instanceof MapPropertySource) {
return ((MapPropertySource) getPropertySource()).getSource().keySet();
}
return getPropertySource().getPropertyNames();
}
@Override
protected EnumerablePropertySource<?> getPropertySource() {
return (EnumerablePropertySource<?>) super.getPropertySource();
}
private static class Cache {
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;
}
}
}

@ -167,8 +167,7 @@ public class ArrayBinderTests {
source.put("foo[1]", "2");
source.put("foo[0]", "1");
source.put("foo[2]", "3");
source.setNonIterable(true);
this.sources.add(source);
this.sources.add(source.nonIterable());
Integer[] result = this.binder.bind("foo", INTEGER_ARRAY).get();
assertThat(result).containsExactly(1, 2, 3);
}

@ -175,8 +175,7 @@ public class BinderTests {
throws Exception {
MockConfigurationPropertySource source = new MockConfigurationPropertySource(
"foo.value", "bar");
source.setNonIterable(true);
this.sources.add(source);
this.sources.add(source.nonIterable());
JavaBean result = this.binder.bind("foo", Bindable.of(JavaBean.class)).get();
assertThat(result.getValue()).isEqualTo("bar");
}

@ -48,8 +48,7 @@ public class CollectionBinderTests {
private static final Bindable<List<String>> STRING_LIST = Bindable
.listOf(String.class);
private static final Bindable<Set<String>> STRING_SET = Bindable
.setOf(String.class);
private static final Bindable<Set<String>> STRING_SET = Bindable.setOf(String.class);
private List<ConfigurationPropertySource> sources = new ArrayList<>();
@ -140,8 +139,7 @@ public class CollectionBinderTests {
source.put("foo[1]", "2");
source.put("foo[0]", "1");
source.put("foo[2]", "3");
source.setNonIterable(true);
this.sources.add(source);
this.sources.add(source.nonIterable());
List<Integer> result = this.binder.bind("foo", INTEGER_LIST).get();
assertThat(result).containsExactly(1, 2, 3);
}
@ -267,7 +265,8 @@ public class CollectionBinderTests {
}
@Test
public void bindToCollectionWhenEmptyStringShouldReturnEmptyCollection() throws Exception {
public void bindToCollectionWhenEmptyStringShouldReturnEmptyCollection()
throws Exception {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("foo", "");
this.sources.add(source);

@ -294,8 +294,7 @@ public class JavaBeanBinderTests {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("foo.value-bean.int-value", "123");
source.put("foo.value-bean.string-value", "foo");
source.setNonIterable(true);
this.sources.add(source);
this.sources.add(source.nonIterable());
BindResult<ExampleNestedBeanWithoutSetterOrType> bean = this.binder.bind("foo",
Bindable.of(ExampleNestedBeanWithoutSetterOrType.class));
assertThat(bean.isBound()).isFalse();
@ -423,8 +422,7 @@ public class JavaBeanBinderTests {
public void bindToClassShouldNotInvokeExtraMethods() throws Exception {
MockConfigurationPropertySource source = new MockConfigurationPropertySource(
"foo.value", "123");
source.setNonIterable(true);
this.sources.add(source);
this.sources.add(source.nonIterable());
ExampleWithThrowingGetters bean = this.binder
.bind("foo", Bindable.of(ExampleWithThrowingGetters.class)).get();
assertThat(bean.getValue()).isEqualTo(123);

@ -28,25 +28,12 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
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
ConfigurationPropertySource aliased = source.nonIterable()
.withAliases(new ConfigurationPropertyNameAliases("foo.bar", "foo.bar1"));
assertThat(getValue(aliased, "foo.bar")).isEqualTo("bing");
assertThat(getValue(aliased, "foo.bar1")).isEqualTo("bing");
@ -58,7 +45,7 @@ public class AliasedConfigurationPropertySourceTests {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("foo.bar", "bing");
source.put("foo.baz", "biff");
ConfigurationPropertySource aliased = source
ConfigurationPropertySource aliased = source.nonIterable()
.withAliases(new ConfigurationPropertyNameAliases("foo.bar", "foo.bar1"));
assertThat(getValue(aliased, "foo.baz")).isEqualTo("biff");
}

@ -0,0 +1,45 @@
/*
* 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 AliasedIterableConfigurationPropertySourceTests
extends AliasedConfigurationPropertySourceTests {
@Test
public void streamShouldInclueAliases() throws Exception {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("foo.bar", "bing");
source.put("foo.baz", "biff");
IterableConfigurationPropertySource 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"));
}
}

@ -25,6 +25,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.env.RandomValuePropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
@ -142,6 +143,64 @@ public class ConfigurationPropertySourcesTests {
assertThat(sources.size()).isEqualTo(1);
}
@Test
public void getWhenNonEnumerableShouldNotBeIterable() throws Exception {
StandardEnvironment environment = new StandardEnvironment();
Map<String, Object> source = new LinkedHashMap<String, Object>() {
@Override
public int size() {
throw new UnsupportedOperationException("Same as security restricted");
}
};
PropertySource<?> propertySource = new MapPropertySource("test", source);
environment.getPropertySources().addFirst(propertySource);
ConfigurationPropertySources sources = ConfigurationPropertySources
.get(environment);
ConfigurationPropertySource configurationPropertySource = sources.iterator()
.next();
assertThat(configurationPropertySource)
.isNotInstanceOf(IterableConfigurationPropertySource.class);
}
@Test
public void getWhenEnumerableButRestrictedShouldNotBeIterable() throws Exception {
StandardEnvironment environment = new StandardEnvironment();
PropertySource<?> propertySource = new PropertySource<Object>("test",
new Object()) {
@Override
public Object getProperty(String name) {
return null;
}
};
environment.getPropertySources().addFirst(propertySource);
ConfigurationPropertySources sources = ConfigurationPropertySources
.get(environment);
ConfigurationPropertySource configurationPropertySource = sources.iterator()
.next();
assertThat(configurationPropertySource)
.isNotInstanceOf(IterableConfigurationPropertySource.class);
}
@Test
public void getWhenEnumerableShouldBeIterable() 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 sources = ConfigurationPropertySources
.get(environment);
ConfigurationPropertySource configurationPropertySource = sources.iterator()
.next();
assertThat(configurationPropertySource)
.isInstanceOf(IterableConfigurationPropertySource.class);
}
@Test
public void environmentProperyExpansionShouldWorkWhenAttached() throws Exception {
StandardEnvironment environment = new StandardEnvironment();
@ -177,4 +236,22 @@ public class ConfigurationPropertySourcesTests {
assertThat(configurationSources.iterator()).hasSize(5);
}
@Test
public void containsDescendantOfForRandomSourceShouldDetectNamesStartingRandom()
throws Exception {
StandardEnvironment environment = new StandardEnvironment();
environment.getPropertySources().addFirst(new RandomValuePropertySource());
ConfigurationPropertySource source = ConfigurationPropertySources.get(environment)
.iterator().next();
assertThat(source.containsDescendantOf(ConfigurationPropertyName.of("")))
.contains(true);
assertThat(source.containsDescendantOf(ConfigurationPropertyName.of("random")))
.contains(true);
assertThat(source.containsDescendantOf(ConfigurationPropertyName.of("other")))
.contains(false);
assertThat(
source.containsDescendantOf(ConfigurationPropertyName.of("random.foo")))
.contains(false);
}
}

@ -25,7 +25,7 @@ import org.junit.rules.ExpectedException;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link FilteredConfigurationPropertiesSource}.
* Test for {@link FilteredIterableConfigurationPropertiesSource}.
*
* @author Phillip Webb
* @author Madhura Bhave
@ -50,17 +50,9 @@ public class FilteredConfigurationPropertiesSourceTests {
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 source = createTestSource();
ConfigurationPropertySource filtered = source.filter(this::noBrackets);
ConfigurationPropertyName name = ConfigurationPropertyName.of("a");
assertThat(source.getConfigurationProperty(name).getValue()).isEqualTo("1");
@ -72,14 +64,19 @@ public class FilteredConfigurationPropertiesSourceTests {
}
private MockConfigurationPropertySource createTestSource() {
protected final ConfigurationPropertySource 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;
return convertSource(source);
}
protected ConfigurationPropertySource convertSource(
MockConfigurationPropertySource source) {
return source.nonIterable();
}
private boolean noBrackets(ConfigurationPropertyName name) {

@ -0,0 +1,50 @@
/*
* 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;
/**
* Test for {@link FilteredIterableConfigurationPropertiesSource}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
public class FilteredIterableConfigurationPropertiesSourceTests
extends FilteredConfigurationPropertiesSourceTests {
@Test
public void iteratorShouldFilterNames() throws Exception {
MockConfigurationPropertySource source = (MockConfigurationPropertySource) createTestSource();
IterableConfigurationPropertySource filtered = source.filter(this::noBrackets);
assertThat(filtered.iterator()).extracting(ConfigurationPropertyName::toString)
.containsExactly("a", "b", "c");
}
@Override
protected ConfigurationPropertySource convertSource(
MockConfigurationPropertySource source) {
return source;
}
private boolean noBrackets(ConfigurationPropertyName name) {
return name.toString().indexOf("[") == -1;
}
}

@ -16,7 +16,6 @@
package org.springframework.boot.context.properties.source;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
@ -31,12 +30,11 @@ import org.springframework.boot.origin.OriginTrackedValue;
* @author Phillip Webb
* @author Madhura Bhave
*/
public class MockConfigurationPropertySource implements ConfigurationPropertySource {
public class MockConfigurationPropertySource
implements IterableConfigurationPropertySource {
private final Map<ConfigurationPropertyName, OriginTrackedValue> map = new LinkedHashMap<>();
private boolean nonIterable;
public MockConfigurationPropertySource() {
}
@ -63,23 +61,17 @@ public class MockConfigurationPropertySource implements ConfigurationPropertySou
this.map.put(name, value);
}
public void setNonIterable(boolean nonIterable) {
this.nonIterable = nonIterable;
public ConfigurationPropertySource nonIterable() {
return new 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();
}
@ -97,4 +89,14 @@ public class MockConfigurationPropertySource implements ConfigurationPropertySou
return this.map.get(name);
}
private class NonIterable implements ConfigurationPropertySource {
@Override
public ConfigurationProperty getConfigurationProperty(
ConfigurationPropertyName name) {
return MockConfigurationPropertySource.this.getConfigurationProperty(name);
}
}
}

@ -16,11 +16,8 @@
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;
@ -28,11 +25,8 @@ 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;
@ -62,43 +56,13 @@ public class PropertySourceConfigurationPropertySourceTests {
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));
PropertySource<?> propertySource = new MapPropertySource("test", source);
TestPropertyMapper mapper = new TestPropertyMapper();
ConfigurationPropertyName name = ConfigurationPropertyName.of("my.key");
mapper.addFromConfigurationProperty(name, "key2");
@ -107,28 +71,11 @@ public class PropertySourceConfigurationPropertySourceTests {
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));
PropertySource<?> propertySource = new MapPropertySource("test", source);
TestPropertyMapper mapper = new TestPropertyMapper();
ConfigurationPropertyName name = ConfigurationPropertyName.of("my.key");
mapper.addFromConfigurationProperty(name, "key",
@ -167,25 +114,6 @@ public class PropertySourceConfigurationPropertySourceTests {
.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}.
*/
@ -218,48 +146,4 @@ public class PropertySourceConfigurationPropertySourceTests {
}
/**
* 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());
}
}
}

@ -0,0 +1,196 @@
/*
* 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 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 static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link PropertySourceIterableConfigurationPropertySource}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
public class PropertySourceIterableConfigurationPropertySourceTests {
@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 PropertySourceIterableConfigurationPropertySource(null,
mock(PropertyMapper.class));
}
@Test
public void createWhenMapperIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Mapper must not be null");
new PropertySourceIterableConfigurationPropertySource(
mock(EnumerablePropertySource.class), null);
}
@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");
EnumerablePropertySource<?> 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");
PropertySourceIterableConfigurationPropertySource adapter = new PropertySourceIterableConfigurationPropertySource(
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");
EnumerablePropertySource<?> propertySource = new MapPropertySource("test",
source);
TestPropertyMapper mapper = new TestPropertyMapper();
ConfigurationPropertyName name = ConfigurationPropertyName.of("my.key");
mapper.addFromConfigurationProperty(name, "key2");
PropertySourceIterableConfigurationPropertySource adapter = new PropertySourceIterableConfigurationPropertySource(
propertySource, mapper);
assertThat(adapter.getConfigurationProperty(name).getValue()).isEqualTo("value2");
}
@Test
public void getValueShouldUseEnumerableMapping() throws Exception {
Map<String, Object> source = new LinkedHashMap<>();
source.put("key1", "value1");
source.put("key2", "value2");
source.put("key3", "value3");
EnumerablePropertySource<?> propertySource = new MapPropertySource("test",
source);
TestPropertyMapper mapper = new TestPropertyMapper();
mapper.addFromProperySource("key1", "my.missing");
mapper.addFromProperySource("key2", "my.k-e-y");
PropertySourceIterableConfigurationPropertySource adapter = new PropertySourceIterableConfigurationPropertySource(
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");
EnumerablePropertySource<?> propertySource = new MapPropertySource("test",
source);
TestPropertyMapper mapper = new TestPropertyMapper();
ConfigurationPropertyName name = ConfigurationPropertyName.of("my.key");
mapper.addFromConfigurationProperty(name, "key",
(value) -> value.toString().replace("ue", "let"));
PropertySourceIterableConfigurationPropertySource adapter = new PropertySourceIterableConfigurationPropertySource(
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");
EnumerablePropertySource<?> propertySource = new MapPropertySource("test",
source);
TestPropertyMapper mapper = new TestPropertyMapper();
ConfigurationPropertyName name = ConfigurationPropertyName.of("my.key");
mapper.addFromConfigurationProperty(name, "key");
PropertySourceIterableConfigurationPropertySource adapter = new PropertySourceIterableConfigurationPropertySource(
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");
EnumerablePropertySource<?> propertySource = new OriginCapablePropertySource<>(
new MapPropertySource("test", source));
TestPropertyMapper mapper = new TestPropertyMapper();
ConfigurationPropertyName name = ConfigurationPropertyName.of("my.key");
mapper.addFromConfigurationProperty(name, "key");
PropertySourceIterableConfigurationPropertySource adapter = new PropertySourceIterableConfigurationPropertySource(
propertySource, mapper);
assertThat(adapter.getConfigurationProperty(name).getOrigin().toString())
.isEqualTo("TestOrigin key");
}
/**
* Test {@link PropertySource} that's also a {@link OriginLookup}.
*/
private static class OriginCapablePropertySource<T>
extends EnumerablePropertySource<T> implements OriginLookup<String> {
private final EnumerablePropertySource<T> propertySource;
OriginCapablePropertySource(EnumerablePropertySource<T> propertySource) {
super(propertySource.getName(), propertySource.getSource());
this.propertySource = propertySource;
}
@Override
public Object getProperty(String name) {
return this.propertySource.getProperty(name);
}
@Override
public String[] getPropertyNames() {
return this.propertySource.getPropertyNames();
}
@Override
public Origin getOrigin(String name) {
return new Origin() {
@Override
public String toString() {
return "TestOrigin " + name;
}
};
}
}
}

@ -0,0 +1,68 @@
/*
* 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.List;
import java.util.function.Function;
import org.springframework.core.env.PropertySource;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* Test {@link PropertyMapper} implementation.
*/
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());
}
}
Loading…
Cancel
Save