Add ConfigurationPropertyCaching support

Add a `ConfigurationPropertyCaching` utility interface that can be
used to control the property source caching.

Prior to this commit, a `ConfigurationPropertySource` that was backed
by a mutable `EnumerablePropertySource` would need to call the
`getPropertyNames()` method each time a property was accessed. Since
this this operation can be expensive, we now provide a way to cache
the results for a specific length of time.

This commit also improves the performance of immutable property sources
by limiting the number of candidates that need to be searched.
Previously, all mapped names would be enumerated. Now, mappings are
grouped by `ConfigurationPropertyName`. This is especially helpful when
the `ConfigurationPropertyName` isn't mapped at all since the hash based
map lookup will be very fast and the resulting mappings will be empty.

Closes gh-20625
This commit is contained in:
Phillip Webb 2020-05-04 22:40:48 -07:00
parent 85e9a73e85
commit 7afd25fc9a
23 changed files with 1125 additions and 529 deletions

View File

@ -0,0 +1,46 @@
/*
* Copyright 2012-2020 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
*
* https://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;
/**
* Interface used to indicate that a {@link ConfigurationPropertySource} supports
* {@link ConfigurationPropertyCaching}.
*
* @author Phillip Webb
*/
interface CachingConfigurationPropertySource {
/**
* Return {@link ConfigurationPropertyCaching} for this source.
* @return source caching
*/
ConfigurationPropertyCaching getCaching();
/**
* Find {@link ConfigurationPropertyCaching} for the given source.
* @param source the configuration property source
* @return a {@link ConfigurationPropertyCaching} instance or {@code null} if the
* source does not support caching.
*/
static ConfigurationPropertyCaching find(ConfigurationPropertySource source) {
if (source instanceof CachingConfigurationPropertySource) {
return ((CachingConfigurationPropertySource) source).getCaching();
}
return null;
}
}

View File

@ -0,0 +1,110 @@
/*
* Copyright 2012-2020 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
*
* https://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.time.Duration;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
/**
* Interface that can be used to control configuration property source caches.
*
* @author Phillip Webb
* @since 2.3.0
*/
public interface ConfigurationPropertyCaching {
/**
* Enable caching with an unlimited time-to-live.
*/
void enable();
/**
* Disable caching.
*/
void disable();
/**
* Set amount of time that an item can live in the cache. Calling this method will
* also enable the cache.
* @param timeToLive the time to live value.
*/
void setTimeToLive(Duration timeToLive);
/**
* Clear the cache and force it to be reloaded on next access.
*/
void clear();
/**
* Get for all configuration property sources in the environment.
* @param environment the spring environment
* @return a caching instance that controls all sources in the environment
*/
static ConfigurationPropertyCaching get(Environment environment) {
return get(environment, null);
}
/**
* Get for a specific configuration property source in the environment.
* @param environment the spring environment
* @param underlyingSource the
* {@link ConfigurationPropertySource#getUnderlyingSource() underlying source} that
* must match
* @return a caching instance that controls the matching source
*/
static ConfigurationPropertyCaching get(Environment environment, Object underlyingSource) {
Iterable<ConfigurationPropertySource> sources = ConfigurationPropertySources.get(environment);
return get(sources, underlyingSource);
}
/**
* Get for all specified configuration property sources.
* @param sources the configuration property sources
* @return a caching instance that controls the sources
*/
static ConfigurationPropertyCaching get(Iterable<ConfigurationPropertySource> sources) {
return get(sources, null);
}
/**
* Get for a specific configuration property source in the specified configuration
* property sources.
* @param sources the configuration property sources
* @param underlyingSource the
* {@link ConfigurationPropertySource#getUnderlyingSource() underlying source} that
* must match
* @return a caching instance that controls the matching source
*/
static ConfigurationPropertyCaching get(Iterable<ConfigurationPropertySource> sources, Object underlyingSource) {
Assert.notNull(sources, "Sources must not be null");
if (underlyingSource == null) {
return new ConfigurationPropertySourcesCaching(sources);
}
for (ConfigurationPropertySource source : sources) {
if (source.getUnderlyingSource() == underlyingSource) {
ConfigurationPropertyCaching caching = CachingConfigurationPropertySource.find(source);
if (caching != null) {
return caching;
}
}
}
throw new IllegalStateException("Unable to find cache from configuration property sources");
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright 2012-2020 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
*
* https://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.time.Duration;
import java.util.function.Consumer;
/**
* {@link ConfigurationPropertyCaching} for an {@link Iterable iterable} set of
* {@link ConfigurationPropertySource} instances.
*
* @author Phillip Webb
*/
class ConfigurationPropertySourcesCaching implements ConfigurationPropertyCaching {
private final Iterable<ConfigurationPropertySource> sources;
ConfigurationPropertySourcesCaching(Iterable<ConfigurationPropertySource> sources) {
this.sources = sources;
}
@Override
public void enable() {
forEach(ConfigurationPropertyCaching::enable);
}
@Override
public void disable() {
forEach(ConfigurationPropertyCaching::disable);
}
@Override
public void setTimeToLive(Duration timeToLive) {
forEach((caching) -> caching.setTimeToLive(timeToLive));
}
@Override
public void clear() {
forEach(ConfigurationPropertyCaching::clear);
}
private void forEach(Consumer<ConfigurationPropertyCaching> action) {
if (this.sources != null) {
for (ConfigurationPropertySource source : this.sources) {
ConfigurationPropertyCaching caching = CachingConfigurationPropertySource.find(source);
if (caching != null) {
action.accept(caching);
}
}
}
}
}

View File

@ -16,6 +16,9 @@
package org.springframework.boot.context.properties.source;
import java.util.Collections;
import java.util.List;
import org.springframework.util.ObjectUtils;
/**
@ -32,48 +35,48 @@ final class DefaultPropertyMapper implements PropertyMapper {
public static final PropertyMapper INSTANCE = new DefaultPropertyMapper();
private LastMapping<ConfigurationPropertyName> lastMappedConfigurationPropertyName;
private LastMapping<ConfigurationPropertyName, List<String>> lastMappedConfigurationPropertyName;
private LastMapping<String> lastMappedPropertyName;
private LastMapping<String, ConfigurationPropertyName> lastMappedPropertyName;
private DefaultPropertyMapper() {
}
@Override
public PropertyMapping[] map(ConfigurationPropertyName configurationPropertyName) {
public List<String> map(ConfigurationPropertyName configurationPropertyName) {
// Use a local copy in case another thread changes things
LastMapping<ConfigurationPropertyName> last = this.lastMappedConfigurationPropertyName;
LastMapping<ConfigurationPropertyName, List<String>> last = this.lastMappedConfigurationPropertyName;
if (last != null && last.isFrom(configurationPropertyName)) {
return last.getMapping();
}
String convertedName = configurationPropertyName.toString();
PropertyMapping[] mapping = { new PropertyMapping(convertedName, configurationPropertyName) };
List<String> mapping = Collections.singletonList(convertedName);
this.lastMappedConfigurationPropertyName = new LastMapping<>(configurationPropertyName, mapping);
return mapping;
}
@Override
public PropertyMapping[] map(String propertySourceName) {
public ConfigurationPropertyName map(String propertySourceName) {
// Use a local copy in case another thread changes things
LastMapping<String> last = this.lastMappedPropertyName;
LastMapping<String, ConfigurationPropertyName> last = this.lastMappedPropertyName;
if (last != null && last.isFrom(propertySourceName)) {
return last.getMapping();
}
PropertyMapping[] mapping = tryMap(propertySourceName);
ConfigurationPropertyName mapping = tryMap(propertySourceName);
this.lastMappedPropertyName = new LastMapping<>(propertySourceName, mapping);
return mapping;
}
private PropertyMapping[] tryMap(String propertySourceName) {
private ConfigurationPropertyName tryMap(String propertySourceName) {
try {
ConfigurationPropertyName convertedName = ConfigurationPropertyName.adapt(propertySourceName, '.');
if (!convertedName.isEmpty()) {
return new PropertyMapping[] { new PropertyMapping(propertySourceName, convertedName) };
return convertedName;
}
}
catch (Exception ex) {
}
return NO_MAPPINGS;
return ConfigurationPropertyName.EMPTY;
}
@Override
@ -81,13 +84,13 @@ final class DefaultPropertyMapper implements PropertyMapper {
return name.isAncestorOf(candidate);
}
private static class LastMapping<T> {
private static class LastMapping<T, M> {
private final T from;
private final PropertyMapping[] mapping;
private final M mapping;
LastMapping(T from, PropertyMapping[] mapping) {
LastMapping(T from, M mapping) {
this.from = from;
this.mapping = mapping;
}
@ -96,7 +99,7 @@ final class DefaultPropertyMapper implements PropertyMapper {
return ObjectUtils.nullSafeEquals(from, this.from);
}
PropertyMapping[] getMapping() {
M getMapping() {
return this.mapping;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -35,6 +35,8 @@ import org.springframework.util.Assert;
*/
public class MapConfigurationPropertySource implements IterableConfigurationPropertySource {
private static final PropertyMapper[] DEFAULT_MAPPERS = { DefaultPropertyMapper.INSTANCE };
private final Map<String, Object> source;
private final IterableConfigurationPropertySource delegate;
@ -53,8 +55,8 @@ public class MapConfigurationPropertySource implements IterableConfigurationProp
*/
public MapConfigurationPropertySource(Map<?, ?> map) {
this.source = new LinkedHashMap<>();
this.delegate = new SpringIterableConfigurationPropertySource(new MapPropertySource("source", this.source),
DefaultPropertyMapper.INSTANCE);
MapPropertySource mapPropertySource = new MapPropertySource("source", this.source);
this.delegate = new SpringIterableConfigurationPropertySource(mapPropertySource, DEFAULT_MAPPERS);
putAll(map);
}

View File

@ -16,6 +16,8 @@
package org.springframework.boot.context.properties.source;
import java.util.List;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.PropertySource;
@ -28,8 +30,7 @@ import org.springframework.core.env.PropertySource;
* {@link SpringConfigurationPropertySource} to first attempt any direct mappings (i.e.
* map the {@link ConfigurationPropertyName} directly to the {@link PropertySource} name)
* before falling back to {@link EnumerablePropertySource enumerating} property names,
* mapping them to a {@link ConfigurationPropertyName} and checking for
* {@link PropertyMapping#isApplicable(ConfigurationPropertyName) applicability}. See
* mapping them to a {@link ConfigurationPropertyName} and checking for applicability. See
* {@link SpringConfigurationPropertySource} for more details.
*
* @author Phillip Webb
@ -38,22 +39,21 @@ import org.springframework.core.env.PropertySource;
*/
interface PropertyMapper {
PropertyMapping[] NO_MAPPINGS = {};
/**
* Provide mappings from a {@link ConfigurationPropertySource}
* {@link ConfigurationPropertyName}.
* @param configurationPropertyName the name to map
* @return a stream of mappings or {@code Stream#empty()}
* @return the mapped names or an empty list
*/
PropertyMapping[] map(ConfigurationPropertyName configurationPropertyName);
List<String> map(ConfigurationPropertyName configurationPropertyName);
/**
* Provide mappings from a {@link PropertySource} property name.
* @param propertySourceName the name to map
* @return a stream of mappings or {@code Stream#empty()}
* @return the mapped configuration property name or
* {@link ConfigurationPropertyName#EMPTY}
*/
PropertyMapping[] map(String propertySourceName);
ConfigurationPropertyName map(String propertySourceName);
/**
* Returns {@code true} if {@code name} is an ancestor (immediate or nested parent) of

View File

@ -1,73 +0,0 @@
/*
* Copyright 2012-2019 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.source;
import org.springframework.core.env.PropertySource;
/**
* Details a mapping between a {@link PropertySource} item and a
* {@link ConfigurationPropertySource} item.
*
* @author Phillip Webb
* @author Madhura Bhave
* @see SpringConfigurationPropertySource
*/
class PropertyMapping {
private final String propertySourceName;
private final ConfigurationPropertyName configurationPropertyName;
/**
* Create a new {@link PropertyMapper} instance.
* @param propertySourceName the {@link PropertySource} name
* @param configurationPropertyName the {@link ConfigurationPropertySource}
* {@link ConfigurationPropertyName}
*/
PropertyMapping(String propertySourceName, ConfigurationPropertyName configurationPropertyName) {
this.propertySourceName = propertySourceName;
this.configurationPropertyName = configurationPropertyName;
}
/**
* Return the mapped {@link PropertySource} name.
* @return the property source name (never {@code null})
*/
String getPropertySourceName() {
return this.propertySourceName;
}
/**
* Return the mapped {@link ConfigurationPropertySource}
* {@link ConfigurationPropertyName}.
* @return the configuration property source name (never {@code null})
*/
ConfigurationPropertyName getConfigurationPropertyName() {
return this.configurationPropertyName;
}
/**
* Return if this mapping is applicable for the given
* {@link ConfigurationPropertyName}.
* @param name the name to check
* @return if the mapping is applicable
*/
boolean isApplicable(ConfigurationPropertyName name) {
return this.configurationPropertyName.equals(name);
}
}

View File

@ -0,0 +1,114 @@
/*
* Copyright 2012-2020 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
*
* https://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.lang.ref.SoftReference;
import java.time.Duration;
import java.time.Instant;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
/**
* Simple cache that uses a {@link SoftReference} to cache a value for as long as
* possible.
*
* @param <T> the value type
* @author Phillip Webb
*/
class SoftReferenceConfigurationPropertyCache<T> implements ConfigurationPropertyCaching {
private static final Duration UNLIMITED = Duration.ZERO;
private final boolean neverExpire;
private volatile Duration timeToLive;
private volatile SoftReference<T> value = new SoftReference<>(null);
private volatile Instant lastAccessed = now();
SoftReferenceConfigurationPropertyCache(boolean neverExpire) {
this.neverExpire = neverExpire;
}
@Override
public void enable() {
this.timeToLive = UNLIMITED;
}
@Override
public void disable() {
this.timeToLive = null;
}
@Override
public void setTimeToLive(Duration timeToLive) {
this.timeToLive = (timeToLive == null || timeToLive.isZero()) ? null : timeToLive;
}
@Override
public void clear() {
this.lastAccessed = null;
}
/**
* Get an value from the cache, creating it if necessary.
* @param factory a factory used to create the item if there is no reference to it.
* @param refreshAction action called to refresh the value if it has expired
* @return the value from the cache
*/
T get(Supplier<T> factory, UnaryOperator<T> refreshAction) {
T value = getValue();
if (value == null) {
value = refreshAction.apply(factory.get());
setValue(value);
}
else if (hasExpired()) {
value = refreshAction.apply(value);
setValue(value);
}
if (!this.neverExpire) {
this.lastAccessed = now();
}
return value;
}
private boolean hasExpired() {
if (this.neverExpire) {
return false;
}
Duration timeToLive = this.timeToLive;
Instant lastAccessed = this.lastAccessed;
if (timeToLive == null || lastAccessed == null) {
return true;
}
return !UNLIMITED.equals(timeToLive) && now().isAfter(lastAccessed.plus(timeToLive));
}
protected Instant now() {
return Instant.now();
}
protected T getValue() {
return this.value.get();
}
protected void setValue(T value) {
this.value = new SoftReference<>(value);
}
}

View File

@ -18,7 +18,6 @@ package org.springframework.boot.context.properties.source;
import java.util.Map;
import java.util.Random;
import java.util.function.Function;
import org.springframework.boot.origin.Origin;
import org.springframework.boot.origin.PropertySourceOrigin;
@ -27,7 +26,6 @@ import org.springframework.core.env.PropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.env.SystemEnvironmentPropertySource;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* {@link ConfigurationPropertySource} backed by a non-enumerable Spring
@ -55,38 +53,60 @@ class SpringConfigurationPropertySource implements ConfigurationPropertySource {
private static final ConfigurationPropertyName RANDOM = ConfigurationPropertyName.of("random");
private static final PropertyMapper[] DEFAULT_MAPPERS = { DefaultPropertyMapper.INSTANCE };
private static final PropertyMapper[] SYSTEM_ENVIRONMENT_MAPPERS = { SystemEnvironmentPropertyMapper.INSTANCE,
DefaultPropertyMapper.INSTANCE };
private final PropertySource<?> propertySource;
private final PropertyMapper mapper;
private final Function<ConfigurationPropertyName, ConfigurationPropertyState> containsDescendantOf;
private final PropertyMapper[] mappers;
/**
* Create a new {@link SpringConfigurationPropertySource} implementation.
* @param propertySource the source property source
* @param mapper the property mapper
* @param containsDescendantOf function used to implement
* {@link #containsDescendantOf(ConfigurationPropertyName)} (may be {@code null})
* @param mappers the property mappers
*/
SpringConfigurationPropertySource(PropertySource<?> propertySource, PropertyMapper mapper,
Function<ConfigurationPropertyName, ConfigurationPropertyState> containsDescendantOf) {
SpringConfigurationPropertySource(PropertySource<?> propertySource, PropertyMapper... mappers) {
Assert.notNull(propertySource, "PropertySource must not be null");
Assert.notNull(mapper, "Mapper must not be null");
this.propertySource = propertySource;
this.mapper = (mapper instanceof DelegatingPropertyMapper) ? mapper : new DelegatingPropertyMapper(mapper);
this.containsDescendantOf = (containsDescendantOf != null) ? containsDescendantOf
: (n) -> ConfigurationPropertyState.UNKNOWN;
this.mappers = mappers;
}
@Override
public ConfigurationProperty getConfigurationProperty(ConfigurationPropertyName name) {
PropertyMapping[] mappings = getMapper().map(name);
return find(mappings, name);
if (name == null) {
return null;
}
for (PropertyMapper mapper : this.mappers) {
try {
for (String candidate : mapper.map(name)) {
Object value = getPropertySource().getProperty(candidate);
if (value != null) {
Origin origin = PropertySourceOrigin.get(getPropertySource(), candidate);
return ConfigurationProperty.of(name, value, origin);
}
}
}
catch (Exception ex) {
}
}
return null;
}
@Override
public ConfigurationPropertyState containsDescendantOf(ConfigurationPropertyName name) {
return this.containsDescendantOf.apply(name);
if (getPropertySource().getSource() instanceof Random) {
return containsDescendantOfForRandom(name);
}
return ConfigurationPropertyState.UNKNOWN;
}
private static ConfigurationPropertyState containsDescendantOfForRandom(ConfigurationPropertyName name) {
if (name.isAncestorOf(RANDOM) || name.equals(RANDOM)) {
return ConfigurationPropertyState.PRESENT;
}
return ConfigurationPropertyState.ABSENT;
}
@Override
@ -94,35 +114,12 @@ class SpringConfigurationPropertySource implements ConfigurationPropertySource {
return this.propertySource;
}
protected final ConfigurationProperty find(PropertyMapping[] mappings, ConfigurationPropertyName name) {
for (PropertyMapping candidate : mappings) {
if (candidate.isApplicable(name)) {
ConfigurationProperty result = find(candidate);
if (result != null) {
return result;
}
}
}
return null;
}
private ConfigurationProperty find(PropertyMapping mapping) {
String propertySourceName = mapping.getPropertySourceName();
Object value = getPropertySource().getProperty(propertySourceName);
if (value == null) {
return null;
}
ConfigurationPropertyName configurationPropertyName = mapping.getConfigurationPropertyName();
Origin origin = PropertySourceOrigin.get(this.propertySource, propertySourceName);
return ConfigurationProperty.of(configurationPropertyName, value, origin);
}
protected PropertySource<?> getPropertySource() {
return this.propertySource;
}
protected final PropertyMapper getMapper() {
return this.mapper;
protected final PropertyMapper[] getMappers() {
return this.mappers;
}
@Override
@ -139,19 +136,18 @@ class SpringConfigurationPropertySource implements ConfigurationPropertySource {
*/
static SpringConfigurationPropertySource from(PropertySource<?> source) {
Assert.notNull(source, "Source must not be null");
PropertyMapper mapper = getPropertyMapper(source);
PropertyMapper[] mappers = getPropertyMappers(source);
if (isFullEnumerable(source)) {
return new SpringIterableConfigurationPropertySource((EnumerablePropertySource<?>) source, mapper);
return new SpringIterableConfigurationPropertySource((EnumerablePropertySource<?>) source, mappers);
}
return new SpringConfigurationPropertySource(source, mapper, getContainsDescendantOfForSource(source));
return new SpringConfigurationPropertySource(source, mappers);
}
private static PropertyMapper getPropertyMapper(PropertySource<?> source) {
private static PropertyMapper[] getPropertyMappers(PropertySource<?> source) {
if (source instanceof SystemEnvironmentPropertySource && hasSystemEnvironmentName(source)) {
return new DelegatingPropertyMapper(SystemEnvironmentPropertyMapper.INSTANCE,
DefaultPropertyMapper.INSTANCE);
return SYSTEM_ENVIRONMENT_MAPPERS;
}
return new DelegatingPropertyMapper(DefaultPropertyMapper.INSTANCE);
return DEFAULT_MAPPERS;
}
private static boolean hasSystemEnvironmentName(PropertySource<?> source) {
@ -181,97 +177,4 @@ class SpringConfigurationPropertySource implements ConfigurationPropertySource {
return source;
}
private static Function<ConfigurationPropertyName, ConfigurationPropertyState> getContainsDescendantOfForSource(
PropertySource<?> source) {
if (source.getSource() instanceof Random) {
return SpringConfigurationPropertySource::containsDescendantOfForRandom;
}
return null;
}
private static ConfigurationPropertyState containsDescendantOfForRandom(ConfigurationPropertyName name) {
if (name.isAncestorOf(RANDOM) || name.equals(RANDOM)) {
return ConfigurationPropertyState.PRESENT;
}
return ConfigurationPropertyState.ABSENT;
}
/**
* {@link PropertyMapper} that delegates to other {@link PropertyMapper}s and also
* swallows exceptions when the mapping fails.
*/
private static class DelegatingPropertyMapper implements PropertyMapper {
private static final PropertyMapping[] NONE = {};
private final PropertyMapper first;
private final PropertyMapper second;
DelegatingPropertyMapper(PropertyMapper first) {
this(first, null);
}
DelegatingPropertyMapper(PropertyMapper first, PropertyMapper second) {
this.first = first;
this.second = second;
}
@Override
public PropertyMapping[] map(ConfigurationPropertyName configurationPropertyName) {
PropertyMapping[] first = map(this.first, configurationPropertyName);
PropertyMapping[] second = map(this.second, configurationPropertyName);
return merge(first, second);
}
private PropertyMapping[] map(PropertyMapper mapper, ConfigurationPropertyName configurationPropertyName) {
try {
return (mapper != null) ? mapper.map(configurationPropertyName) : NONE;
}
catch (Exception ex) {
return NONE;
}
}
@Override
public PropertyMapping[] map(String propertySourceName) {
PropertyMapping[] first = map(this.first, propertySourceName);
PropertyMapping[] second = map(this.second, propertySourceName);
return merge(first, second);
}
private PropertyMapping[] map(PropertyMapper mapper, String propertySourceName) {
try {
return (mapper != null) ? mapper.map(propertySourceName) : NONE;
}
catch (Exception ex) {
return NONE;
}
}
@Override
public boolean isAncestorOf(ConfigurationPropertyName name, ConfigurationPropertyName candidate) {
return isAncestorOf(this.first, name, candidate) || isAncestorOf(this.second, name, candidate);
}
private boolean isAncestorOf(PropertyMapper mapper, ConfigurationPropertyName name,
ConfigurationPropertyName candidate) {
return mapper != null && mapper.isAncestorOf(name, candidate);
}
private PropertyMapping[] merge(PropertyMapping[] first, PropertyMapping[] second) {
if (ObjectUtils.isEmpty(second)) {
return first;
}
if (ObjectUtils.isEmpty(first)) {
return second;
}
PropertyMapping[] merged = new PropertyMapping[first.length + second.length];
System.arraycopy(first, 0, merged, 0, first.length);
System.arraycopy(second, 0, merged, first.length, second.length);
return merged;
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.

View File

@ -17,21 +17,27 @@
package org.springframework.boot.context.properties.source;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.HashSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.springframework.boot.origin.Origin;
import org.springframework.boot.origin.OriginLookup;
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.core.env.StandardEnvironment;
import org.springframework.core.env.SystemEnvironmentPropertySource;
import org.springframework.util.ObjectUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* {@link ConfigurationPropertySource} backed by an {@link EnumerablePropertySource}.
@ -45,13 +51,16 @@ import org.springframework.util.ObjectUtils;
* @see PropertyMapper
*/
class SpringIterableConfigurationPropertySource extends SpringConfigurationPropertySource
implements IterableConfigurationPropertySource {
implements IterableConfigurationPropertySource, CachingConfigurationPropertySource {
private volatile Cache cache;
private volatile Collection<ConfigurationPropertyName> configurationPropertyNames;
SpringIterableConfigurationPropertySource(EnumerablePropertySource<?> propertySource, PropertyMapper mapper) {
super(propertySource, mapper, null);
private final SoftReferenceConfigurationPropertyCache<Mappings> cache;
SpringIterableConfigurationPropertySource(EnumerablePropertySource<?> propertySource, PropertyMapper... mappers) {
super(propertySource, mappers);
assertEnumerablePropertySource();
this.cache = new SoftReferenceConfigurationPropertyCache<>(isImmutablePropertySource());
}
private void assertEnumerablePropertySource() {
@ -66,13 +75,28 @@ class SpringIterableConfigurationPropertySource extends SpringConfigurationPrope
}
@Override
public ConfigurationProperty getConfigurationProperty(ConfigurationPropertyName name) {
ConfigurationProperty configurationProperty = super.getConfigurationProperty(name);
if (configurationProperty == null) {
configurationProperty = find(getPropertyMappings(getCache()), name);
public ConfigurationPropertyCaching getCaching() {
return this.cache;
}
@Override
public ConfigurationProperty getConfigurationProperty(ConfigurationPropertyName name) {
if (name == null) {
return null;
}
ConfigurationProperty configurationProperty = super.getConfigurationProperty(name);
if (configurationProperty != null) {
return configurationProperty;
}
for (String candidate : getMappings().getMapped(name)) {
Object value = getPropertySource().getProperty(candidate);
if (value != null) {
Origin origin = PropertySourceOrigin.get(getPropertySource(), candidate);
return ConfigurationProperty.of(name, value, origin);
}
}
return null;
}
@Override
public Stream<ConfigurationPropertyName> stream() {
@ -84,158 +108,52 @@ class SpringIterableConfigurationPropertySource extends SpringConfigurationPrope
return getConfigurationPropertyNames().iterator();
}
private Collection<ConfigurationPropertyName> getConfigurationPropertyNames() {
if (!isImmutablePropertySource()) {
return getMappings().getConfigurationPropertyNames(getPropertySource().getPropertyNames());
}
Collection<ConfigurationPropertyName> configurationPropertyNames = this.configurationPropertyNames;
if (configurationPropertyNames == null) {
configurationPropertyNames = getMappings()
.getConfigurationPropertyNames(getPropertySource().getPropertyNames());
this.configurationPropertyNames = configurationPropertyNames;
}
return configurationPropertyNames;
}
@Override
public ConfigurationPropertyState containsDescendantOf(ConfigurationPropertyName name) {
return ConfigurationPropertyState.search(this, (candidate) -> getMapper().isAncestorOf(name, candidate));
}
private List<ConfigurationPropertyName> getConfigurationPropertyNames() {
Cache cache = getCache();
List<ConfigurationPropertyName> names = (cache != null) ? cache.getNames() : null;
if (names != null) {
return names;
}
PropertyMapping[] mappings = getPropertyMappings(cache);
names = new ArrayList<>(mappings.length);
for (PropertyMapping mapping : mappings) {
names.add(mapping.getConfigurationPropertyName());
}
names = Collections.unmodifiableList(names);
if (cache != null) {
cache.setNames(names);
}
return names;
}
private PropertyMapping[] getPropertyMappings(Cache cache) {
PropertyMapping[] result = (cache != null) ? cache.getMappings() : null;
if (result != null) {
return result;
}
String[] names = getPropertySource().getPropertyNames();
List<PropertyMapping> mappings = new ArrayList<>(names.length * 2);
for (String name : names) {
Collections.addAll(mappings, getMapper().map(name));
}
result = mappings.toArray(new PropertyMapping[0]);
if (cache != null) {
cache.setMappings(result);
ConfigurationPropertyState result = super.containsDescendantOf(name);
if (result == ConfigurationPropertyState.UNKNOWN) {
result = ConfigurationPropertyState.search(this, (candidate) -> isAncestorOf(name, candidate));
}
return result;
}
private Cache getCache() {
CacheKey key = CacheKey.get(getPropertySource());
if (key == null) {
return null;
}
Cache cache = this.cache;
try {
if (cache != null && cache.hasKeyEqualTo(key)) {
return cache;
}
cache = new Cache(key.copy());
this.cache = cache;
return cache;
}
catch (ConcurrentModificationException ex) {
// Not fatal at this point, we can continue without a cache
return null;
}
}
@Override
protected EnumerablePropertySource<?> getPropertySource() {
return (EnumerablePropertySource<?>) super.getPropertySource();
}
private static class Cache {
private final CacheKey key;
private List<ConfigurationPropertyName> names;
private PropertyMapping[] mappings;
Cache(CacheKey key) {
this.key = key;
}
boolean hasKeyEqualTo(CacheKey key) {
return this.key.equals(key);
}
List<ConfigurationPropertyName> getNames() {
return this.names;
}
void setNames(List<ConfigurationPropertyName> names) {
this.names = names;
}
PropertyMapping[] getMappings() {
return this.mappings;
}
void setMappings(PropertyMapping[] mappings) {
this.mappings = mappings;
}
}
private static final class CacheKey {
private static final CacheKey IMMUTABLE_PROPERTY_SOURCE = new CacheKey(new Object[0]);
private final Object key;
private CacheKey(Object key) {
this.key = key;
}
CacheKey copy() {
if (this == IMMUTABLE_PROPERTY_SOURCE) {
return IMMUTABLE_PROPERTY_SOURCE;
}
return new CacheKey(copyKey(this.key));
}
private Object copyKey(Object key) {
if (key instanceof Set) {
return new HashSet<Object>((Set<?>) key);
}
return ((String[]) key).clone();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
private boolean isAncestorOf(ConfigurationPropertyName name, ConfigurationPropertyName candidate) {
for (PropertyMapper mapper : getMappers()) {
if (mapper.isAncestorOf(name, candidate)) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
}
return false;
}
CacheKey otherCacheKey = (CacheKey) obj;
return ObjectUtils.nullSafeEquals(this.key, otherCacheKey.key);
private Mappings getMappings() {
return this.cache.get(this::createMappings, this::updateMappings);
}
@Override
public int hashCode() {
return this.key.hashCode();
private Mappings createMappings() {
return new Mappings(getMappers(), isImmutablePropertySource());
}
static CacheKey get(EnumerablePropertySource<?> source) {
if (isImmutable(source)) {
return IMMUTABLE_PROPERTY_SOURCE;
}
if (source instanceof MapPropertySource) {
MapPropertySource mapPropertySource = (MapPropertySource) source;
return new CacheKey(mapPropertySource.getSource().keySet());
}
return new CacheKey(source.getPropertyNames());
private Mappings updateMappings(Mappings mappings) {
mappings.updateMappings(getPropertySource()::getPropertyNames);
return mappings;
}
private static boolean isImmutable(EnumerablePropertySource<?> source) {
private boolean isImmutablePropertySource() {
EnumerablePropertySource<?> source = getPropertySource();
if (source instanceof OriginLookup) {
return ((OriginLookup<?>) source).isImmutable();
}
@ -245,6 +163,99 @@ class SpringIterableConfigurationPropertySource extends SpringConfigurationPrope
return false;
}
@Override
protected EnumerablePropertySource<?> getPropertySource() {
return (EnumerablePropertySource<?>) super.getPropertySource();
}
private static class Mappings {
private final PropertyMapper[] mappers;
private final boolean immutable;
private volatile MultiValueMap<ConfigurationPropertyName, String> mappings;
private volatile Map<String, ConfigurationPropertyName> reverseMappings;
private volatile Collection<ConfigurationPropertyName> configurationPropertyNames;
private volatile String[] lastUpdated;
Mappings(PropertyMapper[] mappers, boolean immutable) {
this.mappers = mappers;
this.immutable = immutable;
}
void updateMappings(Supplier<String[]> propertyNames) {
if (this.mappings == null || !this.immutable) {
int count = 0;
while (true) {
try {
updateMappings(propertyNames.get());
return;
}
catch (ConcurrentModificationException ex) {
if (count++ > 10) {
throw ex;
}
}
}
}
}
private void updateMappings(String[] propertyNames) {
String[] lastUpdated = this.lastUpdated;
if (lastUpdated != null && Arrays.equals(lastUpdated, propertyNames)) {
return;
}
MultiValueMap<ConfigurationPropertyName, String> previousMappings = this.mappings;
MultiValueMap<ConfigurationPropertyName, String> mappings = (previousMappings != null)
? new LinkedMultiValueMap<>(previousMappings) : new LinkedMultiValueMap<>(propertyNames.length);
Map<String, ConfigurationPropertyName> previousReverseMappings = this.reverseMappings;
Map<String, ConfigurationPropertyName> reverseMappings = (previousReverseMappings != null)
? new HashMap<>(previousReverseMappings) : new HashMap<>(propertyNames.length);
for (PropertyMapper propertyMapper : this.mappers) {
for (String propertyName : propertyNames) {
if (!reverseMappings.containsKey(propertyName)) {
ConfigurationPropertyName configurationPropertyName = propertyMapper.map(propertyName);
if (configurationPropertyName != null && !configurationPropertyName.isEmpty()) {
mappings.add(configurationPropertyName, propertyName);
reverseMappings.put(propertyName, configurationPropertyName);
}
}
}
}
this.mappings = mappings;
this.reverseMappings = reverseMappings;
this.lastUpdated = this.immutable ? null : propertyNames;
this.configurationPropertyNames = this.immutable
? Collections.unmodifiableCollection(reverseMappings.values()) : null;
}
List<String> getMapped(ConfigurationPropertyName configurationPropertyName) {
return this.mappings.getOrDefault(configurationPropertyName, Collections.emptyList());
}
Collection<ConfigurationPropertyName> getConfigurationPropertyNames(String[] propertyNames) {
Collection<ConfigurationPropertyName> names = this.configurationPropertyNames;
if (names != null) {
return names;
}
Map<String, ConfigurationPropertyName> reverseMappings = this.reverseMappings;
if (reverseMappings == null || reverseMappings.isEmpty()) {
return Collections.emptySet();
}
List<ConfigurationPropertyName> relevantNames = new ArrayList<>(reverseMappings.size());
for (String propertyName : propertyNames) {
ConfigurationPropertyName configurationPropertyName = reverseMappings.get(propertyName);
if (configurationPropertyName != null) {
relevantNames.add(configurationPropertyName);
}
}
return relevantNames;
}
}
}

View File

@ -16,6 +16,9 @@
package org.springframework.boot.context.properties.source;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName.Form;
@ -37,59 +40,13 @@ final class SystemEnvironmentPropertyMapper implements PropertyMapper {
public static final PropertyMapper INSTANCE = new SystemEnvironmentPropertyMapper();
@Override
public PropertyMapping[] map(ConfigurationPropertyName configurationPropertyName) {
public List<String> map(ConfigurationPropertyName configurationPropertyName) {
String name = convertName(configurationPropertyName);
String legacyName = convertLegacyName(configurationPropertyName);
if (name.equals(legacyName)) {
return new PropertyMapping[] { new PropertyMapping(name, configurationPropertyName) };
}
return new PropertyMapping[] { new PropertyMapping(name, configurationPropertyName),
new PropertyMapping(legacyName, configurationPropertyName) };
}
@Override
public PropertyMapping[] map(String propertySourceName) {
ConfigurationPropertyName name = convertName(propertySourceName);
if (name == null || name.isEmpty()) {
return NO_MAPPINGS;
}
return new PropertyMapping[] { new PropertyMapping(propertySourceName, name) };
}
@Override
public boolean isAncestorOf(ConfigurationPropertyName name, ConfigurationPropertyName candidate) {
return name.isAncestorOf(candidate) || isLegacyAncestorOf(name, candidate);
}
private boolean isLegacyAncestorOf(ConfigurationPropertyName name, ConfigurationPropertyName candidate) {
if (!hasDashedEntries(name)) {
return false;
}
StringBuilder legacyCompatibleName = new StringBuilder();
for (int i = 0; i < name.getNumberOfElements(); i++) {
legacyCompatibleName.append((i != 0) ? "." : "");
legacyCompatibleName.append(name.getElement(i, Form.DASHED).replace('-', '.'));
}
return ConfigurationPropertyName.isValid(legacyCompatibleName)
&& ConfigurationPropertyName.of(legacyCompatibleName).isAncestorOf(candidate);
}
boolean hasDashedEntries(ConfigurationPropertyName name) {
for (int i = 0; i < name.getNumberOfElements(); i++) {
if (name.getElement(i, Form.DASHED).indexOf('-') != -1) {
return true;
}
}
return false;
}
private ConfigurationPropertyName convertName(String propertySourceName) {
try {
return ConfigurationPropertyName.adapt(propertySourceName, '_', this::processElementValue);
}
catch (Exception ex) {
return null;
return Collections.singletonList(name);
}
return Arrays.asList(name, legacyName);
}
private String convertName(ConfigurationPropertyName name) {
@ -122,6 +79,20 @@ final class SystemEnvironmentPropertyMapper implements PropertyMapper {
return element.replace('-', '_').toUpperCase(Locale.ENGLISH);
}
@Override
public ConfigurationPropertyName map(String propertySourceName) {
return convertName(propertySourceName);
}
private ConfigurationPropertyName convertName(String propertySourceName) {
try {
return ConfigurationPropertyName.adapt(propertySourceName, '_', this::processElementValue);
}
catch (Exception ex) {
return ConfigurationPropertyName.EMPTY;
}
}
private CharSequence processElementValue(CharSequence value) {
String result = value.toString().toLowerCase(Locale.ENGLISH);
return isNumber(result) ? "[" + result + "]" : result;
@ -131,4 +102,31 @@ final class SystemEnvironmentPropertyMapper implements PropertyMapper {
return string.chars().allMatch(Character::isDigit);
}
@Override
public boolean isAncestorOf(ConfigurationPropertyName name, ConfigurationPropertyName candidate) {
return name.isAncestorOf(candidate) || isLegacyAncestorOf(name, candidate);
}
private boolean isLegacyAncestorOf(ConfigurationPropertyName name, ConfigurationPropertyName candidate) {
if (!hasDashedEntries(name)) {
return false;
}
StringBuilder legacyCompatibleName = new StringBuilder();
for (int i = 0; i < name.getNumberOfElements(); i++) {
legacyCompatibleName.append((i != 0) ? "." : "");
legacyCompatibleName.append(name.getElement(i, Form.DASHED).replace('-', '.'));
}
return ConfigurationPropertyName.isValid(legacyCompatibleName)
&& ConfigurationPropertyName.of(legacyCompatibleName).isAncestorOf(candidate);
}
boolean hasDashedEntries(ConfigurationPropertyName name) {
for (int i = 0; i < name.getNumberOfElements(); i++) {
if (name.getElement(i, Form.DASHED).indexOf('-') != -1) {
return true;
}
}
return false;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -16,8 +16,7 @@
package org.springframework.boot.context.properties.source;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
/**
* Abstract base class for {@link PropertyMapper} tests.
@ -30,14 +29,12 @@ public abstract class AbstractPropertyMapperTests {
protected abstract PropertyMapper getMapper();
protected final Iterator<String> namesFromString(String name) {
return Arrays.stream(getMapper().map(name)).map((mapping) -> mapping.getConfigurationPropertyName().toString())
.iterator();
protected final List<String> mapConfigurationPropertyName(String configurationPropertyName) {
return getMapper().map(ConfigurationPropertyName.of(configurationPropertyName));
}
protected final Iterator<String> namesFromConfiguration(String name) {
return Arrays.stream(getMapper().map(ConfigurationPropertyName.of(name)))
.map(PropertyMapping::getPropertySourceName).iterator();
protected final String mapPropertySourceName(String propertySourceName) {
return getMapper().map(propertySourceName).toString();
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2012-2020 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
*
* https://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.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.withSettings;
/**
* Tests for {@link CachingConfigurationPropertySource}.
*
* @author Phillip Webb
*/
class CachingConfigurationPropertySourceTests {
@Test
void findWhenNullSourceReturnsNull() {
ConfigurationPropertySource source = null;
assertThat(CachingConfigurationPropertySource.find(source)).isNull();
}
@Test
void findWhenNotCachingConfigurationPropertySourceReturnsNull() {
ConfigurationPropertySource source = mock(ConfigurationPropertySource.class);
assertThat(CachingConfigurationPropertySource.find(source)).isNull();
}
@Test
void findWhenCachingConfigurationPropertySourceReturnsCaching() {
ConfigurationPropertySource source = mock(ConfigurationPropertySource.class,
withSettings().extraInterfaces(CachingConfigurationPropertySource.class));
ConfigurationPropertyCaching caching = mock(ConfigurationPropertyCaching.class);
given(((CachingConfigurationPropertySource) source).getCaching()).willReturn(caching);
assertThat(CachingConfigurationPropertySource.find(source)).isEqualTo(caching);
}
}

View File

@ -0,0 +1,96 @@
/*
* Copyright 2012-2020 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.source;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.StandardEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link ConfigurationPropertyCaching}.
*
* @author Phillip Webb
*/
class ConfigurationPropertyCachingTests {
private StandardEnvironment environment;
private MapPropertySource propertySource;
@BeforeEach
void setup() {
this.environment = new StandardEnvironment();
this.propertySource = new MapPropertySource("test", Collections.singletonMap("spring", "boot"));
this.environment.getPropertySources().addLast(this.propertySource);
}
@Test
void getFromEnvironmentReturnsCaching() {
ConfigurationPropertyCaching caching = ConfigurationPropertyCaching.get(this.environment);
assertThat(caching).isInstanceOf(ConfigurationPropertySourcesCaching.class);
}
@Test
void getFromEnvironmentForUnderlyingSourceReturnsCaching() {
ConfigurationPropertyCaching caching = ConfigurationPropertyCaching.get(this.environment, this.propertySource);
assertThat(caching).isInstanceOf(SoftReferenceConfigurationPropertyCache.class);
}
@Test
void getFromSourcesWhenSourcesIsNullThrowsException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> ConfigurationPropertyCaching.get((Iterable<ConfigurationPropertySource>) null))
.withMessage("Sources must not be null");
}
@Test
void getFromSourcesReturnsCachingComposite() {
List<ConfigurationPropertySource> sources = new ArrayList<>();
sources.add(SpringConfigurationPropertySource.from(this.propertySource));
ConfigurationPropertyCaching caching = ConfigurationPropertyCaching.get(sources);
assertThat(caching).isInstanceOf(ConfigurationPropertySourcesCaching.class);
}
@Test
void getFromSourcesForUnderlyingSourceReturnsCaching() {
List<ConfigurationPropertySource> sources = new ArrayList<>();
sources.add(SpringConfigurationPropertySource.from(this.propertySource));
ConfigurationPropertyCaching caching = ConfigurationPropertyCaching.get(sources, this.propertySource);
assertThat(caching).isInstanceOf(SoftReferenceConfigurationPropertyCache.class);
}
@Test
void getFromSourcesForUnderlyingSourceWhenCantFindThrowsException() {
List<ConfigurationPropertySource> sources = new ArrayList<>();
sources.add(SpringConfigurationPropertySource.from(this.propertySource));
MapPropertySource anotherPropertySource = new MapPropertySource("test2", Collections.emptyMap());
assertThatIllegalStateException()
.isThrownBy(() -> ConfigurationPropertyCaching.get(sources, anotherPropertySource))
.withMessage("Unable to find cache from configuration property sources");
}
}

View File

@ -0,0 +1,115 @@
/*
* Copyright 2012-2020 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
*
* https://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.time.Duration;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.withSettings;
/**
* Tests for {@link ConfigurationPropertySourcesCaching}.
*
* @author Phillip Webb
*/
class ConfigurationPropertySourcesCachingTests {
private List<ConfigurationPropertySource> sources;
private ConfigurationPropertySourcesCaching caching;
@BeforeEach
void setup() {
this.sources = new ArrayList<>();
for (int i = 0; i < 4; i++) {
this.sources.add(mockSource(i % 2 == 0));
}
this.caching = new ConfigurationPropertySourcesCaching(this.sources);
}
private ConfigurationPropertySource mockSource(boolean cachingSource) {
if (!cachingSource) {
return mock(ConfigurationPropertySource.class);
}
ConfigurationPropertySource source = mock(ConfigurationPropertySource.class,
withSettings().extraInterfaces(CachingConfigurationPropertySource.class));
ConfigurationPropertyCaching caching = mock(ConfigurationPropertyCaching.class);
given(((CachingConfigurationPropertySource) source).getCaching()).willReturn(caching);
return source;
}
@Test
void enableDelegatesToCachingConfigurationPropertySources() {
this.caching.enable();
verify(getCaching(0)).enable();
verify(getCaching(2)).enable();
}
@Test
void enableWhenSourcesIsNullDoesNothing() {
new ConfigurationPropertySourcesCaching(null).enable();
}
@Test
void disableDelegatesToCachingConfigurationPropertySources() {
this.caching.disable();
verify(getCaching(0)).disable();
verify(getCaching(2)).disable();
}
@Test
void disableWhenSourcesIsNullDoesNothing() {
new ConfigurationPropertySourcesCaching(null).disable();
}
@Test
void setTimeToLiveDelegatesToCachingConfigurationPropertySources() {
Duration ttl = Duration.ofDays(1);
this.caching.setTimeToLive(ttl);
verify(getCaching(0)).setTimeToLive(ttl);
verify(getCaching(2)).setTimeToLive(ttl);
}
@Test
void setTimeToLiveWhenSourcesIsNullDoesNothing() {
new ConfigurationPropertySourcesCaching(null).setTimeToLive(Duration.ofSeconds(1));
}
@Test
void clearDelegatesToCachingConfigurationPropertySources() {
this.caching.clear();
verify(getCaching(0)).clear();
verify(getCaching(2)).clear();
}
@Test
void clearWhenSourcesIsNullDoesNothing() {
new ConfigurationPropertySourcesCaching(null).enable();
}
private ConfigurationPropertyCaching getCaching(int index) {
return CachingConfigurationPropertySource.find(this.sources.get(index));
}
}

View File

@ -130,6 +130,13 @@ class ConfigurationPropertySourcesTests {
testPropertySourcePerformance(true, 1000);
}
@Test // gh-20625
void environmentPropertyAccessWhenMutableWithCacheShouldBePerformant() {
StandardEnvironment environment = createPerformanceTestEnvironment(false);
ConfigurationPropertyCaching.get(environment).enable();
testPropertySourcePerformance(environment, 1000);
}
@Test // gh-20625
@Disabled("for manual testing")
void environmentPropertyAccessWhenMutableShouldBeTolerable() {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -35,29 +35,29 @@ class DefaultPropertyMapperTests extends AbstractPropertyMapperTests {
@Test
void mapFromStringShouldReturnBestGuess() {
assertThat(namesFromString("server")).toIterable().containsExactly("server");
assertThat(namesFromString("server.port")).toIterable().containsExactly("server.port");
assertThat(namesFromString("host[0]")).toIterable().containsExactly("host[0]");
assertThat(namesFromString("host[0][1]")).toIterable().containsExactly("host[0][1]");
assertThat(namesFromString("host[0].name")).toIterable().containsExactly("host[0].name");
assertThat(namesFromString("host.f00.name")).toIterable().containsExactly("host.f00.name");
assertThat(namesFromString("my.host-name")).toIterable().containsExactly("my.host-name");
assertThat(namesFromString("my.hostName")).toIterable().containsExactly("my.hostname");
assertThat(namesFromString("my.HOST_NAME")).toIterable().containsExactly("my.hostname");
assertThat(namesFromString("s[!@#$%^&*()=+]e-rVeR")).toIterable().containsExactly("s[!@#$%^&*()=+].e-rver");
assertThat(namesFromString("host[FOO].name")).toIterable().containsExactly("host[FOO].name");
assertThat(mapPropertySourceName("server")).isEqualTo("server");
assertThat(mapPropertySourceName("server.port")).isEqualTo("server.port");
assertThat(mapPropertySourceName("host[0]")).isEqualTo("host[0]");
assertThat(mapPropertySourceName("host[0][1]")).isEqualTo("host[0][1]");
assertThat(mapPropertySourceName("host[0].name")).isEqualTo("host[0].name");
assertThat(mapPropertySourceName("host.f00.name")).isEqualTo("host.f00.name");
assertThat(mapPropertySourceName("my.host-name")).isEqualTo("my.host-name");
assertThat(mapPropertySourceName("my.hostName")).isEqualTo("my.hostname");
assertThat(mapPropertySourceName("my.HOST_NAME")).isEqualTo("my.hostname");
assertThat(mapPropertySourceName("s[!@#$%^&*()=+]e-rVeR")).isEqualTo("s[!@#$%^&*()=+].e-rver");
assertThat(mapPropertySourceName("host[FOO].name")).isEqualTo("host[FOO].name");
}
@Test
void mapFromConfigurationShouldReturnBestGuess() {
assertThat(namesFromConfiguration("server")).toIterable().containsExactly("server");
assertThat(namesFromConfiguration("server.port")).toIterable().containsExactly("server.port");
assertThat(namesFromConfiguration("host[0]")).toIterable().containsExactly("host[0]");
assertThat(namesFromConfiguration("host[0][1]")).toIterable().containsExactly("host[0][1]");
assertThat(namesFromConfiguration("host[0].name")).toIterable().containsExactly("host[0].name");
assertThat(namesFromConfiguration("host.f00.name")).toIterable().containsExactly("host.f00.name");
assertThat(namesFromConfiguration("my.host-name")).toIterable().containsExactly("my.host-name");
assertThat(namesFromConfiguration("host[FOO].name")).toIterable().containsExactly("host[FOO].name");
assertThat(mapConfigurationPropertyName("server")).containsExactly("server");
assertThat(mapConfigurationPropertyName("server.port")).containsExactly("server.port");
assertThat(mapConfigurationPropertyName("host[0]")).containsExactly("host[0]");
assertThat(mapConfigurationPropertyName("host[0][1]")).containsExactly("host[0][1]");
assertThat(mapConfigurationPropertyName("host[0].name")).containsExactly("host[0].name");
assertThat(mapConfigurationPropertyName("host.f00.name")).containsExactly("host.f00.name");
assertThat(mapConfigurationPropertyName("my.host-name")).containsExactly("my.host-name");
assertThat(mapConfigurationPropertyName("host[FOO].name")).containsExactly("host[FOO].name");
}
}

View File

@ -0,0 +1,169 @@
/*
* Copyright 2012-2020 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
*
* https://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.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link SoftReferenceConfigurationPropertyCache}.
*
* @author Phillip Webb
*/
class SoftReferenceConfigurationPropertyCacheTests {
private static final Clock FIXED_CLOCK = Clock.fixed(Instant.parse("2020-01-02T09:00:00Z"), ZoneOffset.UTC);
private Clock clock = FIXED_CLOCK;
private AtomicInteger createCount = new AtomicInteger();
private TestSoftReferenceConfigurationPropertyCache cache = new TestSoftReferenceConfigurationPropertyCache(false);
@Test
void getReturnsValueWithCorrectCounts() {
get(this.cache).assertCounts(0, 0);
get(this.cache).assertCounts(0, 1);
get(this.cache).assertCounts(0, 2);
}
@Test
void getWhenNeverExpireReturnsValueWithCorrectCounts() {
this.cache = new TestSoftReferenceConfigurationPropertyCache(true);
get(this.cache).assertCounts(0, 0);
get(this.cache).assertCounts(0, 0);
get(this.cache).assertCounts(0, 0);
}
@Test
void enableEnablesCachingWithUnlimtedTimeToLive() {
this.cache.enable();
get(this.cache).assertCounts(0, 0);
tick(Duration.ofDays(300));
get(this.cache).assertCounts(0, 0);
}
@Test
void setTimeToLiveEnablesCachingWithTimeToLive() {
this.cache.setTimeToLive(Duration.ofDays(1));
get(this.cache).assertCounts(0, 0);
tick(Duration.ofHours(2));
get(this.cache).assertCounts(0, 0);
tick(Duration.ofDays(2));
get(this.cache).assertCounts(0, 1);
}
@Test
void setTimeToLiveWhenZeroDisablesCaching() {
this.cache.setTimeToLive(Duration.ZERO);
get(this.cache).assertCounts(0, 0);
get(this.cache).assertCounts(0, 1);
get(this.cache).assertCounts(0, 2);
}
@Test
void setTimeToLiveWhenNullDisablesCaching() {
this.cache.setTimeToLive(null);
get(this.cache).assertCounts(0, 0);
get(this.cache).assertCounts(0, 1);
get(this.cache).assertCounts(0, 2);
}
@Test
void clearExpiresCache() {
this.cache.enable();
get(this.cache).assertCounts(0, 0);
get(this.cache).assertCounts(0, 0);
this.cache.clear();
get(this.cache).assertCounts(0, 1);
}
private Value get(SoftReferenceConfigurationPropertyCache<Value> cache) {
return cache.get(this::createValue, this::updateValue);
}
private Value createValue() {
return new Value(this.createCount.getAndIncrement(), -1);
}
private Value updateValue(Value value) {
return new Value(value.createCount, value.refreshCount + 1);
}
private void tick(Duration duration) {
this.clock = Clock.offset(this.clock, duration);
}
/**
* Testable {@link SoftReferenceConfigurationPropertyCache} that actually uses real
* references.
*/
class TestSoftReferenceConfigurationPropertyCache extends SoftReferenceConfigurationPropertyCache<Value> {
private Value value;
TestSoftReferenceConfigurationPropertyCache(boolean neverExpire) {
super(neverExpire);
}
@Override
protected Value getValue() {
return this.value;
}
@Override
protected void setValue(Value value) {
this.value = value;
}
@Override
protected Instant now() {
return SoftReferenceConfigurationPropertyCacheTests.this.clock.instant();
}
}
/**
* Value used for testing.
*/
static class Value {
private final int createCount;
private int refreshCount;
Value(int createCount, int refreshCount) {
this.createCount = createCount;
this.refreshCount = refreshCount;
}
void assertCounts(int expectedCreateCount, int expectedRefreshCount) {
assertThat(this.createCount).as("created").isEqualTo(expectedCreateCount);
assertThat(this.refreshCount).as("refreshed").isEqualTo(expectedRefreshCount);
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -41,17 +41,10 @@ class SpringConfigurationPropertySourceTests {
@Test
void createWhenPropertySourceIsNullShouldThrowException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new SpringConfigurationPropertySource(null, mock(PropertyMapper.class), null))
.isThrownBy(() -> new SpringConfigurationPropertySource(null, mock(PropertyMapper.class)))
.withMessageContaining("PropertySource must not be null");
}
@Test
void createWhenMapperIsNullShouldThrowException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new SpringConfigurationPropertySource(mock(PropertySource.class), null, null))
.withMessageContaining("Mapper must not be null");
}
@Test
void getValueShouldUseDirectMapping() {
Map<String, Object> source = new LinkedHashMap<>();
@ -62,7 +55,7 @@ class SpringConfigurationPropertySourceTests {
TestPropertyMapper mapper = new TestPropertyMapper();
ConfigurationPropertyName name = ConfigurationPropertyName.of("my.key");
mapper.addFromConfigurationProperty(name, "key2");
SpringConfigurationPropertySource adapter = new SpringConfigurationPropertySource(propertySource, mapper, null);
SpringConfigurationPropertySource adapter = new SpringConfigurationPropertySource(propertySource, mapper);
assertThat(adapter.getConfigurationProperty(name).getValue()).isEqualTo("value2");
}
@ -74,7 +67,7 @@ class SpringConfigurationPropertySourceTests {
TestPropertyMapper mapper = new TestPropertyMapper();
ConfigurationPropertyName name = ConfigurationPropertyName.of("my.key");
mapper.addFromConfigurationProperty(name, "key");
SpringConfigurationPropertySource adapter = new SpringConfigurationPropertySource(propertySource, mapper, null);
SpringConfigurationPropertySource adapter = new SpringConfigurationPropertySource(propertySource, mapper);
assertThat(adapter.getConfigurationProperty(name).getOrigin().toString())
.isEqualTo("\"key\" from property source \"test\"");
}
@ -87,7 +80,7 @@ class SpringConfigurationPropertySourceTests {
TestPropertyMapper mapper = new TestPropertyMapper();
ConfigurationPropertyName name = ConfigurationPropertyName.of("my.key");
mapper.addFromConfigurationProperty(name, "key");
SpringConfigurationPropertySource adapter = new SpringConfigurationPropertySource(propertySource, mapper, null);
SpringConfigurationPropertySource adapter = new SpringConfigurationPropertySource(propertySource, mapper);
assertThat(adapter.getConfigurationProperty(name).getOrigin().toString()).isEqualTo("TestOrigin key");
}
@ -97,7 +90,7 @@ class SpringConfigurationPropertySourceTests {
source.put("foo.bar", "value");
PropertySource<?> propertySource = new MapPropertySource("test", source);
SpringConfigurationPropertySource adapter = new SpringConfigurationPropertySource(propertySource,
DefaultPropertyMapper.INSTANCE, null);
DefaultPropertyMapper.INSTANCE);
assertThat(adapter.containsDescendantOf(ConfigurationPropertyName.of("foo")))
.isEqualTo(ConfigurationPropertyState.UNKNOWN);
}

View File

@ -54,14 +54,6 @@ class SpringIterableConfigurationPropertySourceTests {
.withMessageContaining("PropertySource must not be null");
}
@Test
void createWhenMapperIsNullShouldThrowException() {
assertThatIllegalArgumentException()
.isThrownBy(
() -> new SpringIterableConfigurationPropertySource(mock(EnumerablePropertySource.class), null))
.withMessageContaining("Mapper must not be null");
}
@Test
void iteratorShouldAdaptNames() {
Map<String, Object> source = new LinkedHashMap<>();
@ -70,14 +62,16 @@ class SpringIterableConfigurationPropertySourceTests {
source.put("key3", "value3");
source.put("key4", "value4");
EnumerablePropertySource<?> propertySource = new MapPropertySource("test", source);
TestPropertyMapper mapper = new TestPropertyMapper();
mapper.addFromPropertySource("key1", "my.key1");
mapper.addFromPropertySource("key2", "my.key2a", "my.key2b");
mapper.addFromPropertySource("key4", "my.key4");
TestPropertyMapper mapper1 = new TestPropertyMapper();
mapper1.addFromPropertySource("key1", "my.key1");
mapper1.addFromPropertySource("key2", "my.key2a");
mapper1.addFromPropertySource("key4", "my.key4");
TestPropertyMapper mapper2 = new TestPropertyMapper();
mapper2.addFromPropertySource("key2", "my.key2b");
SpringIterableConfigurationPropertySource adapter = new SpringIterableConfigurationPropertySource(
propertySource, mapper);
propertySource, mapper1, mapper2);
assertThat(adapter.iterator()).toIterable().extracting(Object::toString).containsExactly("my.key1", "my.key2a",
"my.key2b", "my.key4");
"my.key4");
}
@Test

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -35,44 +35,36 @@ class SystemEnvironmentPropertyMapperTests extends AbstractPropertyMapperTests {
@Test
void mapFromStringShouldReturnBestGuess() {
assertThat(namesFromString("SERVER")).toIterable().containsExactly("server");
assertThat(namesFromString("SERVER_PORT")).toIterable().containsExactly("server.port");
assertThat(namesFromString("HOST_0")).toIterable().containsExactly("host[0]");
assertThat(namesFromString("HOST_0_1")).toIterable().containsExactly("host[0][1]");
assertThat(namesFromString("HOST_0_NAME")).toIterable().containsExactly("host[0].name");
assertThat(namesFromString("HOST_F00_NAME")).toIterable().containsExactly("host.f00.name");
assertThat(namesFromString("S-ERVER")).toIterable().containsExactly("s-erver");
assertThat(mapPropertySourceName("SERVER")).isEqualTo("server");
assertThat(mapPropertySourceName("SERVER_PORT")).isEqualTo("server.port");
assertThat(mapPropertySourceName("HOST_0")).isEqualTo("host[0]");
assertThat(mapPropertySourceName("HOST_0_1")).isEqualTo("host[0][1]");
assertThat(mapPropertySourceName("HOST_0_NAME")).isEqualTo("host[0].name");
assertThat(mapPropertySourceName("HOST_F00_NAME")).isEqualTo("host.f00.name");
assertThat(mapPropertySourceName("S-ERVER")).isEqualTo("s-erver");
}
@Test
void mapFromConfigurationShouldReturnBestGuess() {
assertThat(namesFromConfiguration("server")).toIterable().containsExactly("SERVER");
assertThat(namesFromConfiguration("server.port")).toIterable().containsExactly("SERVER_PORT");
assertThat(namesFromConfiguration("host[0]")).toIterable().containsExactly("HOST_0");
assertThat(namesFromConfiguration("host[0][1]")).toIterable().containsExactly("HOST_0_1");
assertThat(namesFromConfiguration("host[0].name")).toIterable().containsExactly("HOST_0_NAME");
assertThat(namesFromConfiguration("host.f00.name")).toIterable().containsExactly("HOST_F00_NAME");
assertThat(namesFromConfiguration("foo.the-bar")).toIterable().containsExactly("FOO_THEBAR", "FOO_THE_BAR");
assertThat(mapConfigurationPropertyName("server")).containsExactly("SERVER");
assertThat(mapConfigurationPropertyName("server.port")).containsExactly("SERVER_PORT");
assertThat(mapConfigurationPropertyName("host[0]")).containsExactly("HOST_0");
assertThat(mapConfigurationPropertyName("host[0][1]")).containsExactly("HOST_0_1");
assertThat(mapConfigurationPropertyName("host[0].name")).containsExactly("HOST_0_NAME");
assertThat(mapConfigurationPropertyName("host.f00.name")).containsExactly("HOST_F00_NAME");
assertThat(mapConfigurationPropertyName("foo.the-bar")).containsExactly("FOO_THEBAR", "FOO_THE_BAR");
}
@Test
void underscoreShouldNotMapToEmptyString() {
PropertyMapping[] mappings = getMapper().map("_");
boolean applicable = false;
for (PropertyMapping mapping : mappings) {
applicable = mapping.isApplicable(ConfigurationPropertyName.of(""));
}
assertThat(applicable).isFalse();
void underscoreShouldMapToEmptyString() {
ConfigurationPropertyName mapped = getMapper().map("_");
assertThat(mapped.isEmpty()).isTrue();
}
@Test
void underscoreWithWhitespaceShouldNotMapToEmptyString() {
PropertyMapping[] mappings = getMapper().map(" _");
boolean applicable = false;
for (PropertyMapping mapping : mappings) {
applicable = mapping.isApplicable(ConfigurationPropertyName.of(""));
}
assertThat(applicable).isFalse();
void underscoreWithWhitespaceShouldMapToEmptyString() {
ConfigurationPropertyName mapped = getMapper().map(" _");
assertThat(mapped.isEmpty()).isTrue();
}
@Test

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -17,6 +17,9 @@
package org.springframework.boot.context.properties.source;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
@ -26,33 +29,28 @@ import org.springframework.util.MultiValueMap;
*/
class TestPropertyMapper implements PropertyMapper {
private MultiValueMap<String, PropertyMapping> fromSource = new LinkedMultiValueMap<>();
private MultiValueMap<ConfigurationPropertyName, String> fromConfig = new LinkedMultiValueMap<>();
private MultiValueMap<ConfigurationPropertyName, PropertyMapping> fromConfig = new LinkedMultiValueMap<>();
private Map<String, ConfigurationPropertyName> fromSource = new LinkedHashMap<>();
void addFromPropertySource(String from, String... to) {
for (String configurationPropertyName : to) {
this.fromSource.add(from,
new PropertyMapping(from, ConfigurationPropertyName.of(configurationPropertyName)));
}
void addFromPropertySource(String from, String to) {
this.fromSource.put(from, ConfigurationPropertyName.of(to));
}
void addFromConfigurationProperty(ConfigurationPropertyName from, String... to) {
for (String propertySourceName : to) {
this.fromConfig.add(from, new PropertyMapping(propertySourceName, from));
this.fromConfig.add(from, propertySourceName);
}
}
@Override
public PropertyMapping[] map(String propertySourceName) {
return this.fromSource.getOrDefault(propertySourceName, Collections.emptyList())
.toArray(new PropertyMapping[0]);
public List<String> map(ConfigurationPropertyName configurationPropertyName) {
return this.fromConfig.getOrDefault(configurationPropertyName, Collections.emptyList());
}
@Override
public PropertyMapping[] map(ConfigurationPropertyName configurationPropertyName) {
return this.fromConfig.getOrDefault(configurationPropertyName, Collections.emptyList())
.toArray(new PropertyMapping[0]);
public ConfigurationPropertyName map(String propertySourceName) {
return this.fromSource.getOrDefault(propertySourceName, ConfigurationPropertyName.EMPTY);
}
@Override