diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java index cf338cbafc..8970710770 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java @@ -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 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)) { diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/IndexedElementsBinder.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/IndexedElementsBinder.java index 12a0fcd1e8..296b00bf76 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/IndexedElementsBinder.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/IndexedElementsBinder.java @@ -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 extends AggregateBinder { private MultiValueMap getKnownIndexedChildren( ConfigurationPropertySource source, ConfigurationPropertyName root) { MultiValueMap 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); diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/MapBinder.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/MapBinder.java index c014b48221..a80a775829 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/MapBinder.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/MapBinder.java @@ -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> { public void bindEntries(ConfigurationPropertySource source, Map 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); + } } } diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/handler/NoUnboundElementsBindHandler.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/handler/NoUnboundElementsBindHandler.java index fbb4a3fa9b..15ecfb8a8b 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/handler/NoUnboundElementsBindHandler.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/handler/NoUnboundElementsBindHandler.java @@ -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 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 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); diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/source/AliasedConfigurationPropertySource.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/source/AliasedConfigurationPropertySource.java index acfb907db9..303748c0b7 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/properties/source/AliasedConfigurationPropertySource.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/source/AliasedConfigurationPropertySource.java @@ -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 stream() { - return StreamSupport.stream(this.source.spliterator(), false) - .flatMap(this::addAliases); - } - - private Stream addAliases(ConfigurationPropertyName name) { - Stream names = Stream.of(name); - List 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; + } + } diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/source/AliasedIterableConfigurationPropertySource.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/source/AliasedIterableConfigurationPropertySource.java new file mode 100644 index 0000000000..38c0bea31e --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/source/AliasedIterableConfigurationPropertySource.java @@ -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 stream() { + return StreamSupport.stream(getSource().spliterator(), false) + .flatMap(this::addAliases); + } + + private Stream addAliases(ConfigurationPropertyName name) { + Stream names = Stream.of(name); + List aliases = getAliases().getAliases(name); + if (CollectionUtils.isEmpty(aliases)) { + return names; + } + return Stream.concat(names, aliases.stream()); + } + + @Override + protected IterableConfigurationPropertySource getSource() { + return (IterableConfigurationPropertySource) super.getSource(); + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySource.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySource.java index d2109a62a7..80f729577f 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySource.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySource.java @@ -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 { +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 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 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( diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySources.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySources.java index c1690f6c84..78292f48e5 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySources.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/source/ConfigurationPropertySources.java @@ -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 diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/source/FilteredConfigurationPropertiesSource.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/source/FilteredConfigurationPropertiesSource.java index 407bec986c..3788f0d9bf 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/properties/source/FilteredConfigurationPropertiesSource.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/source/FilteredConfigurationPropertiesSource.java @@ -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 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 getFilter() { + return this.filter; } } diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/source/FilteredIterableConfigurationPropertiesSource.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/source/FilteredIterableConfigurationPropertiesSource.java new file mode 100644 index 0000000000..7b85cbf36f --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/source/FilteredIterableConfigurationPropertiesSource.java @@ -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 filter) { + super(source, filter); + } + + @Override + public Stream stream() { + return StreamSupport.stream(getSource().spliterator(), false).filter(getFilter()); + } + + @Override + protected IterableConfigurationPropertySource getSource() { + return (IterableConfigurationPropertySource) super.getSource(); + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/source/IterableConfigurationPropertySource.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/source/IterableConfigurationPropertySource.java new file mode 100644 index 0000000000..55ae3a6c55 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/source/IterableConfigurationPropertySource.java @@ -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 must 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 { + + /** + * 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 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 stream(); + + @Override + default IterableConfigurationPropertySource filter( + Predicate filter) { + return new FilteredIterableConfigurationPropertiesSource(this, filter); + } + + @Override + default IterableConfigurationPropertySource withAliases( + ConfigurationPropertyNameAliases aliases) { + return new AliasedIterableConfigurationPropertySource(this, aliases); + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/source/MapConfigurationPropertySource.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/source/MapConfigurationPropertySource.java index 4334b571ff..cc7bb87e47 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/properties/source/MapConfigurationPropertySource.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/source/MapConfigurationPropertySource.java @@ -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 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); + } + } + } diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/source/PropertySourceConfigurationPropertySource.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/source/PropertySourceConfigurationPropertySource.java index 8bc5856e85..d3808b85e7 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/properties/source/PropertySourceConfigurationPropertySource.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/source/PropertySourceConfigurationPropertySource.java @@ -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. *

- * 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. *

- * 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 mappings = this.mapper.map(this.propertySource, name); - return find(mappings, name); - } - - private ConfigurationProperty findByEnumeration(ConfigurationPropertyName name) { - List mappings = getPropertyMappings(); + List mappings = getMapper().map(getPropertySource(), name); return find(mappings, name); } - private ConfigurationProperty find(List mappings, + protected final ConfigurationProperty find(List 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 stream() { - return getConfigurationPropertyNames().stream(); + protected PropertySource getPropertySource() { + return this.propertySource; } - @Override - public Iterator iterator() { - return getConfigurationPropertyNames().iterator(); - } - - private List getConfigurationPropertyNames() { - Cache cache = getCache(); - List names = (cache != null ? cache.getNames() : null); - if (names != null) { - return names; - } - List 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 getPropertyMappings() { - if (!(this.propertySource instanceof EnumerablePropertySource)) { - return Collections.emptyList(); - } - Cache cache = getCache(); - List 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 names; - - private List mappings; - - public List getNames() { - return this.names; - } - - public void setNames(List names) { - this.names = names; - } - - public List getMappings() { - return this.mappings; - } - - public void setMappings(List mappings) { - this.mappings = mappings; - } - - } - } diff --git a/spring-boot/src/main/java/org/springframework/boot/context/properties/source/PropertySourceIterableConfigurationPropertySource.java b/spring-boot/src/main/java/org/springframework/boot/context/properties/source/PropertySourceIterableConfigurationPropertySource.java new file mode 100644 index 0000000000..79f06d8a33 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/context/properties/source/PropertySourceIterableConfigurationPropertySource.java @@ -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 stream() { + return getConfigurationPropertyNames().stream(); + } + + @Override + public Iterator iterator() { + return getConfigurationPropertyNames().iterator(); + } + + private List getConfigurationPropertyNames() { + Cache cache = getCache(); + List names = (cache != null ? cache.getNames() : null); + if (names != null) { + return names; + } + List 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 getPropertyMappings() { + Cache cache = getCache(); + List 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 names; + + private List mappings; + + public List getNames() { + return this.names; + } + + public void setNames(List names) { + this.names = names; + } + + public List getMappings() { + return this.mappings; + } + + public void setMappings(List mappings) { + this.mappings = mappings; + } + + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ArrayBinderTests.java b/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ArrayBinderTests.java index 3510c971a1..ec6222257f 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ArrayBinderTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ArrayBinderTests.java @@ -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); } diff --git a/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BinderTests.java b/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BinderTests.java index 7935c8f84d..48d135e83f 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BinderTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BinderTests.java @@ -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"); } diff --git a/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/CollectionBinderTests.java b/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/CollectionBinderTests.java index 5fc7f64f7b..edeed1ec56 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/CollectionBinderTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/CollectionBinderTests.java @@ -48,8 +48,7 @@ public class CollectionBinderTests { private static final Bindable> STRING_LIST = Bindable .listOf(String.class); - private static final Bindable> STRING_SET = Bindable - .setOf(String.class); + private static final Bindable> STRING_SET = Bindable.setOf(String.class); private List 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 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); diff --git a/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/JavaBeanBinderTests.java b/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/JavaBeanBinderTests.java index 3bc3f73ed4..01b0343c1d 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/JavaBeanBinderTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/JavaBeanBinderTests.java @@ -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 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); diff --git a/spring-boot/src/test/java/org/springframework/boot/context/properties/source/AliasedConfigurationPropertySourceTests.java b/spring-boot/src/test/java/org/springframework/boot/context/properties/source/AliasedConfigurationPropertySourceTests.java index 8fc13652dd..7daf45a35f 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/properties/source/AliasedConfigurationPropertySourceTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/properties/source/AliasedConfigurationPropertySourceTests.java @@ -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"); } diff --git a/spring-boot/src/test/java/org/springframework/boot/context/properties/source/AliasedIterableConfigurationPropertySourceTests.java b/spring-boot/src/test/java/org/springframework/boot/context/properties/source/AliasedIterableConfigurationPropertySourceTests.java new file mode 100644 index 0000000000..ad559564db --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/context/properties/source/AliasedIterableConfigurationPropertySourceTests.java @@ -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")); + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/context/properties/source/ConfigurationPropertySourcesTests.java b/spring-boot/src/test/java/org/springframework/boot/context/properties/source/ConfigurationPropertySourcesTests.java index 554600c23b..00437f3185 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/properties/source/ConfigurationPropertySourcesTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/properties/source/ConfigurationPropertySourcesTests.java @@ -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 source = new LinkedHashMap() { + + @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("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 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); + } + } diff --git a/spring-boot/src/test/java/org/springframework/boot/context/properties/source/FilteredConfigurationPropertiesSourceTests.java b/spring-boot/src/test/java/org/springframework/boot/context/properties/source/FilteredConfigurationPropertiesSourceTests.java index b0b760dc49..5a991a8ef1 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/properties/source/FilteredConfigurationPropertiesSourceTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/properties/source/FilteredConfigurationPropertiesSourceTests.java @@ -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) { diff --git a/spring-boot/src/test/java/org/springframework/boot/context/properties/source/FilteredIterableConfigurationPropertiesSourceTests.java b/spring-boot/src/test/java/org/springframework/boot/context/properties/source/FilteredIterableConfigurationPropertiesSourceTests.java new file mode 100644 index 0000000000..c274376338 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/context/properties/source/FilteredIterableConfigurationPropertiesSourceTests.java @@ -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; + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/context/properties/source/MockConfigurationPropertySource.java b/spring-boot/src/test/java/org/springframework/boot/context/properties/source/MockConfigurationPropertySource.java index 94e01c9e72..838f268c64 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/properties/source/MockConfigurationPropertySource.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/properties/source/MockConfigurationPropertySource.java @@ -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 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 iterator() { - if (this.nonIterable) { - return Collections.emptyList().iterator(); - } return this.map.keySet().iterator(); } @Override public Stream stream() { - if (this.nonIterable) { - return Collections.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); + } + + } + } diff --git a/spring-boot/src/test/java/org/springframework/boot/context/properties/source/PropertySourceConfigurationPropertySourceTests.java b/spring-boot/src/test/java/org/springframework/boot/context/properties/source/PropertySourceConfigurationPropertySourceTests.java index 13a43baa37..2c8faa827e 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/properties/source/PropertySourceConfigurationPropertySourceTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/properties/source/PropertySourceConfigurationPropertySourceTests.java @@ -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 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 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 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 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 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 extends PropertySource { - - private final PropertySource propertySource; - - NonEnumerablePropertySource(PropertySource 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 fromSource = new LinkedMultiValueMap<>(); - - private MultiValueMap 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 extractor) { - this.fromConfig.add(from, new PropertyMapping(to, from, extractor)); - } - - @Override - public List map(PropertySource propertySource, - String propertySourceName) { - return this.fromSource.getOrDefault(propertySourceName, - Collections.emptyList()); - } - - @Override - public List map(PropertySource propertySource, - ConfigurationPropertyName configurationPropertyName) { - return this.fromConfig.getOrDefault(configurationPropertyName, - Collections.emptyList()); - } - - } - } diff --git a/spring-boot/src/test/java/org/springframework/boot/context/properties/source/PropertySourceIterableConfigurationPropertySourceTests.java b/spring-boot/src/test/java/org/springframework/boot/context/properties/source/PropertySourceIterableConfigurationPropertySourceTests.java new file mode 100644 index 0000000000..9f0de2dfee --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/context/properties/source/PropertySourceIterableConfigurationPropertySourceTests.java @@ -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 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 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 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 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 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 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 + extends EnumerablePropertySource implements OriginLookup { + + private final EnumerablePropertySource propertySource; + + OriginCapablePropertySource(EnumerablePropertySource 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; + } + + }; + } + + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/context/properties/source/TestPropertyMapper.java b/spring-boot/src/test/java/org/springframework/boot/context/properties/source/TestPropertyMapper.java new file mode 100644 index 0000000000..d6ed30f8bc --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/context/properties/source/TestPropertyMapper.java @@ -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 fromSource = new LinkedMultiValueMap<>(); + + private MultiValueMap 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 extractor) { + this.fromConfig.add(from, new PropertyMapping(to, from, extractor)); + } + + @Override + public List map(PropertySource propertySource, + String propertySourceName) { + return this.fromSource.getOrDefault(propertySourceName, Collections.emptyList()); + } + + @Override + public List map(PropertySource propertySource, + ConfigurationPropertyName configurationPropertyName) { + return this.fromConfig.getOrDefault(configurationPropertyName, + Collections.emptyList()); + } + +}