Commit fa4de135 authored by Phillip Webb's avatar Phillip Webb

Rework ConfigurationPropertySources

Rework the ConfigurationPropertySources and related adapter classes to
help with performance. The ConfigurationPropertySources class now only
monitors for updates when `.attach` is used. The `.get` methods now
return the adapted version, but no longer checks to see if sources have
been added or removed on each call.

This commit also fixes a few caching issues and makes both the
`PropertyMapper` implementations true static singletons.

See gh-9000
parent 133f11df
......@@ -25,7 +25,6 @@ import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.boot.info.InfoProperties;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.util.StringUtils;
......@@ -93,9 +92,7 @@ public abstract class InfoPropertiesInfoContributor<T extends InfoProperties>
* @return the raw content
*/
protected Map<String, Object> extractContent(PropertySource<?> propertySource) {
MutablePropertySources sources = new MutablePropertySources();
sources.addFirst(propertySource);
return new Binder(ConfigurationPropertySources.get(sources))
return new Binder(ConfigurationPropertySources.from(propertySource))
.bind("", STRING_OBJECT_MAP).orElseGet(LinkedHashMap::new);
}
......
......@@ -21,6 +21,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import javax.sql.DataSource;
......@@ -320,9 +321,8 @@ public class EndpointAutoConfigurationTests {
if (!location.exists()) {
return;
}
MapConfigurationPropertySource source = new MapConfigurationPropertySource(
PropertiesLoaderUtils.loadProperties(location));
new Binder(source).bind("info",
Properties properties = PropertiesLoaderUtils.loadProperties(location);
new Binder(new MapConfigurationPropertySource(properties)).bind("info",
Bindable.of(STRING_OBJECT_MAP).withExistingValue(this.content));
}
......
......@@ -28,6 +28,7 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.boot.test.mock.web.SpringBootMockServletContext;
import org.springframework.boot.test.util.EnvironmentTestUtils;
......@@ -173,7 +174,7 @@ public class SpringBootContextLoader extends AbstractContextLoader {
return binder.bind("server.port", Bindable.of(String.class)).isBound();
}
private MapConfigurationPropertySource convertToConfigurationPropertySource(
private ConfigurationPropertySource convertToConfigurationPropertySource(
List<String> properties) {
String[] array = properties.toArray(new String[properties.size()]);
return new MapConfigurationPropertySource(
......
......@@ -480,9 +480,7 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
}
private void handleProfileProperties(PropertySource<?> propertySource) {
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(propertySource);
Binder binder = new Binder(ConfigurationPropertySources.get(propertySources),
Binder binder = new Binder(ConfigurationPropertySources.from(propertySource),
new PropertySourcesPlaceholdersResolver(this.environment));
Set<Profile> active = getProfiles(binder, "spring.profiles.active");
Set<Profile> include = getProfiles(binder, "spring.profiles.include");
......
......@@ -40,6 +40,7 @@ import org.springframework.boot.context.properties.bind.PropertySourcesPlacehold
import org.springframework.boot.context.properties.bind.handler.IgnoreErrorsBindHandler;
import org.springframework.boot.context.properties.bind.handler.NoUnboundElementsBindHandler;
import org.springframework.boot.context.properties.bind.validation.ValidationBindHandler;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.boot.validation.MessageInterpolatorFactory;
import org.springframework.context.ApplicationContext;
......@@ -59,6 +60,7 @@ import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.util.Assert;
......@@ -95,7 +97,7 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
private ConfigurationBeanFactoryMetaData beans = new ConfigurationBeanFactoryMetaData();
private PropertySources propertySources;
private Iterable<PropertySource<?>> propertySources;
private Validator validator;
......@@ -117,7 +119,7 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
private int order = Ordered.HIGHEST_PRECEDENCE + 1;
private ConfigurationPropertySources configurationSources;
private Iterable<ConfigurationPropertySource> configurationSources;
private Binder binder;
......@@ -164,7 +166,7 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
* Set the property sources to bind.
* @param propertySources the property sources
*/
public void setPropertySources(PropertySources propertySources) {
public void setPropertySources(Iterable<PropertySource<?>> propertySources) {
this.propertySources = propertySources;
}
......@@ -221,7 +223,7 @@ public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProc
ConversionService.class);
}
this.configurationSources = ConfigurationPropertySources
.get(this.propertySources);
.from(this.propertySources);
}
@Override
......
......@@ -37,7 +37,6 @@ import org.springframework.boot.context.properties.source.ConfigurationPropertyN
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.util.Assert;
......@@ -331,13 +330,12 @@ public class Binder {
/**
* Create a new {@link Binder} instance from the specified environment.
* @param environment the environment (must be a {@link ConfigurableEnvironment})
* @param environment the environment source (must have attached
* {@link ConfigurationPropertySources})
* @return a {@link Binder} instance
*/
public static Binder get(Environment environment) {
Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
return new Binder(
ConfigurationPropertySources.get((ConfigurableEnvironment) environment),
return new Binder(ConfigurationPropertySources.get(environment),
new PropertySourcesPlaceholdersResolver(environment));
}
......
......@@ -33,7 +33,7 @@ import org.springframework.util.SystemPropertyUtils;
*/
public class PropertySourcesPlaceholdersResolver implements PlaceholdersResolver {
private final PropertySources sources;
private final Iterable<PropertySource<?>> sources;
private final PropertyPlaceholderHelper helper;
......@@ -41,11 +41,11 @@ public class PropertySourcesPlaceholdersResolver implements PlaceholdersResolver
this(getSources(environment), null);
}
public PropertySourcesPlaceholdersResolver(PropertySources sources) {
public PropertySourcesPlaceholdersResolver(Iterable<PropertySource<?>> sources) {
this(sources, null);
}
public PropertySourcesPlaceholdersResolver(PropertySources sources,
public PropertySourcesPlaceholdersResolver(Iterable<PropertySource<?>> sources,
PropertyPlaceholderHelper helper) {
this.sources = sources;
this.helper = (helper != null ? helper
......
......@@ -16,10 +16,6 @@
package org.springframework.boot.context.properties.source;
import java.util.Objects;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.springframework.boot.origin.Origin;
import org.springframework.boot.origin.OriginLookup;
import org.springframework.core.env.Environment;
......@@ -70,10 +66,14 @@ class ConfigurationPropertySourcesPropertySource
if (name == null) {
return null;
}
Stream<ConfigurationPropertySource> sources = StreamSupport
.stream(getSource().spliterator(), false);
return sources.map(source -> source.getConfigurationProperty(name))
.filter(Objects::nonNull).findFirst().orElse(null);
for (ConfigurationPropertySource configurationPropertySource : getSource()) {
ConfigurationProperty configurationProperty = configurationPropertySource
.getConfigurationProperty(name);
if (configurationProperty != null) {
return configurationProperty;
}
}
return null;
}
}
......@@ -17,11 +17,10 @@
package org.springframework.boot.context.properties.source;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.core.env.PropertySource;
import org.springframework.util.ObjectUtils;
/**
* Default {@link PropertyMapper} implementation. Names are mapped by removing invalid
......@@ -31,36 +30,48 @@ import org.springframework.core.env.PropertySource;
* @author Phillip Webb
* @author Madhura Bhave
* @see PropertyMapper
* @see PropertySourceConfigurationPropertySource
* @see SpringConfigurationPropertySource
*/
class DefaultPropertyMapper implements PropertyMapper {
final class DefaultPropertyMapper implements PropertyMapper {
public static final PropertyMapper INSTANCE = new DefaultPropertyMapper();
private final Cache<ConfigurationPropertyName> configurationPropertySourceCache = new Cache<>();
private LastMapping<ConfigurationPropertyName> lastMappedConfigurationPropertyName;
private final Cache<String> propertySourceCache = new Cache<>();
private LastMapping<String> lastMappedPropertyName;
private final ConfigurationPropertyNameBuilder nameBuilder = new ConfigurationPropertyNameBuilder();
private DefaultPropertyMapper() {
}
@Override
public List<PropertyMapping> map(PropertySource<?> propertySource,
ConfigurationPropertyName configurationPropertyName) {
List<PropertyMapping> mapping = this.configurationPropertySourceCache
.get(configurationPropertyName);
if (mapping == null) {
String convertedName = configurationPropertyName.toString();
mapping = Collections.singletonList(
new PropertyMapping(convertedName, configurationPropertyName));
this.configurationPropertySourceCache.put(configurationPropertyName, mapping);
// Use a local copy in case another thread changes things
LastMapping<ConfigurationPropertyName> last = this.lastMappedConfigurationPropertyName;
if (last != null && last.isFrom(configurationPropertyName)) {
return last.getMapping();
}
String convertedName = configurationPropertyName.toString();
List<PropertyMapping> mapping = Collections.singletonList(
new PropertyMapping(convertedName, configurationPropertyName));
this.lastMappedConfigurationPropertyName = new LastMapping<>(
configurationPropertyName, mapping);
return mapping;
}
@Override
public List<PropertyMapping> map(PropertySource<?> propertySource,
String propertySourceName) {
return this.propertySourceCache.computeIfAbsent(propertySourceName, this::tryMap);
// Use a local copy in case another thread changes things
LastMapping<String> last = this.lastMappedPropertyName;
if (last != null && last.isFrom(propertySourceName)) {
return last.getMapping();
}
List<PropertyMapping> mapping = tryMap(propertySourceName);
this.lastMappedPropertyName = new LastMapping<>(propertySourceName, mapping);
return mapping;
}
private List<PropertyMapping> tryMap(String propertySourceName) {
......@@ -75,23 +86,23 @@ class DefaultPropertyMapper implements PropertyMapper {
}
}
private static class Cache<K> extends LinkedHashMap<K, List<PropertyMapping>> {
private static class LastMapping<T> {
private final int capacity;
private final T from;
Cache() {
this(1);
}
private final List<PropertyMapping> mapping;
Cache(int capacity) {
super(capacity, (float) 0.75, true);
this.capacity = capacity;
LastMapping(T from, List<PropertyMapping> mapping) {
this.from = from;
this.mapping = mapping;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, List<PropertyMapping>> eldest) {
return size() > this.capacity;
public boolean isFrom(T from) {
return ObjectUtils.nullSafeEquals(from, this.from);
}
public List<PropertyMapping> getMapping() {
return this.mapping;
}
}
......
......@@ -31,7 +31,6 @@ import org.springframework.util.Assert;
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.0.0
*/
public class MapConfigurationPropertySource
implements IterableConfigurationPropertySource {
......@@ -54,9 +53,9 @@ public class MapConfigurationPropertySource
*/
public MapConfigurationPropertySource(Map<?, ?> map) {
this.source = new LinkedHashMap<>();
this.delegate = new PropertySourceIterableConfigurationPropertySource(
this.delegate = new SpringIterableConfigurationPropertySource(
new MapPropertySource("source", this.source),
new DefaultPropertyMapper());
DefaultPropertyMapper.INSTANCE);
putAll(map);
}
......
......@@ -27,16 +27,16 @@ import org.springframework.core.env.PropertySource;
* <P>
* Mappings should be provided for both {@link ConfigurationPropertyName
* ConfigurationPropertyName} types and {@code String} based names. This allows the
* {@link PropertySourceConfigurationPropertySource} to first attempt any direct mappings
* {@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
* {@link PropertySourceConfigurationPropertySource} for more details.
* {@link SpringConfigurationPropertySource} for more details.
*
* @author Phillip Webb
* @author Madhura Bhave
* @see PropertySourceConfigurationPropertySource
* @see SpringConfigurationPropertySource
*/
interface PropertyMapper {
......
......@@ -26,7 +26,7 @@ import org.springframework.core.env.PropertySource;
*
* @author Phillip Webb
* @author Madhura Bhave
* @see PropertySourceConfigurationPropertySource
* @see SpringConfigurationPropertySource
*/
class PropertyMapping {
......
......@@ -18,14 +18,17 @@ package org.springframework.boot.context.properties.source;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import org.springframework.boot.env.RandomValuePropertySource;
import org.springframework.boot.origin.Origin;
import org.springframework.boot.origin.PropertySourceOrigin;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.SystemEnvironmentPropertySource;
import org.springframework.util.Assert;
/**
......@@ -41,16 +44,19 @@ import org.springframework.util.Assert;
* {@link ConfigurationPropertyName} to one or more {@code String} based names. This
* allows fast property resolution for well formed property sources.
* <p>
* If at all possible the {@link PropertySourceIterableConfigurationPropertySource} should
* be used in preference to this implementation since it supports "relaxed" style
* resolution.
* When possible the {@link SpringIterableConfigurationPropertySource} will be used in
* preference to this implementation since it supports full "relaxed" style resolution.
*
* @author Phillip Webb
* @author Madhura Bhave
* @see #from(PropertySource)
* @see PropertyMapper
* @see PropertySourceIterableConfigurationPropertySource
* @see SpringIterableConfigurationPropertySource
*/
class PropertySourceConfigurationPropertySource implements ConfigurationPropertySource {
class SpringConfigurationPropertySource implements ConfigurationPropertySource {
private static final ConfigurationPropertyName RANDOM = ConfigurationPropertyName
.of("random");
private final PropertySource<?> propertySource;
......@@ -59,21 +65,21 @@ class PropertySourceConfigurationPropertySource implements ConfigurationProperty
private final Function<ConfigurationPropertyName, Optional<Boolean>> containsDescendantOfMethod;
/**
* Create a new {@link PropertySourceConfigurationPropertySource} implementation.
* Create a new {@link SpringConfigurationPropertySource} implementation.
* @param propertySource the source property source
* @param mapper the property mapper
* @param containsDescendantOfMethod function used to implement
* {@link #containsDescendantOf(ConfigurationPropertyName)} (may be {@code null})
*/
PropertySourceConfigurationPropertySource(PropertySource<?> propertySource,
SpringConfigurationPropertySource(PropertySource<?> propertySource,
PropertyMapper mapper,
Function<ConfigurationPropertyName, Optional<Boolean>> containsDescendantOfMethod) {
Assert.notNull(propertySource, "PropertySource must not be null");
Assert.notNull(mapper, "Mapper must not be null");
this.propertySource = propertySource;
this.mapper = new ExceptionSwallowingPropertyMapper(mapper);
this.containsDescendantOfMethod = (containsDescendantOfMethod != null ? containsDescendantOfMethod
: (n) -> Optional.empty());
this.containsDescendantOfMethod = (containsDescendantOfMethod != null
? containsDescendantOfMethod : (n) -> Optional.empty());
}
@Override
......@@ -120,6 +126,62 @@ class PropertySourceConfigurationPropertySource implements ConfigurationProperty
return this.propertySource.toString();
}
/**
* Create a new {@link SpringConfigurationPropertySource} for the specified
* {@link PropertySource}.
* @param source the source Spring {@link PropertySource}
* @return a {@link SpringConfigurationPropertySource} or
* {@link SpringIterableConfigurationPropertySource} instance
*/
public static SpringConfigurationPropertySource from(PropertySource<?> source) {
Assert.notNull(source, "Source must not be null");
PropertyMapper mapper = getPropertyMapper(source);
if (isFullEnumerable(source)) {
return new SpringIterableConfigurationPropertySource(
(EnumerablePropertySource<?>) source, mapper);
}
return new SpringConfigurationPropertySource(source, mapper,
getContainsDescendantOfMethod(source));
}
private static PropertyMapper getPropertyMapper(PropertySource<?> source) {
if (source instanceof SystemEnvironmentPropertySource) {
return SystemEnvironmentPropertyMapper.INSTANCE;
}
return DefaultPropertyMapper.INSTANCE;
}
private static 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 static PropertySource<?> getRootSource(PropertySource<?> source) {
while (source.getSource() != null
&& source.getSource() instanceof PropertySource) {
source = (PropertySource<?>) source.getSource();
}
return source;
}
private static Function<ConfigurationPropertyName, Optional<Boolean>> getContainsDescendantOfMethod(
PropertySource<?> source) {
if (source instanceof RandomValuePropertySource) {
return (name) -> Optional
.of(name.isAncestorOf(RANDOM) || name.equals(RANDOM));
}
return null;
}
/**
* {@link PropertyMapper} that swallows exceptions when the mapping fails.
*/
......
/*
* 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.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySource.StubPropertySource;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* Adapter to convert Spring's {@link MutablePropertySources} to
* {@link ConfigurationPropertySource ConfigurationPropertySources}.
*
* @author Phillip Webb
*/
class SpringConfigurationPropertySources
implements Iterable<ConfigurationPropertySource> {
private final MutablePropertySources sources;
private volatile PropertySourcesKey lastKey;
private volatile List<ConfigurationPropertySource> adaptedSources;
SpringConfigurationPropertySources(MutablePropertySources sources) {
Assert.notNull(sources, "Sources must not be null");
this.sources = sources;
}
@Override
public Iterator<ConfigurationPropertySource> iterator() {
checkForChanges();
return this.adaptedSources.iterator();
}
private void checkForChanges() {
PropertySourcesKey lastKey = this.lastKey;
PropertySourcesKey currentKey = new PropertySourcesKey(this.sources);
if (!currentKey.equals(lastKey)) {
onChange(this.sources);
this.lastKey = currentKey;
}
}
private void onChange(MutablePropertySources sources) {
this.adaptedSources = streamPropertySources(sources)
.map(SpringConfigurationPropertySource::from)
.collect(Collectors.toList());
}
private Stream<PropertySource<?>> streamPropertySources(
Iterable<PropertySource<?>> sources) {
return StreamSupport.stream(sources.spliterator(), false).flatMap(this::flatten)
.filter(this::isIncluded);
}
private Stream<PropertySource<?>> flatten(PropertySource<?> source) {
if (source.getSource() instanceof ConfigurableEnvironment) {
return streamPropertySources(
((ConfigurableEnvironment) source.getSource()).getPropertySources());
}
return Stream.of(source);
}
private boolean isIncluded(PropertySource<?> source) {
return !(source instanceof StubPropertySource)
&& !(source instanceof ConfigurationPropertySourcesPropertySource);
}
private static class PropertySourcesKey {
private final List<PropertySourceKey> keys = new ArrayList<>();
PropertySourcesKey(MutablePropertySources sources) {
sources.forEach(this::addKey);
}
private void addKey(PropertySource<?> source) {
this.keys.add(new PropertySourceKey(source));
}
@Override
public int hashCode() {
return this.keys.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
return this.keys.equals(((PropertySourcesKey) obj).keys);
}
}
private static class PropertySourceKey {
private final String name;
private final Class<?> type;
PropertySourceKey(PropertySource<?> source) {
this.name = source.getName();
this.type = source.getClass();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ObjectUtils.nullSafeHashCode(this.name);
result = prime * result + ObjectUtils.nullSafeHashCode(this.type);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
PropertySourceKey other = (PropertySourceKey) obj;
boolean result = true;
result = result && ObjectUtils.nullSafeEquals(this.name, other.name);
result = result && ObjectUtils.nullSafeEquals(this.type, other.type);
return result;
}
}
}
......@@ -31,21 +31,24 @@ 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.
* Extends {@link SpringConfigurationPropertySource} 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
class SpringIterableConfigurationPropertySource extends SpringConfigurationPropertySource
implements IterableConfigurationPropertySource {
PropertySourceIterableConfigurationPropertySource(
EnumerablePropertySource<?> propertySource, PropertyMapper mapper) {
private volatile Object cacheKey;
private volatile Cache cache;
SpringIterableConfigurationPropertySource(EnumerablePropertySource<?> propertySource,
PropertyMapper mapper) {
super(propertySource, mapper, null);
assertEnumerablePropertySource(propertySource);
}
......@@ -63,10 +66,6 @@ class PropertySourceIterableConfigurationPropertySource
}
}
private volatile Object cacheKey;
private volatile Cache cache;
@Override
public ConfigurationProperty getConfigurationProperty(
ConfigurationPropertyName name) {
......
......@@ -41,15 +41,18 @@ import org.springframework.util.StringUtils;
* @author Phillip Webb
* @author Madhura Bhave
* @see PropertyMapper
* @see PropertySourceConfigurationPropertySource
* @see SpringConfigurationPropertySource
*/
class SystemEnvironmentPropertyMapper implements PropertyMapper {
final class SystemEnvironmentPropertyMapper implements PropertyMapper {
public static final PropertyMapper INSTANCE = new SystemEnvironmentPropertyMapper();
private final ConfigurationPropertyNameBuilder nameBuilder = new ConfigurationPropertyNameBuilder(
this::createElement);
private SystemEnvironmentPropertyMapper() {
}
@Override
public List<PropertyMapping> map(PropertySource<?> propertySource,
String propertySourceName) {
......
......@@ -48,7 +48,6 @@ import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEven
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.context.event.ApplicationStartingEvent;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.boot.testutil.InternalOutputCapture;
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
......@@ -868,8 +867,7 @@ public class SpringApplicationTests {
assertThat(this.context.getEnvironment().getProperty("foo")).isEqualTo("bar");
Iterator<PropertySource<?>> iterator = this.context.getEnvironment()
.getPropertySources().iterator();
assertThat(iterator.next().getName())
.isEqualTo(ConfigurationPropertySources.PROPERTY_SOURCE_NAME);
assertThat(iterator.next().getName()).isEqualTo("configurationProperties");
assertThat(iterator.next().getName()).isEqualTo(
TestPropertySourceUtils.INLINED_PROPERTIES_PROPERTY_SOURCE_NAME);
}
......
......@@ -105,7 +105,7 @@ public class LoggingApplicationListenerTests {
new File("target/foo.log").delete();
new File(tmpDir() + "/spring.log").delete();
ConfigurableEnvironment environment = this.context.getEnvironment();
ConfigurationPropertySources.attach(environment.getPropertySources());
ConfigurationPropertySources.attach(environment);
}
@After
......
......@@ -28,11 +28,9 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
public class DefaultPropertyMapperTests extends AbstractPropertyMapperTests {
private DefaultPropertyMapper mapper = new DefaultPropertyMapper();
@Override
protected PropertyMapper getMapper() {
return this.mapper;
return DefaultPropertyMapper.INSTANCE;
}
@Test
......
......@@ -32,12 +32,12 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link PropertySourceConfigurationPropertySource}.
* Tests for {@link SpringConfigurationPropertySource}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
public class PropertySourceConfigurationPropertySourceTests {
public class SpringConfigurationPropertySourceTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
......@@ -46,16 +46,14 @@ public class PropertySourceConfigurationPropertySourceTests {
public void createWhenPropertySourceIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("PropertySource must not be null");
new PropertySourceConfigurationPropertySource(null, mock(PropertyMapper.class),
null);
new SpringConfigurationPropertySource(null, mock(PropertyMapper.class), null);
}
@Test
public void createWhenMapperIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Mapper must not be null");
new PropertySourceConfigurationPropertySource(mock(PropertySource.class), null,
null);
new SpringConfigurationPropertySource(mock(PropertySource.class), null, null);
}
@Test
......@@ -68,7 +66,7 @@ public class PropertySourceConfigurationPropertySourceTests {
TestPropertyMapper mapper = new TestPropertyMapper();
ConfigurationPropertyName name = ConfigurationPropertyName.of("my.key");
mapper.addFromConfigurationProperty(name, "key2");
PropertySourceConfigurationPropertySource adapter = new PropertySourceConfigurationPropertySource(
SpringConfigurationPropertySource adapter = new SpringConfigurationPropertySource(
propertySource, mapper, null);
assertThat(adapter.getConfigurationProperty(name).getValue()).isEqualTo("value2");
}
......@@ -82,7 +80,7 @@ public class PropertySourceConfigurationPropertySourceTests {
ConfigurationPropertyName name = ConfigurationPropertyName.of("my.key");
mapper.addFromConfigurationProperty(name, "key",
(value) -> value.toString().replace("ue", "let"));
PropertySourceConfigurationPropertySource adapter = new PropertySourceConfigurationPropertySource(
SpringConfigurationPropertySource adapter = new SpringConfigurationPropertySource(
propertySource, mapper, null);
assertThat(adapter.getConfigurationProperty(name).getValue()).isEqualTo("vallet");
}
......@@ -95,7 +93,7 @@ public class PropertySourceConfigurationPropertySourceTests {
TestPropertyMapper mapper = new TestPropertyMapper();
ConfigurationPropertyName name = ConfigurationPropertyName.of("my.key");
mapper.addFromConfigurationProperty(name, "key");
PropertySourceConfigurationPropertySource adapter = new PropertySourceConfigurationPropertySource(
SpringConfigurationPropertySource adapter = new SpringConfigurationPropertySource(
propertySource, mapper, null);
assertThat(adapter.getConfigurationProperty(name).getOrigin().toString())
.isEqualTo("\"key\" from property source \"test\"");
......@@ -110,7 +108,7 @@ public class PropertySourceConfigurationPropertySourceTests {
TestPropertyMapper mapper = new TestPropertyMapper();
ConfigurationPropertyName name = ConfigurationPropertyName.of("my.key");
mapper.addFromConfigurationProperty(name, "key");
PropertySourceConfigurationPropertySource adapter = new PropertySourceConfigurationPropertySource(
SpringConfigurationPropertySource adapter = new SpringConfigurationPropertySource(
propertySource, mapper, null);
assertThat(adapter.getConfigurationProperty(name).getOrigin().toString())
.isEqualTo("TestOrigin key");
......@@ -121,12 +119,61 @@ public class PropertySourceConfigurationPropertySourceTests {
Map<String, Object> source = new LinkedHashMap<>();
source.put("foo.bar", "value");
PropertySource<?> propertySource = new MapPropertySource("test", source);
PropertySourceConfigurationPropertySource adapter = new PropertySourceConfigurationPropertySource(
propertySource, new DefaultPropertyMapper(), null);
SpringConfigurationPropertySource adapter = new SpringConfigurationPropertySource(
propertySource, DefaultPropertyMapper.INSTANCE, null);
assertThat(adapter.containsDescendantOf(ConfigurationPropertyName.of("foo")))
.isEmpty();
}
@Test
public void fromWhenPropertySourceIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Source must not be null");
SpringConfigurationPropertySource.from(null);
}
@Test
public void fromWhenNonEnumerableShouldReturnNonIterable() throws Exception {
PropertySource<?> propertySource = new PropertySource<Object>("test",
new Object()) {
@Override
public Object getProperty(String name) {
return null;
}
};
assertThat(SpringConfigurationPropertySource.from(propertySource))
.isNotInstanceOf(IterableConfigurationPropertySource.class);
}
@Test
public void fromWhenEnumerableButRestrictedShouldReturnNonIterable()
throws Exception {
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);
assertThat(SpringConfigurationPropertySource.from(propertySource))
.isNotInstanceOf(IterableConfigurationPropertySource.class);
}
@Test
public void getWhenEnumerableShouldBeIterable() throws Exception {
Map<String, Object> source = new LinkedHashMap<>();
source.put("fooBar", "Spring ${barBaz} ${bar-baz}");
source.put("barBaz", "Boot");
PropertySource<?> propertySource = new MapPropertySource("test", source);
assertThat(SpringConfigurationPropertySource.from(propertySource))
.isInstanceOf(IterableConfigurationPropertySource.class);
}
/**
* Test {@link PropertySource} that's also a {@link OriginLookup}.
*/
......
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.properties.source;
import java.util.Collections;
import java.util.Iterator;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.env.SystemEnvironmentPropertySource;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link SpringConfigurationPropertySources}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
public class SpringConfigurationPropertySourcesTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void createWhenPropertySourcesIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Sources must not be null");
new SpringConfigurationPropertySources(null);
}
@Test
public void shouldAdaptPropertySource() throws Exception {
MutablePropertySources sources = new MutablePropertySources();
sources.addFirst(new MapPropertySource("test",
Collections.<String, Object>singletonMap("a", "b")));
Iterator<ConfigurationPropertySource> iterator = new SpringConfigurationPropertySources(
sources).iterator();
ConfigurationPropertyName name = ConfigurationPropertyName.of("a");
assertThat(iterator.next().getConfigurationProperty(name).getValue())
.isEqualTo("b");
assertThat(iterator.hasNext()).isFalse();
}
@Test
public void shouldAdaptSystemEnvironmentPropertySource() throws Exception {
MutablePropertySources sources = new MutablePropertySources();
sources.addLast(new SystemEnvironmentPropertySource("system",
Collections.<String, Object>singletonMap("SERVER_PORT", "1234")));
Iterator<ConfigurationPropertySource> iterator = new SpringConfigurationPropertySources(
sources).iterator();
ConfigurationPropertyName name = ConfigurationPropertyName.of("server.port");
assertThat(iterator.next().getConfigurationProperty(name).getValue())
.isEqualTo("1234");
assertThat(iterator.hasNext()).isFalse();
}
@Test
public void shouldAdaptMultiplePropertySources() throws Exception {
MutablePropertySources sources = new MutablePropertySources();
sources.addLast(new SystemEnvironmentPropertySource("system",
Collections.<String, Object>singletonMap("SERVER_PORT", "1234")));
sources.addLast(new MapPropertySource("test1",
Collections.<String, Object>singletonMap("server.po-rt", "4567")));
sources.addLast(new MapPropertySource("test2",
Collections.<String, Object>singletonMap("a", "b")));
Iterator<ConfigurationPropertySource> iterator = new SpringConfigurationPropertySources(
sources).iterator();
ConfigurationPropertyName name = ConfigurationPropertyName.of("server.port");
assertThat(iterator.next().getConfigurationProperty(name).getValue())
.isEqualTo("1234");
assertThat(iterator.next().getConfigurationProperty(name).getValue())
.isEqualTo("4567");
assertThat(iterator.next()
.getConfigurationProperty(ConfigurationPropertyName.of("a")).getValue())
.isEqualTo("b");
assertThat(iterator.hasNext()).isFalse();
}
@Test
public void shouldFlattenEnvironment() throws Exception {
StandardEnvironment environment = new StandardEnvironment();
environment.getPropertySources().addFirst(new MapPropertySource("foo",
Collections.<String, Object>singletonMap("foo", "bar")));
environment.getPropertySources().addFirst(new MapPropertySource("far",
Collections.<String, Object>singletonMap("far", "far")));
MutablePropertySources sources = new MutablePropertySources();
sources.addFirst(new PropertySource<Environment>("env", environment) {
@Override
public String getProperty(String key) {
return this.source.getProperty(key);
}
});
sources.addLast(new MapPropertySource("baz",
Collections.<String, Object>singletonMap("baz", "barf")));
SpringConfigurationPropertySources configurationSources = new SpringConfigurationPropertySources(
sources);
assertThat(configurationSources.iterator()).hasSize(5);
}
@Test
public void shouldTrackChanges() throws Exception {
MutablePropertySources sources = new MutablePropertySources();
sources.addLast(new MapPropertySource("test1",
Collections.<String, Object>singletonMap("a", "b")));
assertThat(new SpringConfigurationPropertySources(sources).iterator()).hasSize(1);
sources.addLast(new MapPropertySource("test2",
Collections.<String, Object>singletonMap("b", "c")));
assertThat(new SpringConfigurationPropertySources(sources).iterator()).hasSize(2);
}
}
......@@ -33,12 +33,12 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link PropertySourceIterableConfigurationPropertySource}.
* Tests for {@link SpringIterableConfigurationPropertySource}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
public class PropertySourceIterableConfigurationPropertySourceTests {
public class SpringIterableConfigurationPropertySourceTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
......@@ -47,15 +47,14 @@ public class PropertySourceIterableConfigurationPropertySourceTests {
public void createWhenPropertySourceIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("PropertySource must not be null");
new PropertySourceIterableConfigurationPropertySource(null,
mock(PropertyMapper.class));
new SpringIterableConfigurationPropertySource(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(
new SpringIterableConfigurationPropertySource(
mock(EnumerablePropertySource.class), null);
}
......@@ -72,7 +71,7 @@ public class PropertySourceIterableConfigurationPropertySourceTests {
mapper.addFromPropertySource("key1", "my.key1");
mapper.addFromPropertySource("key2", "my.key2a", "my.key2b");
mapper.addFromPropertySource("key4", "my.key4");
PropertySourceIterableConfigurationPropertySource adapter = new PropertySourceIterableConfigurationPropertySource(
SpringIterableConfigurationPropertySource adapter = new SpringIterableConfigurationPropertySource(
propertySource, mapper);
assertThat(adapter.iterator()).extracting(Object::toString)
.containsExactly("my.key1", "my.key2a", "my.key2b", "my.key4");
......@@ -89,7 +88,7 @@ public class PropertySourceIterableConfigurationPropertySourceTests {
TestPropertyMapper mapper = new TestPropertyMapper();
ConfigurationPropertyName name = ConfigurationPropertyName.of("my.key");
mapper.addFromConfigurationProperty(name, "key2");
PropertySourceIterableConfigurationPropertySource adapter = new PropertySourceIterableConfigurationPropertySource(
SpringIterableConfigurationPropertySource adapter = new SpringIterableConfigurationPropertySource(
propertySource, mapper);
assertThat(adapter.getConfigurationProperty(name).getValue()).isEqualTo("value2");
}
......@@ -105,7 +104,7 @@ public class PropertySourceIterableConfigurationPropertySourceTests {
TestPropertyMapper mapper = new TestPropertyMapper();
mapper.addFromPropertySource("key1", "my.missing");
mapper.addFromPropertySource("key2", "my.k-e-y");
PropertySourceIterableConfigurationPropertySource adapter = new PropertySourceIterableConfigurationPropertySource(
SpringIterableConfigurationPropertySource adapter = new SpringIterableConfigurationPropertySource(
propertySource, mapper);
ConfigurationPropertyName name = ConfigurationPropertyName.of("my.key");
assertThat(adapter.getConfigurationProperty(name).getValue()).isEqualTo("value2");
......@@ -121,7 +120,7 @@ public class PropertySourceIterableConfigurationPropertySourceTests {
ConfigurationPropertyName name = ConfigurationPropertyName.of("my.key");
mapper.addFromConfigurationProperty(name, "key",
(value) -> value.toString().replace("ue", "let"));
PropertySourceIterableConfigurationPropertySource adapter = new PropertySourceIterableConfigurationPropertySource(
SpringIterableConfigurationPropertySource adapter = new SpringIterableConfigurationPropertySource(
propertySource, mapper);
assertThat(adapter.getConfigurationProperty(name).getValue()).isEqualTo("vallet");
}
......@@ -135,7 +134,7 @@ public class PropertySourceIterableConfigurationPropertySourceTests {
TestPropertyMapper mapper = new TestPropertyMapper();
ConfigurationPropertyName name = ConfigurationPropertyName.of("my.key");
mapper.addFromConfigurationProperty(name, "key");
PropertySourceIterableConfigurationPropertySource adapter = new PropertySourceIterableConfigurationPropertySource(
SpringIterableConfigurationPropertySource adapter = new SpringIterableConfigurationPropertySource(
propertySource, mapper);
assertThat(adapter.getConfigurationProperty(name).getOrigin().toString())
.isEqualTo("\"key\" from property source \"test\"");
......@@ -150,7 +149,7 @@ public class PropertySourceIterableConfigurationPropertySourceTests {
TestPropertyMapper mapper = new TestPropertyMapper();
ConfigurationPropertyName name = ConfigurationPropertyName.of("my.key");
mapper.addFromConfigurationProperty(name, "key");
PropertySourceIterableConfigurationPropertySource adapter = new PropertySourceIterableConfigurationPropertySource(
SpringIterableConfigurationPropertySource adapter = new SpringIterableConfigurationPropertySource(
propertySource, mapper);
assertThat(adapter.getConfigurationProperty(name).getOrigin().toString())
.isEqualTo("TestOrigin key");
......@@ -163,8 +162,8 @@ public class PropertySourceIterableConfigurationPropertySourceTests {
source.put("faf", "value");
EnumerablePropertySource<?> propertySource = new OriginCapablePropertySource<>(
new MapPropertySource("test", source));
PropertySourceIterableConfigurationPropertySource adapter = new PropertySourceIterableConfigurationPropertySource(
propertySource, new DefaultPropertyMapper());
SpringIterableConfigurationPropertySource adapter = new SpringIterableConfigurationPropertySource(
propertySource, DefaultPropertyMapper.INSTANCE);
assertThat(adapter.containsDescendantOf(ConfigurationPropertyName.of("foo")))
.contains(true);
assertThat(adapter.containsDescendantOf(ConfigurationPropertyName.of("faf")))
......
......@@ -36,11 +36,9 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
public class SystemEnvironmentPropertyMapperTests extends AbstractPropertyMapperTests {
private SystemEnvironmentPropertyMapper mapper = new SystemEnvironmentPropertyMapper();
@Override
protected PropertyMapper getMapper() {
return this.mapper;
return SystemEnvironmentPropertyMapper.INSTANCE;
}
@Test
......@@ -75,7 +73,7 @@ public class SystemEnvironmentPropertyMapperTests extends AbstractPropertyMapper
Map<String, Object> source = new LinkedHashMap<>();
source.put("SERVER__", "foo,bar,baz");
PropertySource<?> propertySource = new MapPropertySource("test", source);
List<PropertyMapping> mappings = this.mapper.map(propertySource, "SERVER__");
List<PropertyMapping> mappings = getMapper().map(propertySource, "SERVER__");
List<Object> result = new ArrayList<>();
for (PropertyMapping mapping : mappings) {
Object value = propertySource.getProperty(mapping.getPropertySourceName());
......@@ -91,7 +89,7 @@ public class SystemEnvironmentPropertyMapperTests extends AbstractPropertyMapper
Map<String, Object> source = new LinkedHashMap<>();
source.put("SERVER__", "foo,bar,baz");
PropertySource<?> propertySource = new MapPropertySource("test", source);
List<PropertyMapping> mappings = this.mapper.map(propertySource,
List<PropertyMapping> mappings = getMapper().map(propertySource,
ConfigurationPropertyName.of("server[1]"));
List<Object> result = new ArrayList<>();
for (PropertyMapping mapping : mappings) {
......@@ -108,7 +106,7 @@ public class SystemEnvironmentPropertyMapperTests extends AbstractPropertyMapper
public void underscoreShouldNotMapToEmptyString() throws Exception {
Map<String, Object> source = new LinkedHashMap<>();
PropertySource<?> propertySource = new MapPropertySource("test", source);
List<PropertyMapping> mappings = this.mapper.map(propertySource, "_");
List<PropertyMapping> mappings = getMapper().map(propertySource, "_");
boolean applicable = false;
for (PropertyMapping mapping : mappings) {
applicable = mapping.isApplicable(ConfigurationPropertyName.of(""));
......@@ -120,7 +118,7 @@ public class SystemEnvironmentPropertyMapperTests extends AbstractPropertyMapper
public void underscoreWithWhitespaceShouldNotMapToEmptyString() throws Exception {
Map<String, Object> source = new LinkedHashMap<>();
PropertySource<?> propertySource = new MapPropertySource("test", source);
List<PropertyMapping> mappings = this.mapper.map(propertySource, " _");
List<PropertyMapping> mappings = getMapper().map(propertySource, " _");
boolean applicable = false;
for (PropertyMapping mapping : mappings) {
applicable = mapping.isApplicable(ConfigurationPropertyName.of(""));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment