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
...@@ -19,12 +19,13 @@ package org.springframework.boot.context.config; ...@@ -19,12 +19,13 @@ package org.springframework.boot.context.config;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Queue; import java.util.Queue;
import java.util.Set; import java.util.Set;
...@@ -39,9 +40,8 @@ import org.springframework.boot.context.event.ApplicationPreparedEvent; ...@@ -39,9 +40,8 @@ import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.bind.PropertySourcesPlaceholdersResolver; import org.springframework.boot.context.properties.bind.PropertySourcesPlaceholdersResolver;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources; import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.boot.env.EnumerableCompositePropertySource;
import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.env.PropertySourcesLoader; import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.boot.env.RandomValuePropertySource; import org.springframework.boot.env.RandomValuePropertySource;
import org.springframework.boot.logging.DeferredLog; import org.springframework.boot.logging.DeferredLog;
import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEvent;
...@@ -51,10 +51,9 @@ import org.springframework.context.annotation.ConfigurationClassPostProcessor; ...@@ -51,10 +51,9 @@ import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources;
import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
...@@ -258,7 +257,6 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor, ...@@ -258,7 +257,6 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
} }
private void reorderSources(ConfigurableEnvironment environment) { private void reorderSources(ConfigurableEnvironment environment) {
LoadedPropertySources.finishAndRelocate(environment.getPropertySources());
PropertySource<?> defaultProperties = environment.getPropertySources() PropertySource<?> defaultProperties = environment.getPropertySources()
.remove(DEFAULT_PROPERTIES); .remove(DEFAULT_PROPERTIES);
if (defaultProperties != null) { if (defaultProperties != null) {
...@@ -279,7 +277,7 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor, ...@@ -279,7 +277,7 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
private final ResourceLoader resourceLoader; private final ResourceLoader resourceLoader;
private PropertySourcesLoader propertiesLoader; private final List<PropertySourceLoader> propertySourceLoaders;
private Queue<Profile> profiles; private Queue<Profile> profiles;
...@@ -287,55 +285,61 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor, ...@@ -287,55 +285,61 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
private boolean activatedProfiles; private boolean activatedProfiles;
private Map<Profile, MutablePropertySources> loaded;
Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
this.environment = environment; this.environment = environment;
this.resourceLoader = resourceLoader == null ? new DefaultResourceLoader() this.resourceLoader = resourceLoader == null ? new DefaultResourceLoader()
: resourceLoader; : resourceLoader;
this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(
PropertySourceLoader.class, getClass().getClassLoader());
} }
public void load() { public void load() {
this.propertiesLoader = new PropertySourcesLoader();
this.activatedProfiles = false;
this.profiles = Collections.asLifoQueue(new LinkedList<Profile>()); this.profiles = Collections.asLifoQueue(new LinkedList<Profile>());
this.processedProfiles = new LinkedList<>(); this.processedProfiles = new LinkedList<>();
this.activatedProfiles = false;
// Pre-existing active profiles set via Environment.setActiveProfiles() this.loaded = new LinkedHashMap<>();
// are additional profiles and config files are allowed to add more if initializeProfiles();
// they want to, so don't call addActiveProfiles() here.
Set<Profile> initialActiveProfiles = initializeActiveProfiles();
this.profiles.addAll(getUnprocessedActiveProfiles(initialActiveProfiles));
if (this.profiles.isEmpty()) {
for (String defaultProfileName : this.environment.getDefaultProfiles()) {
Profile defaultProfile = new Profile(defaultProfileName, true);
if (!this.profiles.contains(defaultProfile)) {
this.profiles.add(defaultProfile);
}
}
}
// The default profile for these purposes is represented as null. We add it
// last so that it is first out of the queue (active profiles will then
// override any settings in the defaults when the list is reversed later).
this.profiles.add(null);
while (!this.profiles.isEmpty()) { while (!this.profiles.isEmpty()) {
Profile profile = this.profiles.poll(); Profile profile = this.profiles.poll();
for (String location : getSearchLocations()) { for (String location : getSearchLocations()) {
if (!location.endsWith("/")) { if (!location.endsWith("/")) {
// location is a filename already, so don't search for more // location is a filename already, so don't search for more
// filenames // filenames
load(location, null, profile); load(profile, location, null);
} }
else { else {
for (String name : getSearchNames()) { for (String name : getSearchNames()) {
load(location, name, profile); load(profile, location, name);
} }
} }
} }
this.processedProfiles.add(profile); this.processedProfiles.add(profile);
} }
addLoadedPropertySources();
}
addConfigurationProperties(this.propertiesLoader.getPropertySources()); /**
* Initialize profile information from both the {@link Environment} active
* profiles and any {@code spring.profiles.active}/{@code spring.profiles.include}
* properties that are already set.
*/
private void initializeProfiles() {
Set<Profile> initialActiveProfiles = initializeActiveProfiles();
this.profiles.addAll(getUnprocessedActiveProfiles(initialActiveProfiles));
if (this.profiles.isEmpty()) {
for (String defaultProfileName : this.environment.getDefaultProfiles()) {
Profile defaultProfile = new Profile(defaultProfileName, true);
if (!this.profiles.contains(defaultProfile)) {
this.profiles.add(defaultProfile);
}
}
}
// The default profile for these purposes is represented as null. We add it
// last so that it is first out of the queue (active profiles will then
// override any settings in the defaults when the list is reversed later).
this.profiles.add(null);
} }
private Set<Profile> initializeActiveProfiles() { private Set<Profile> initializeActiveProfiles() {
...@@ -345,11 +349,10 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor, ...@@ -345,11 +349,10 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
} }
// Any pre-existing active profiles set via property sources (e.g. System // Any pre-existing active profiles set via property sources (e.g. System
// properties) take precedence over those added in config files. // properties) take precedence over those added in config files.
Set<Profile> active = getProfiles(this.environment, "spring.profiles.active"); Binder binder = Binder.get(this.environment);
Set<Profile> activeProfiles = new LinkedHashSet<>(active); Set<Profile> activeProfiles = new LinkedHashSet<>();
Set<Profile> include = getProfiles(this.environment, activeProfiles.addAll(getProfiles(binder, ACTIVE_PROFILES_PROPERTY));
"spring.profiles.include"); activeProfiles.addAll(getProfiles(binder, INCLUDE_PROFILES_PROPERTY));
activeProfiles.addAll(include);
maybeActivateProfiles(activeProfiles); maybeActivateProfiles(activeProfiles);
return activeProfiles; return activeProfiles;
} }
...@@ -381,117 +384,113 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor, ...@@ -381,117 +384,113 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
return unprocessedActiveProfiles; return unprocessedActiveProfiles;
} }
private void load(String location, String name, Profile profile) { /**
String group = "profile=" + (profile == null ? "" : profile); * Load an actual property source file.
* @param profile the profile being loaded
* @param location the location of the resource
* @param name an optional name to be combined with the location
*/
private void load(Profile profile, String location, String name) {
if (!StringUtils.hasText(name)) { if (!StringUtils.hasText(name)) {
// Try to load directly from the location for (PropertySourceLoader loader : this.propertySourceLoaders) {
loadIntoGroup(group, location, profile); if (canLoadFileExtension(loader, location)) {
} load(loader, profile, location,
else { (profile == null ? null : profile.getName()));
// Search for a file with the given name
for (String ext : this.propertiesLoader.getAllFileExtensions()) {
if (profile != null) {
// Try the profile-specific file
loadIntoGroup(group, location + name + "-" + profile + "." + ext,
null);
for (Profile processedProfile : this.processedProfiles) {
if (processedProfile != null) {
loadIntoGroup(group, location + name + "-"
+ processedProfile + "." + ext, profile);
}
}
// Sometimes people put "spring.profiles: dev" in
// application-dev.yml (gh-340). Arguably we should try and error
// out on that, but we can be kind and load it anyway.
loadIntoGroup(group, location + name + "-" + profile + "." + ext,
profile);
} }
// Also try the profile-specific section (if any) of the normal file }
loadIntoGroup(group, location + name + "." + ext, profile); }
for (PropertySourceLoader loader : this.propertySourceLoaders) {
for (String ext : loader.getFileExtensions()) {
loadForFileExtention(loader, profile, location + name, "." + ext);
} }
} }
} }
private PropertySource<?> loadIntoGroup(String identifier, String location, private boolean canLoadFileExtension(PropertySourceLoader loader, String name) {
Profile profile) { return Arrays.stream(loader.getFileExtensions()).map(String::toLowerCase)
try { .anyMatch(name.toLowerCase()::endsWith);
return doLoadIntoGroup(identifier, location, profile); }
}
catch (Exception ex) { private void loadForFileExtention(PropertySourceLoader loader, Profile profile,
throw new IllegalStateException( String prefix, String ext) {
"Failed to load property source from location '" + location + "'", if (profile != null) {
ex); // Try the profile-specific file
load(loader, profile, prefix + "-" + profile + ext, null);
// Support profile section in profile file (gh-340)
load(loader, profile, prefix + "-" + profile + ext, profile.getName());
// Try profile specific sections in files we've already processed
for (Profile processedProfile : this.processedProfiles) {
if (processedProfile != null) {
String previouslyLoaded = prefix + "-" + processedProfile + ext;
load(loader, profile, previouslyLoaded, profile.getName());
}
}
} }
// Also try the profile-specific section (if any) of the normal file
load(loader, profile, prefix + ext,
(profile == null ? null : profile.getName()));
} }
private PropertySource<?> doLoadIntoGroup(String identifier, String location, private void load(PropertySourceLoader loader, Profile profile, String location,
Profile profile) throws IOException { String loadProfile) {
Resource resource = this.resourceLoader.getResource(location); try {
PropertySource<?> propertySource = null; Resource resource = this.resourceLoader.getResource(location);
StringBuilder msg = new StringBuilder(); String description = getDescription(profile, location, resource);
if (resource != null && resource.exists()) { if (profile != null) {
String name = "applicationConfig: [" + location + "]"; description = description + " for profile " + profile;
String group = "applicationConfig: [" + identifier + "]";
propertySource = this.propertiesLoader.load(resource, group, name,
(profile == null ? null : profile.getName()));
if (propertySource != null) {
msg.append("Loaded ");
handleProfileProperties(propertySource);
} }
else { if (resource == null || !resource.exists()) {
msg.append("Skipped (empty) "); this.logger.trace("Skipped missing config " + description);
return;
} }
if (!StringUtils.hasText(
StringUtils.getFilenameExtension(resource.getFilename()))) {
this.logger.trace("Skipped empty config extension " + description);
return;
}
String name = "applicationConfig: [" + location + "]"
+ (loadProfile == null ? "" : "#" + loadProfile);
PropertySource<?> loaded = loader.load(name, resource, loadProfile);
if (loaded == null) {
this.logger.trace("Skipped unloaded config " + description);
return;
}
handleProfileProperties(loaded);
this.loaded.computeIfAbsent(profile, (k) -> new MutablePropertySources())
.addLast(loaded);
this.logger.debug("Loaded config file " + description);
} }
else { catch (Exception ex) {
msg.append("Skipped "); throw new IllegalStateException("Failed to load property "
} + "source from location '" + location + "'", ex);
msg.append("config file ");
msg.append(getResourceDescription(location, resource));
if (profile != null) {
msg.append(" for profile ").append(profile);
}
if (resource == null || !resource.exists()) {
msg.append(" resource not found");
this.logger.trace(msg);
}
else {
this.logger.debug(msg);
} }
return propertySource;
} }
private String getResourceDescription(String location, Resource resource) { private String getDescription(Profile profile, String location,
String resourceDescription = "'" + location + "'"; Resource resource) {
if (resource != null) { try {
try { if (resource != null) {
resourceDescription = String.format("'%s' (%s)", String uri = resource.getURI().toASCIIString();
resource.getURI().toASCIIString(), location); return String.format("'%s' (%s)", uri, location);
}
catch (IOException ex) {
// Use the location as the description
} }
} }
return resourceDescription; catch (IOException ex) {
}
return String.format("'%s'", location);
} }
private void handleProfileProperties(PropertySource<?> propertySource) { private void handleProfileProperties(PropertySource<?> propertySource) {
MutablePropertySources propertySources = new MutablePropertySources(); MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(propertySource); propertySources.addFirst(propertySource);
Set<Profile> active = getProfiles(propertySources, "spring.profiles.active"); Binder binder = new Binder(ConfigurationPropertySources.get(propertySources),
Set<Profile> include = getProfiles(propertySources, new PropertySourcesPlaceholdersResolver(this.environment));
"spring.profiles.include"); Set<Profile> active = getProfiles(binder, "spring.profiles.active");
Set<Profile> include = getProfiles(binder, "spring.profiles.include");
maybeActivateProfiles(active); maybeActivateProfiles(active);
addProfiles(include); addProfiles(include);
} }
private Set<Profile> getProfiles(ConfigurableEnvironment environment, private Set<Profile> getProfiles(Binder binder, String name) {
String name) {
return getProfiles(environment.getPropertySources(), name);
}
private Set<Profile> getProfiles(PropertySources sources, String name) {
Binder binder = new Binder(ConfigurationPropertySources.get(sources),
new PropertySourcesPlaceholdersResolver(this.environment));
return binder.bind(name, String[].class).map(this::asProfileSet) return binder.bind(name, String[].class).map(this::asProfileSet)
.orElse(Collections.emptySet()); .orElse(Collections.emptySet());
} }
...@@ -506,20 +505,19 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor, ...@@ -506,20 +505,19 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
} }
private void maybeActivateProfiles(Set<Profile> profiles) { private void maybeActivateProfiles(Set<Profile> profiles) {
if (this.activatedProfiles) { if (profiles.isEmpty()) {
if (!profiles.isEmpty()) {
this.logger.debug("Profiles already activated, '" + profiles
+ "' will not be applied");
}
return; return;
} }
if (!profiles.isEmpty()) { if (this.activatedProfiles) {
addProfiles(profiles); this.logger.debug("Profiles already activated, '" + profiles
this.logger.debug("Activated profiles " + "' will not be applied");
+ StringUtils.collectionToCommaDelimitedString(profiles)); return;
this.activatedProfiles = true;
removeUnprocessedDefaultProfiles();
} }
addProfiles(profiles);
this.logger.debug("Activated profiles "
+ StringUtils.collectionToCommaDelimitedString(profiles));
this.activatedProfiles = true;
removeUnprocessedDefaultProfiles();
} }
private void removeUnprocessedDefaultProfiles() { private void removeUnprocessedDefaultProfiles() {
...@@ -584,8 +582,8 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor, ...@@ -584,8 +582,8 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
private Set<String> getSearchNames() { private Set<String> getSearchNames() {
if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) { if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
return asResolvedSet(this.environment.getProperty(CONFIG_NAME_PROPERTY), String property = this.environment.getProperty(CONFIG_NAME_PROPERTY);
null); return asResolvedSet(property, null);
} }
return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES); return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
} }
...@@ -598,22 +596,26 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor, ...@@ -598,22 +596,26 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
return new LinkedHashSet<>(list); return new LinkedHashSet<>(list);
} }
private void addConfigurationProperties(MutablePropertySources sources) { private void addLoadedPropertySources() {
List<PropertySource<?>> reorderedSources = new ArrayList<>(); MutablePropertySources destination = this.environment.getPropertySources();
for (PropertySource<?> item : sources) { String lastAdded = null;
reorderedSources.add(item); List<MutablePropertySources> loaded = new ArrayList<>(this.loaded.values());
} Collections.reverse(loaded);
addConfigurationProperties(new LoadedPropertySources(reorderedSources)); for (MutablePropertySources sources : loaded) {
} for (PropertySource<?> source : sources) {
if (lastAdded == null) {
private void addConfigurationProperties(LoadedPropertySources loadedSources) { if (destination.contains(DEFAULT_PROPERTIES)) {
MutablePropertySources existingSources = this.environment destination.addBefore(DEFAULT_PROPERTIES, source);
.getPropertySources(); }
if (existingSources.contains(DEFAULT_PROPERTIES)) { else {
existingSources.addBefore(DEFAULT_PROPERTIES, loadedSources); destination.addLast(source);
} }
else { }
existingSources.addLast(loadedSources); else {
destination.addAfter(lastAdded, source);
}
lastAdded = source.getName();
}
} }
} }
...@@ -666,67 +668,4 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor, ...@@ -666,67 +668,4 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
} }
/**
* Holds the configuration {@link PropertySource}s as they are loaded can relocate
* them once configuration classes have been processed.
*/
static class LoadedPropertySources
extends EnumerablePropertySource<Collection<PropertySource<?>>> {
private final Collection<PropertySource<?>> sources;
private final String[] names;
LoadedPropertySources(Collection<PropertySource<?>> sources) {
super(APPLICATION_CONFIGURATION_PROPERTY_SOURCE_NAME, sources);
this.sources = sources;
List<String> names = new ArrayList<>();
for (PropertySource<?> source : sources) {
if (source instanceof EnumerablePropertySource) {
names.addAll(Arrays.asList(
((EnumerablePropertySource<?>) source).getPropertyNames()));
}
}
this.names = names.toArray(new String[names.size()]);
}
@Override
public Object getProperty(String name) {
for (PropertySource<?> propertySource : this.sources) {
Object value = propertySource.getProperty(name);
if (value != null) {
return value;
}
}
return null;
}
public static void finishAndRelocate(MutablePropertySources propertySources) {
String name = APPLICATION_CONFIGURATION_PROPERTY_SOURCE_NAME;
LoadedPropertySources removed = (LoadedPropertySources) propertySources
.get(name);
if (removed != null) {
for (PropertySource<?> propertySource : removed.sources) {
if (propertySource instanceof EnumerableCompositePropertySource) {
EnumerableCompositePropertySource composite = (EnumerableCompositePropertySource) propertySource;
for (PropertySource<?> nested : composite.getSource()) {
propertySources.addAfter(name, nested);
name = nested.getName();
}
}
else {
propertySources.addAfter(name, propertySource);
}
}
propertySources.remove(APPLICATION_CONFIGURATION_PROPERTY_SOURCE_NAME);
}
}
@Override
public String[] getPropertyNames() {
return this.names;
}
}
} }
/*
* 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; ...@@ -21,12 +21,13 @@ import java.io.FileOutputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Properties; 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.Level;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
...@@ -40,10 +41,8 @@ import org.junit.rules.ExpectedException; ...@@ -40,10 +41,8 @@ import org.junit.rules.ExpectedException;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType; 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.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.context.event.ApplicationPreparedEvent; import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.boot.env.EnumerableCompositePropertySource;
import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.testutil.InternalOutputCapture; import org.springframework.boot.testutil.InternalOutputCapture;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
...@@ -55,7 +54,6 @@ import org.springframework.core.Ordered; ...@@ -55,7 +54,6 @@ import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.SimpleCommandLinePropertySource; import org.springframework.core.env.SimpleCommandLinePropertySource;
import org.springframework.core.env.StandardEnvironment; import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.ByteArrayResource;
...@@ -512,24 +510,10 @@ public class ConfigFileApplicationListenerTests { ...@@ -512,24 +510,10 @@ public class ConfigFileApplicationListenerTests {
String property = this.environment.getProperty("my.property"); String property = this.environment.getProperty("my.property");
assertThat(this.environment.getActiveProfiles()).contains("dev"); assertThat(this.environment.getActiveProfiles()).contains("dev");
assertThat(property).isEqualTo("fromdevprofile"); assertThat(property).isEqualTo("fromdevprofile");
LoadedPropertySources propertySource = (LoadedPropertySources) this.environment List<String> names = StreamSupport
.getPropertySources() .stream(this.environment.getPropertySources().spliterator(), false)
.get(ConfigFileApplicationListener.APPLICATION_CONFIGURATION_PROPERTY_SOURCE_NAME); .map(org.springframework.core.env.PropertySource::getName)
Collection<org.springframework.core.env.PropertySource<?>> sources = propertySource .collect(Collectors.toList());
.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());
}
}
assertThat(names).contains( assertThat(names).contains(
"applicationConfig: [classpath:/testsetprofiles.yml]#dev", "applicationConfig: [classpath:/testsetprofiles.yml]#dev",
"applicationConfig: [classpath:/testsetprofiles.yml]"); "applicationConfig: [classpath:/testsetprofiles.yml]");
...@@ -846,10 +830,7 @@ public class ConfigFileApplicationListenerTests { ...@@ -846,10 +830,7 @@ public class ConfigFileApplicationListenerTests {
@Override @Override
public boolean matches(ConfigurableEnvironment value) { public boolean matches(ConfigurableEnvironment value) {
MutablePropertySources sources = new MutablePropertySources( return value.getPropertySources().contains(sourceName);
value.getPropertySources());
LoadedPropertySources.finishAndRelocate(sources);
return sources.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