Commit 133f11df authored by Phillip Webb's avatar Phillip Webb

Refactor ConfigFileApplicationListener

Refactor `ConfigFileApplicationListener` to use a `MultiValueMap` when
loading sources. The helps to simplify the code and removes the need
for the inner `LoadedPropertySources` class. We're also able to delete
the now unused `EnumerableCompositePropertySource` and
`PropertySourcesLoader` classes.

Fixes gh-9144
parent 5cad11d6
/*
* 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.env;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.PropertySource;
/**
* An mutable, enumerable, composite property source. New sources are added last (and
* hence resolved with lowest priority).
*
* @author Dave Syer
* @see PropertySource
* @see EnumerablePropertySource
*/
public class EnumerableCompositePropertySource
extends EnumerablePropertySource<Collection<PropertySource<?>>> {
private volatile String[] names;
public EnumerableCompositePropertySource(String sourceName) {
super(sourceName, new LinkedHashSet<PropertySource<?>>());
}
@Override
public Object getProperty(String name) {
for (PropertySource<?> propertySource : getSource()) {
Object value = propertySource.getProperty(name);
if (value != null) {
return value;
}
}
return null;
}
@Override
public String[] getPropertyNames() {
String[] result = this.names;
if (result == null) {
List<String> names = new ArrayList<>();
for (PropertySource<?> source : new ArrayList<>(getSource())) {
if (source instanceof EnumerablePropertySource) {
names.addAll(Arrays.asList(
((EnumerablePropertySource<?>) source).getPropertyNames()));
}
}
this.names = names.toArray(new String[0]);
result = this.names;
}
return result;
}
public void add(PropertySource<?> source) {
getSource().add(source);
this.names = null;
}
}
/*
* 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.env;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Utility that can be used to update {@link MutablePropertySources} using
* {@link PropertySourceLoader PropertySourceLoaders}.
*
* @author Phillip Webb
*/
public class PropertySourcesLoader {
private static final Log logger = LogFactory.getLog(PropertySourcesLoader.class);
private final MutablePropertySources propertySources;
private final List<PropertySourceLoader> loaders;
/**
* Create a new {@link PropertySourceLoader} instance backed by a new
* {@link MutablePropertySources}.
*/
public PropertySourcesLoader() {
this(new MutablePropertySources());
}
/**
* Create a new {@link PropertySourceLoader} instance backed by the specified
* {@link MutablePropertySources}.
* @param propertySources the destination property sources
*/
public PropertySourcesLoader(MutablePropertySources propertySources) {
Assert.notNull(propertySources, "PropertySources must not be null");
this.propertySources = propertySources;
this.loaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
getClass().getClassLoader());
}
/**
* Load the specified resource (if possible) and add it as the first source.
* @param resource the source resource (may be {@code null}).
* @return the loaded property source or {@code null}
* @throws IOException if the source cannot be loaded
*/
public PropertySource<?> load(Resource resource) throws IOException {
return load(resource, null);
}
/**
* Load the profile-specific properties from the specified resource (if any) and add
* it as the first source.
* @param resource the source resource (may be {@code null}).
* @param profile a specific profile to load or {@code null} to load the default.
* @return the loaded property source or {@code null}
* @throws IOException if the source cannot be loaded
*/
public PropertySource<?> load(Resource resource, String profile) throws IOException {
return load(resource, resource.getDescription(), profile);
}
/**
* Load the profile-specific properties from the specified resource (if any), give the
* name provided and add it as the first source.
* @param resource the source resource (may be {@code null}).
* @param name the root property name (may be {@code null}).
* @param profile a specific profile to load or {@code null} to load the default.
* @return the loaded property source or {@code null}
* @throws IOException if the source cannot be loaded
*/
public PropertySource<?> load(Resource resource, String name, String profile)
throws IOException {
return load(resource, null, name, profile);
}
/**
* Load the profile-specific properties from the specified resource (if any), give the
* name provided and add it to a group of property sources identified by the group
* name. Property sources are added to the end of a group, but new groups are added as
* the first in the chain being assembled. This means the normal sequence of calls is
* to first create the group for the default (null) profile, and then add specific
* groups afterwards (with the highest priority last). Property resolution from the
* resulting sources will consider all keys for a given group first and then move to
* the next group.
* @param resource the source resource (may be {@code null}).
* @param group an identifier for the group that this source belongs to
* @param name the root property name (may be {@code null}).
* @param profile a specific profile to load or {@code null} to load the default.
* @return the loaded property source or {@code null}
* @throws IOException if the source cannot be loaded
*/
public PropertySource<?> load(Resource resource, String group, String name,
String profile) throws IOException {
if (isFile(resource)) {
String sourceName = generatePropertySourceName(name, profile);
for (PropertySourceLoader loader : this.loaders) {
if (canLoadFileExtension(loader, resource)) {
PropertySource<?> specific = loader.load(sourceName, resource,
profile);
addPropertySource(group, specific, profile);
return specific;
}
}
}
return null;
}
private boolean isFile(Resource resource) {
return resource != null && resource.exists() && StringUtils
.hasText(StringUtils.getFilenameExtension(resource.getFilename()));
}
private String generatePropertySourceName(String name, String profile) {
return (profile == null ? name : name + "#" + profile);
}
private boolean canLoadFileExtension(PropertySourceLoader loader, Resource resource) {
String filename = resource.getFilename().toLowerCase();
for (String extension : loader.getFileExtensions()) {
if (filename.endsWith("." + extension.toLowerCase())) {
return true;
}
}
return false;
}
private void addPropertySource(String basename, PropertySource<?> source,
String profile) {
if (source == null) {
return;
}
if (basename == null) {
this.propertySources.addLast(source);
return;
}
EnumerableCompositePropertySource group = getGeneric(basename);
group.add(source);
logger.trace("Adding PropertySource: " + source + " in group: " + basename);
if (this.propertySources.contains(group.getName())) {
this.propertySources.replace(group.getName(), group);
}
else {
this.propertySources.addFirst(group);
}
}
private EnumerableCompositePropertySource getGeneric(String name) {
PropertySource<?> source = this.propertySources.get(name);
if (source instanceof EnumerableCompositePropertySource) {
return (EnumerableCompositePropertySource) source;
}
EnumerableCompositePropertySource composite = new EnumerableCompositePropertySource(
name);
return composite;
}
/**
* Return the {@link MutablePropertySources} being loaded.
* @return the property sources
*/
public MutablePropertySources getPropertySources() {
return this.propertySources;
}
/**
* Returns all file extensions that could be loaded.
* @return the file extensions
*/
public Set<String> getAllFileExtensions() {
Set<String> fileExtensions = new LinkedHashSet<>();
for (PropertySourceLoader loader : this.loaders) {
fileExtensions.addAll(Arrays.asList(loader.getFileExtensions()));
}
return fileExtensions;
}
}
......@@ -21,12 +21,13 @@ import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
......@@ -40,10 +41,8 @@ import org.junit.rules.ExpectedException;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.context.config.ConfigFileApplicationListener.LoadedPropertySources;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.boot.env.EnumerableCompositePropertySource;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.testutil.InternalOutputCapture;
import org.springframework.context.ConfigurableApplicationContext;
......@@ -55,7 +54,6 @@ import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.SimpleCommandLinePropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.ByteArrayResource;
......@@ -512,24 +510,10 @@ public class ConfigFileApplicationListenerTests {
String property = this.environment.getProperty("my.property");
assertThat(this.environment.getActiveProfiles()).contains("dev");
assertThat(property).isEqualTo("fromdevprofile");
LoadedPropertySources propertySource = (LoadedPropertySources) this.environment
.getPropertySources()
.get(ConfigFileApplicationListener.APPLICATION_CONFIGURATION_PROPERTY_SOURCE_NAME);
Collection<org.springframework.core.env.PropertySource<?>> sources = propertySource
.getSource();
assertThat(sources).hasSize(2);
List<String> names = new ArrayList<>();
for (org.springframework.core.env.PropertySource<?> source : sources) {
if (source instanceof EnumerableCompositePropertySource) {
for (org.springframework.core.env.PropertySource<?> nested : ((EnumerableCompositePropertySource) source)
.getSource()) {
names.add(nested.getName());
}
}
else {
names.add(source.getName());
}
}
List<String> names = StreamSupport
.stream(this.environment.getPropertySources().spliterator(), false)
.map(org.springframework.core.env.PropertySource::getName)
.collect(Collectors.toList());
assertThat(names).contains(
"applicationConfig: [classpath:/testsetprofiles.yml]#dev",
"applicationConfig: [classpath:/testsetprofiles.yml]");
......@@ -846,10 +830,7 @@ public class ConfigFileApplicationListenerTests {
@Override
public boolean matches(ConfigurableEnvironment value) {
MutablePropertySources sources = new MutablePropertySources(
value.getPropertySources());
LoadedPropertySources.finishAndRelocate(sources);
return sources.contains(sourceName);
return value.getPropertySources().contains(sourceName);
}
};
......
/*
* Copyright 2012-2016 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.env;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link PropertySourcesLoader}.
*
* @author Dave Syer
*/
public class PropertySourcesLoaderTests {
private PropertySourcesLoader loader = new PropertySourcesLoader();
@Test
public void fileExtensions() {
assertThat(this.loader.getAllFileExtensions()).containsOnly("yml", "yaml",
"properties", "xml");
}
}
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