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
This commit is contained in:
@@ -19,12 +19,13 @@ package org.springframework.boot.context.config;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -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.PropertySourcesPlaceholdersResolver;
|
||||
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.PropertySourcesLoader;
|
||||
import org.springframework.boot.env.PropertySourceLoader;
|
||||
import org.springframework.boot.env.RandomValuePropertySource;
|
||||
import org.springframework.boot.logging.DeferredLog;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
@@ -51,10 +51,9 @@ import org.springframework.context.annotation.ConfigurationClassPostProcessor;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
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.PropertySource;
|
||||
import org.springframework.core.env.PropertySources;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
@@ -258,7 +257,6 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
|
||||
}
|
||||
|
||||
private void reorderSources(ConfigurableEnvironment environment) {
|
||||
LoadedPropertySources.finishAndRelocate(environment.getPropertySources());
|
||||
PropertySource<?> defaultProperties = environment.getPropertySources()
|
||||
.remove(DEFAULT_PROPERTIES);
|
||||
if (defaultProperties != null) {
|
||||
@@ -279,7 +277,7 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
|
||||
|
||||
private final ResourceLoader resourceLoader;
|
||||
|
||||
private PropertySourcesLoader propertiesLoader;
|
||||
private final List<PropertySourceLoader> propertySourceLoaders;
|
||||
|
||||
private Queue<Profile> profiles;
|
||||
|
||||
@@ -287,21 +285,47 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
|
||||
|
||||
private boolean activatedProfiles;
|
||||
|
||||
private Map<Profile, MutablePropertySources> loaded;
|
||||
|
||||
Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
|
||||
this.environment = environment;
|
||||
this.resourceLoader = resourceLoader == null ? new DefaultResourceLoader()
|
||||
: resourceLoader;
|
||||
this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(
|
||||
PropertySourceLoader.class, getClass().getClassLoader());
|
||||
}
|
||||
|
||||
public void load() {
|
||||
this.propertiesLoader = new PropertySourcesLoader();
|
||||
this.activatedProfiles = false;
|
||||
this.profiles = Collections.asLifoQueue(new LinkedList<Profile>());
|
||||
this.processedProfiles = new LinkedList<>();
|
||||
this.activatedProfiles = false;
|
||||
this.loaded = new LinkedHashMap<>();
|
||||
initializeProfiles();
|
||||
while (!this.profiles.isEmpty()) {
|
||||
Profile profile = this.profiles.poll();
|
||||
for (String location : getSearchLocations()) {
|
||||
if (!location.endsWith("/")) {
|
||||
// location is a filename already, so don't search for more
|
||||
// filenames
|
||||
load(profile, location, null);
|
||||
}
|
||||
else {
|
||||
for (String name : getSearchNames()) {
|
||||
load(profile, location, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.processedProfiles.add(profile);
|
||||
}
|
||||
addLoadedPropertySources();
|
||||
}
|
||||
|
||||
// Pre-existing active profiles set via Environment.setActiveProfiles()
|
||||
// are additional profiles and config files are allowed to add more if
|
||||
// they want to, so don't call addActiveProfiles() here.
|
||||
/**
|
||||
* 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()) {
|
||||
@@ -312,30 +336,10 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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()) {
|
||||
Profile profile = this.profiles.poll();
|
||||
for (String location : getSearchLocations()) {
|
||||
if (!location.endsWith("/")) {
|
||||
// location is a filename already, so don't search for more
|
||||
// filenames
|
||||
load(location, null, profile);
|
||||
}
|
||||
else {
|
||||
for (String name : getSearchNames()) {
|
||||
load(location, name, profile);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.processedProfiles.add(profile);
|
||||
}
|
||||
|
||||
addConfigurationProperties(this.propertiesLoader.getPropertySources());
|
||||
}
|
||||
|
||||
private Set<Profile> initializeActiveProfiles() {
|
||||
@@ -345,11 +349,10 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
|
||||
}
|
||||
// Any pre-existing active profiles set via property sources (e.g. System
|
||||
// properties) take precedence over those added in config files.
|
||||
Set<Profile> active = getProfiles(this.environment, "spring.profiles.active");
|
||||
Set<Profile> activeProfiles = new LinkedHashSet<>(active);
|
||||
Set<Profile> include = getProfiles(this.environment,
|
||||
"spring.profiles.include");
|
||||
activeProfiles.addAll(include);
|
||||
Binder binder = Binder.get(this.environment);
|
||||
Set<Profile> activeProfiles = new LinkedHashSet<>();
|
||||
activeProfiles.addAll(getProfiles(binder, ACTIVE_PROFILES_PROPERTY));
|
||||
activeProfiles.addAll(getProfiles(binder, INCLUDE_PROFILES_PROPERTY));
|
||||
maybeActivateProfiles(activeProfiles);
|
||||
return activeProfiles;
|
||||
}
|
||||
@@ -381,117 +384,113 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
|
||||
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)) {
|
||||
// Try to load directly from the location
|
||||
loadIntoGroup(group, location, profile);
|
||||
}
|
||||
else {
|
||||
// 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);
|
||||
for (PropertySourceLoader loader : this.propertySourceLoaders) {
|
||||
if (canLoadFileExtension(loader, location)) {
|
||||
load(loader, profile, location,
|
||||
(profile == null ? null : profile.getName()));
|
||||
}
|
||||
// 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,
|
||||
Profile profile) {
|
||||
private boolean canLoadFileExtension(PropertySourceLoader loader, String name) {
|
||||
return Arrays.stream(loader.getFileExtensions()).map(String::toLowerCase)
|
||||
.anyMatch(name.toLowerCase()::endsWith);
|
||||
}
|
||||
|
||||
private void loadForFileExtention(PropertySourceLoader loader, Profile profile,
|
||||
String prefix, String ext) {
|
||||
if (profile != null) {
|
||||
// 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 void load(PropertySourceLoader loader, Profile profile, String location,
|
||||
String loadProfile) {
|
||||
try {
|
||||
return doLoadIntoGroup(identifier, location, profile);
|
||||
Resource resource = this.resourceLoader.getResource(location);
|
||||
String description = getDescription(profile, location, resource);
|
||||
if (profile != null) {
|
||||
description = description + " for profile " + profile;
|
||||
}
|
||||
if (resource == null || !resource.exists()) {
|
||||
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);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(
|
||||
"Failed to load property source from location '" + location + "'",
|
||||
ex);
|
||||
throw new IllegalStateException("Failed to load property "
|
||||
+ "source from location '" + location + "'", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private PropertySource<?> doLoadIntoGroup(String identifier, String location,
|
||||
Profile profile) throws IOException {
|
||||
Resource resource = this.resourceLoader.getResource(location);
|
||||
PropertySource<?> propertySource = null;
|
||||
StringBuilder msg = new StringBuilder();
|
||||
if (resource != null && resource.exists()) {
|
||||
String name = "applicationConfig: [" + location + "]";
|
||||
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 {
|
||||
msg.append("Skipped (empty) ");
|
||||
private String getDescription(Profile profile, String location,
|
||||
Resource resource) {
|
||||
try {
|
||||
if (resource != null) {
|
||||
String uri = resource.getURI().toASCIIString();
|
||||
return String.format("'%s' (%s)", uri, location);
|
||||
}
|
||||
}
|
||||
else {
|
||||
msg.append("Skipped ");
|
||||
catch (IOException 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) {
|
||||
String resourceDescription = "'" + location + "'";
|
||||
if (resource != null) {
|
||||
try {
|
||||
resourceDescription = String.format("'%s' (%s)",
|
||||
resource.getURI().toASCIIString(), location);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
// Use the location as the description
|
||||
}
|
||||
}
|
||||
return resourceDescription;
|
||||
return String.format("'%s'", location);
|
||||
}
|
||||
|
||||
private void handleProfileProperties(PropertySource<?> propertySource) {
|
||||
MutablePropertySources propertySources = new MutablePropertySources();
|
||||
propertySources.addFirst(propertySource);
|
||||
Set<Profile> active = getProfiles(propertySources, "spring.profiles.active");
|
||||
Set<Profile> include = getProfiles(propertySources,
|
||||
"spring.profiles.include");
|
||||
Binder binder = new Binder(ConfigurationPropertySources.get(propertySources),
|
||||
new PropertySourcesPlaceholdersResolver(this.environment));
|
||||
Set<Profile> active = getProfiles(binder, "spring.profiles.active");
|
||||
Set<Profile> include = getProfiles(binder, "spring.profiles.include");
|
||||
maybeActivateProfiles(active);
|
||||
addProfiles(include);
|
||||
}
|
||||
|
||||
private Set<Profile> getProfiles(ConfigurableEnvironment environment,
|
||||
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));
|
||||
private Set<Profile> getProfiles(Binder binder, String name) {
|
||||
return binder.bind(name, String[].class).map(this::asProfileSet)
|
||||
.orElse(Collections.emptySet());
|
||||
}
|
||||
@@ -506,20 +505,19 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
|
||||
}
|
||||
|
||||
private void maybeActivateProfiles(Set<Profile> profiles) {
|
||||
if (this.activatedProfiles) {
|
||||
if (!profiles.isEmpty()) {
|
||||
this.logger.debug("Profiles already activated, '" + profiles
|
||||
+ "' will not be applied");
|
||||
}
|
||||
if (profiles.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (!profiles.isEmpty()) {
|
||||
addProfiles(profiles);
|
||||
this.logger.debug("Activated profiles "
|
||||
+ StringUtils.collectionToCommaDelimitedString(profiles));
|
||||
this.activatedProfiles = true;
|
||||
removeUnprocessedDefaultProfiles();
|
||||
if (this.activatedProfiles) {
|
||||
this.logger.debug("Profiles already activated, '" + profiles
|
||||
+ "' will not be applied");
|
||||
return;
|
||||
}
|
||||
addProfiles(profiles);
|
||||
this.logger.debug("Activated profiles "
|
||||
+ StringUtils.collectionToCommaDelimitedString(profiles));
|
||||
this.activatedProfiles = true;
|
||||
removeUnprocessedDefaultProfiles();
|
||||
}
|
||||
|
||||
private void removeUnprocessedDefaultProfiles() {
|
||||
@@ -584,8 +582,8 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
|
||||
|
||||
private Set<String> getSearchNames() {
|
||||
if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
|
||||
return asResolvedSet(this.environment.getProperty(CONFIG_NAME_PROPERTY),
|
||||
null);
|
||||
String property = this.environment.getProperty(CONFIG_NAME_PROPERTY);
|
||||
return asResolvedSet(property, null);
|
||||
}
|
||||
return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
|
||||
}
|
||||
@@ -598,22 +596,26 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
|
||||
return new LinkedHashSet<>(list);
|
||||
}
|
||||
|
||||
private void addConfigurationProperties(MutablePropertySources sources) {
|
||||
List<PropertySource<?>> reorderedSources = new ArrayList<>();
|
||||
for (PropertySource<?> item : sources) {
|
||||
reorderedSources.add(item);
|
||||
}
|
||||
addConfigurationProperties(new LoadedPropertySources(reorderedSources));
|
||||
}
|
||||
|
||||
private void addConfigurationProperties(LoadedPropertySources loadedSources) {
|
||||
MutablePropertySources existingSources = this.environment
|
||||
.getPropertySources();
|
||||
if (existingSources.contains(DEFAULT_PROPERTIES)) {
|
||||
existingSources.addBefore(DEFAULT_PROPERTIES, loadedSources);
|
||||
}
|
||||
else {
|
||||
existingSources.addLast(loadedSources);
|
||||
private void addLoadedPropertySources() {
|
||||
MutablePropertySources destination = this.environment.getPropertySources();
|
||||
String lastAdded = null;
|
||||
List<MutablePropertySources> loaded = new ArrayList<>(this.loaded.values());
|
||||
Collections.reverse(loaded);
|
||||
for (MutablePropertySources sources : loaded) {
|
||||
for (PropertySource<?> source : sources) {
|
||||
if (lastAdded == null) {
|
||||
if (destination.contains(DEFAULT_PROPERTIES)) {
|
||||
destination.addBefore(DEFAULT_PROPERTIES, source);
|
||||
}
|
||||
else {
|
||||
destination.addLast(source);
|
||||
}
|
||||
}
|
||||
else {
|
||||
destination.addAfter(lastAdded, source);
|
||||
}
|
||||
lastAdded = source.getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* 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");
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user