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
This commit is contained in:
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);
|
||||
List<PropertyMapping> mappings = getMapper().map(getPropertySource(), name);
|
||||
return find(mappings, name);
|
||||
}
|
||||
|
||||
private ConfigurationProperty findByEnumeration(ConfigurationPropertyName name) {
|
||||
List<PropertyMapping> mappings = getPropertyMappings();
|
||||
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…
Reference in New Issue