Commit c0d79b92 authored by Phillip Webb's avatar Phillip Webb

Rationalize multi-document config file handling

Update `PropertySourceLoader` so that it no longer needs to deal with
matching multi-document files using the `spring.profile` property. The
loader now simply returns one or more `PropertSource` instances for a
given `Resource`.

All property matching now occurs in the `ConfigFileApplicationListener`.
This allows document processing logic to be contained in a single place,
and allows us to rationalize the algorithm so that negative matching
profiles are processed last.

Fixes gh-12159
parent 3d8f760e
...@@ -22,7 +22,6 @@ import org.springframework.boot.SpringApplication; ...@@ -22,7 +22,6 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.env.YamlPropertySourceLoader; import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
...@@ -41,17 +40,16 @@ public class EnvironmentPostProcessorExample implements EnvironmentPostProcessor ...@@ -41,17 +40,16 @@ public class EnvironmentPostProcessorExample implements EnvironmentPostProcessor
public void postProcessEnvironment(ConfigurableEnvironment environment, public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) { SpringApplication application) {
Resource path = new ClassPathResource("com/example/myapp/config.yml"); Resource path = new ClassPathResource("com/example/myapp/config.yml");
PropertySource<?> propertySource = loadYaml(path, environment); PropertySource<?> propertySource = loadYaml(path);
environment.getPropertySources().addLast(propertySource); environment.getPropertySources().addLast(propertySource);
} }
private PropertySource<?> loadYaml(Resource path, Environment environment) { private PropertySource<?> loadYaml(Resource path) {
if (!path.exists()) { if (!path.exists()) {
throw new IllegalArgumentException("Resource " + path + " does not exist"); throw new IllegalArgumentException("Resource " + path + " does not exist");
} }
try { try {
return this.loader.load("custom-resource", path, null, return this.loader.load("custom-resource", path).get(0);
environment::acceptsProfiles);
} }
catch (IOException ex) { catch (IOException ex) {
throw new IllegalStateException( throw new IllegalStateException(
......
...@@ -180,10 +180,10 @@ public class PropertiesMigrationReporterTests { ...@@ -180,10 +180,10 @@ public class PropertiesMigrationReporterTests {
private PropertySource<?> loadPropertySource(String name, String path) private PropertySource<?> loadPropertySource(String name, String path)
throws IOException { throws IOException {
ClassPathResource resource = new ClassPathResource(path); ClassPathResource resource = new ClassPathResource(path);
PropertySource<?> propertySource = new PropertiesPropertySourceLoader().load(name, List<PropertySource<?>> propertySources = new PropertiesPropertySourceLoader()
resource, null, (profile) -> true); .load(name, resource);
assertThat(propertySource).isNotNull(); assertThat(propertySources).isNotEmpty();
return propertySource; return propertySources.get(0);
} }
private ConfigurationMetadataRepository loadRepository(String... content) { private ConfigurationMetadataRepository loadRepository(String... content) {
......
...@@ -20,6 +20,7 @@ import java.io.IOException; ...@@ -20,6 +20,7 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.LinkedList; import java.util.LinkedList;
...@@ -27,6 +28,8 @@ import java.util.List; ...@@ -27,6 +28,8 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Queue; import java.util.Queue;
import java.util.Set; import java.util.Set;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
...@@ -36,6 +39,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; ...@@ -36,6 +39,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
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.context.properties.bind.Bindable;
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;
...@@ -58,6 +62,8 @@ import org.springframework.core.io.Resource; ...@@ -58,6 +62,8 @@ import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ResourceUtils; import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
...@@ -101,6 +107,8 @@ public class ConfigFileApplicationListener ...@@ -101,6 +107,8 @@ public class ConfigFileApplicationListener
private static final String DEFAULT_NAMES = "application"; private static final String DEFAULT_NAMES = "application";
private static final Set<String> NO_SEARCH_NAMES = Collections.singleton(null);
/** /**
* The "active profiles" property name. * The "active profiles" property name.
*/ */
...@@ -302,6 +310,8 @@ public class ConfigFileApplicationListener ...@@ -302,6 +310,8 @@ public class ConfigFileApplicationListener
private Map<Profile, MutablePropertySources> loaded; private Map<Profile, MutablePropertySources> loaded;
private Map<DocumentsCacheKey, List<Document>> loadDocumentsCache = new HashMap<>();
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()
...@@ -318,20 +328,12 @@ public class ConfigFileApplicationListener ...@@ -318,20 +328,12 @@ public class ConfigFileApplicationListener
initializeProfiles(); initializeProfiles();
while (!this.profiles.isEmpty()) { while (!this.profiles.isEmpty()) {
Profile profile = this.profiles.poll(); Profile profile = this.profiles.poll();
for (String location : getSearchLocations()) { load(profile, this::getPositiveProfileFilter,
if (!location.endsWith("/")) { addToLoaded(MutablePropertySources::addLast, false));
// 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); this.processedProfiles.add(profile);
} }
load(null, this::getNegativeProfileFilter,
addToLoaded(MutablePropertySources::addFirst, true));
addLoadedPropertySources(); addLoadedPropertySources();
} }
...@@ -399,24 +401,66 @@ public class ConfigFileApplicationListener ...@@ -399,24 +401,66 @@ public class ConfigFileApplicationListener
return unprocessedActiveProfiles; return unprocessedActiveProfiles;
} }
/** private DocumentFilter getPositiveProfileFilter(Profile profile) {
* Load an actual property source file. return (Document document) -> {
* @param profile the profile being loaded if (profile == null) {
* @param location the location of the resource return ObjectUtils.isEmpty(document.getProfiles());
* @param name an optional name to be combined with the location }
*/ return ObjectUtils.containsElement(document.getProfiles(),
private void load(Profile profile, String location, String name) { profile.getName())
&& this.environment.acceptsProfiles(document.getProfiles());
};
}
private DocumentFilter getNegativeProfileFilter(Profile profile) {
return (Document document) -> (profile == null
&& !ObjectUtils.isEmpty(document.getProfiles())
&& this.environment.acceptsProfiles(document.getProfiles()));
}
private DocumentConsumer addToLoaded(
BiConsumer<MutablePropertySources, PropertySource<?>> addMethod,
boolean checkForExisting) {
return (profile, document) -> {
if (checkForExisting) {
for (MutablePropertySources merged : this.loaded.values()) {
if (merged.contains(document.getPropertySource().getName())) {
return;
}
}
}
MutablePropertySources merged = this.loaded.computeIfAbsent(profile,
(k) -> new MutablePropertySources());
addMethod.accept(merged, document.getPropertySource());
};
}
private void load(Profile profile, DocumentFilterFactory filterFactory,
DocumentConsumer consumer) {
getSearchLocations().forEach((location) -> {
boolean isFolder = location.endsWith("/");
Set<String> names = (isFolder ? getSearchNames() : NO_SEARCH_NAMES);
names.forEach(
(name) -> load(location, name, profile, filterFactory, consumer));
});
}
private void load(String location, String name, Profile profile,
DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
if (!StringUtils.hasText(name)) { if (!StringUtils.hasText(name)) {
for (PropertySourceLoader loader : this.propertySourceLoaders) { for (PropertySourceLoader loader : this.propertySourceLoaders) {
if (canLoadFileExtension(loader, location)) { if (canLoadFileExtension(loader, location)) {
load(loader, profile, location, load(loader, location, profile,
(profile == null ? null : profile.getName())); filterFactory.getDocumentFilter(profile), consumer);
} }
} }
} }
for (PropertySourceLoader loader : this.propertySourceLoaders) { for (PropertySourceLoader loader : this.propertySourceLoaders) {
for (String ext : loader.getFileExtensions()) { for (String fileExtension : loader.getFileExtensions()) {
loadForFileExtension(loader, profile, location + name, "." + ext); String prefix = location + name;
fileExtension = "." + fileExtension;
loadForFileExtension(loader, prefix, fileExtension, profile,
filterFactory, consumer);
} }
} }
} }
...@@ -427,28 +471,31 @@ public class ConfigFileApplicationListener ...@@ -427,28 +471,31 @@ public class ConfigFileApplicationListener
fileExtension)); fileExtension));
} }
private void loadForFileExtension(PropertySourceLoader loader, Profile profile, private void loadForFileExtension(PropertySourceLoader loader, String prefix,
String prefix, String ext) { String fileExtension, Profile profile,
DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null);
DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile);
if (profile != null) { if (profile != null) {
// Try the profile-specific file // Try profile-specific file & profile section in profile file (gh-340)
load(loader, profile, prefix + "-" + profile + ext, null); String profileSpecificFile = prefix + "-" + profile + fileExtension;
// Support profile section in profile file (gh-340) load(loader, profileSpecificFile, profile, defaultFilter, consumer);
load(loader, profile, prefix + "-" + profile + ext, profile.getName()); load(loader, profileSpecificFile, profile, profileFilter, consumer);
// Try profile specific sections in files we've already processed // Try profile specific sections in files we've already processed
for (Profile processedProfile : this.processedProfiles) { for (Profile processedProfile : this.processedProfiles) {
if (processedProfile != null) { if (processedProfile != null) {
String previouslyLoaded = prefix + "-" + processedProfile + ext; String previouslyLoaded = prefix + "-" + processedProfile
load(loader, profile, previouslyLoaded, profile.getName()); + fileExtension;
load(loader, previouslyLoaded, profile, profileFilter, consumer);
} }
} }
} }
// Also try the profile-specific section (if any) of the normal file // Also try the profile-specific section (if any) of the normal file
load(loader, profile, prefix + ext, load(loader, prefix + fileExtension, profile, profileFilter, consumer);
(profile == null ? null : profile.getName()));
} }
private void load(PropertySourceLoader loader, Profile profile, String location, private void load(PropertySourceLoader loader, String location, Profile profile,
String loadProfile) { DocumentFilter filter, DocumentConsumer consumer) {
try { try {
Resource resource = this.resourceLoader.getResource(location); Resource resource = this.resourceLoader.getResource(location);
String description = getDescription(location, resource); String description = getDescription(location, resource);
...@@ -464,18 +511,25 @@ public class ConfigFileApplicationListener ...@@ -464,18 +511,25 @@ public class ConfigFileApplicationListener
this.logger.trace("Skipped empty config extension " + description); this.logger.trace("Skipped empty config extension " + description);
return; return;
} }
String name = "applicationConfig: [" + location + "]" String name = "applicationConfig: [" + location + "]";
+ (loadProfile == null ? "" : "#" + loadProfile); List<Document> documents = loadDocuments(loader, name, resource);
PropertySource<?> loaded = loader.load(name, resource, loadProfile, if (CollectionUtils.isEmpty(documents)) {
this.environment::acceptsProfiles);
if (loaded == null) {
this.logger.trace("Skipped unloaded config " + description); this.logger.trace("Skipped unloaded config " + description);
return; return;
} }
handleProfileProperties(loaded); List<Document> loaded = new ArrayList<>();
this.loaded.computeIfAbsent(profile, (k) -> new MutablePropertySources()) for (Document document : documents) {
.addLast(loaded); if (filter.match(document)) {
this.logger.debug("Loaded config file " + description); maybeActivateProfiles(document.getActiveProfiles());
addProfiles(document.getIncludeProfiles());
loaded.add(document);
}
}
Collections.reverse(loaded);
if (!loaded.isEmpty()) {
loaded.forEach((document) -> consumer.accept(profile, document));
this.logger.debug("Loaded config file " + description);
}
} }
catch (Exception ex) { catch (Exception ex) {
throw new IllegalStateException("Failed to load property " throw new IllegalStateException("Failed to load property "
...@@ -483,6 +537,34 @@ public class ConfigFileApplicationListener ...@@ -483,6 +537,34 @@ public class ConfigFileApplicationListener
} }
} }
private List<Document> loadDocuments(PropertySourceLoader loader, String name,
Resource resource) throws IOException {
loader.load(name, resource);
DocumentsCacheKey cacheKey = new DocumentsCacheKey(loader, resource);
List<Document> documents = this.loadDocumentsCache.get(cacheKey);
if (documents == null) {
List<PropertySource<?>> loaded = loader.load(name, resource);
documents = asDocuments(loaded);
}
return documents;
}
private List<Document> asDocuments(List<PropertySource<?>> loaded) {
if (loaded == null) {
return Collections.emptyList();
}
return loaded.stream().map((propertySource) -> {
Binder binder = new Binder(
ConfigurationPropertySources.from(propertySource),
new PropertySourcesPlaceholdersResolver(this.environment));
return new Document(propertySource,
binder.bind("spring.profiles", Bindable.of(String[].class))
.orElse(null),
getProfiles(binder, ACTIVE_PROFILES_PROPERTY),
getProfiles(binder, INCLUDE_PROFILES_PROPERTY));
}).collect(Collectors.toList());
}
private String getDescription(String location, Resource resource) { private String getDescription(String location, Resource resource) {
try { try {
if (resource != null) { if (resource != null) {
...@@ -495,15 +577,6 @@ public class ConfigFileApplicationListener ...@@ -495,15 +577,6 @@ public class ConfigFileApplicationListener
return String.format("'%s'", location); return String.format("'%s'", location);
} }
private void handleProfileProperties(PropertySource<?> propertySource) {
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");
maybeActivateProfiles(active);
addProfiles(include);
}
private Set<Profile> getProfiles(Binder binder, String name) { private Set<Profile> getProfiles(Binder binder, String name) {
return binder.bind(name, String[].class).map(this::asProfileSet) return binder.bind(name, String[].class).map(this::asProfileSet)
.orElse(Collections.emptySet()); .orElse(Collections.emptySet());
...@@ -638,6 +711,9 @@ public class ConfigFileApplicationListener ...@@ -638,6 +711,9 @@ public class ConfigFileApplicationListener
} }
/**
* A Spring Profile that can be loaded.
*/
private static class Profile { private static class Profile {
private final String name; private final String name;
...@@ -685,4 +761,117 @@ public class ConfigFileApplicationListener ...@@ -685,4 +761,117 @@ public class ConfigFileApplicationListener
} }
/**
* Cache key used to save loading the same document multiple times.
*/
private static class DocumentsCacheKey {
private final PropertySourceLoader loader;
private final Resource resource;
DocumentsCacheKey(PropertySourceLoader loader, Resource resource) {
this.loader = loader;
this.resource = resource;
}
@Override
public int hashCode() {
return this.loader.hashCode() * 31 + this.resource.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
DocumentsCacheKey other = (DocumentsCacheKey) obj;
return this.loader.equals(other.loader)
&& this.resource.equals(other.resource);
}
}
/**
* A single document loaded by a {@link PropertySourceLoader}.
*/
private static class Document {
private final PropertySource<?> propertySource;
private String[] profiles;
private final Set<Profile> activeProfiles;
private final Set<Profile> includeProfiles;
Document(PropertySource<?> propertySource, String[] profiles,
Set<Profile> activeProfiles, Set<Profile> includeProfiles) {
this.propertySource = propertySource;
this.profiles = profiles;
this.activeProfiles = activeProfiles;
this.includeProfiles = includeProfiles;
}
public PropertySource<?> getPropertySource() {
return this.propertySource;
}
public String[] getProfiles() {
return this.profiles;
}
public Set<Profile> getActiveProfiles() {
return this.activeProfiles;
}
public Set<Profile> getIncludeProfiles() {
return this.includeProfiles;
}
@Override
public String toString() {
return this.propertySource.toString();
}
}
/**
* Factory used to create a {@link DocumentFilter}.
*/
@FunctionalInterface
private interface DocumentFilterFactory {
/**
* Create a filter for the given profile.
* @param profile the profile or {@code null}
* @return the filter
*/
DocumentFilter getDocumentFilter(Profile profile);
}
/**
* Filter used to restrict when a {@link Document} is loaded.
*/
@FunctionalInterface
private interface DocumentFilter {
boolean match(Document document);
}
/**
* Consumer used to handle a loaded {@link Document}.
*/
@FunctionalInterface
private interface DocumentConsumer {
void accept(Profile profile, Document document);
}
} }
/*
* Copyright 2012-2018 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.function.Predicate;
import org.springframework.util.ObjectUtils;
/**
* {@link SpringProfilesDocumentMatcher} that tests if a profile is accepted.
*
* @author Phillip Webb
*/
class AcceptsProfilesDocumentMatcher extends SpringProfilesDocumentMatcher {
private final Predicate<String[]> acceptsProfiles;
AcceptsProfilesDocumentMatcher(Predicate<String[]> acceptsProfiles) {
this.acceptsProfiles = acceptsProfiles;
}
@Override
protected boolean matches(String[] profiles) {
return ObjectUtils.isEmpty(profiles) || this.acceptsProfiles.test(profiles);
}
}
...@@ -16,9 +16,9 @@ ...@@ -16,9 +16,9 @@
package org.springframework.boot.env; package org.springframework.boot.env;
import java.util.LinkedHashMap; import java.util.ArrayList;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Predicate;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
...@@ -53,11 +53,8 @@ class OriginTrackedYamlLoader extends YamlProcessor { ...@@ -53,11 +53,8 @@ class OriginTrackedYamlLoader extends YamlProcessor {
private final Resource resource; private final Resource resource;
OriginTrackedYamlLoader(Resource resource, String profileToLoad, OriginTrackedYamlLoader(Resource resource) {
Predicate<String[]> acceptsProfiles) {
this.resource = resource; this.resource = resource;
setDocumentMatchers(new ProfileToLoadDocumentMatcher(profileToLoad),
new AcceptsProfilesDocumentMatcher(acceptsProfiles));
setResources(resource); setResources(resource);
} }
...@@ -70,9 +67,11 @@ class OriginTrackedYamlLoader extends YamlProcessor { ...@@ -70,9 +67,11 @@ class OriginTrackedYamlLoader extends YamlProcessor {
return new Yaml(constructor, representer, dumperOptions, resolver); return new Yaml(constructor, representer, dumperOptions, resolver);
} }
public Map<String, Object> load() { public List<Map<String, Object>> load() {
final Map<String, Object> result = new LinkedHashMap<>(); final List<Map<String, Object>> result = new ArrayList<>();
process((properties, map) -> result.putAll(getFlattenedMap(map))); process((properties, map) -> {
result.add(getFlattenedMap(map));
});
return result; return result;
} }
......
/*
* Copyright 2012-2018 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.Arrays;
import org.springframework.util.ObjectUtils;
/**
* {@link SpringProfilesDocumentMatcher} that matches a specific profile to load.
*
* @author Phillip Webb
*/
class ProfileToLoadDocumentMatcher extends SpringProfilesDocumentMatcher {
private final String profile;
ProfileToLoadDocumentMatcher(String profile) {
this.profile = profile;
}
@Override
protected boolean matches(String[] profiles) {
String[] positiveProfiles = (profiles == null ? null : Arrays.stream(profiles)
.filter(this::isPositiveProfile).toArray(String[]::new));
if (this.profile == null) {
return ObjectUtils.isEmpty(positiveProfiles);
}
return ObjectUtils.containsElement(positiveProfiles, this.profile);
}
private boolean isPositiveProfile(String profile) {
return !profile.startsWith("!");
}
}
...@@ -17,8 +17,9 @@ ...@@ -17,8 +17,9 @@
package org.springframework.boot.env; package org.springframework.boot.env;
import java.io.IOException; import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Predicate;
import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
...@@ -41,15 +42,14 @@ public class PropertiesPropertySourceLoader implements PropertySourceLoader { ...@@ -41,15 +42,14 @@ public class PropertiesPropertySourceLoader implements PropertySourceLoader {
} }
@Override @Override
public PropertySource<?> load(String name, Resource resource, String profileToLoad, public List<PropertySource<?>> load(String name, Resource resource)
Predicate<String[]> acceptsProfiles) throws IOException { throws IOException {
if (profileToLoad == null) { Map<String, ?> properties = loadProperties(resource);
Map<String, ?> properties = loadProperties(resource); if (properties.isEmpty()) {
if (!properties.isEmpty()) { return Collections.emptyList();
return new OriginTrackedMapPropertySource(name, properties);
}
} }
return null; return Collections
.singletonList(new OriginTrackedMapPropertySource(name, properties));
} }
@SuppressWarnings({ "unchecked", "rawtypes" }) @SuppressWarnings({ "unchecked", "rawtypes" })
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
package org.springframework.boot.env; package org.springframework.boot.env;
import java.io.IOException; import java.io.IOException;
import java.util.function.Predicate; import java.util.List;
import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
...@@ -39,17 +39,15 @@ public interface PropertySourceLoader { ...@@ -39,17 +39,15 @@ public interface PropertySourceLoader {
String[] getFileExtensions(); String[] getFileExtensions();
/** /**
* Load the resource into a property source. * Load the resource into one or more property sources. Implementations may either
* @param name the name of the property source * return a list containing a single source, or in the case of a multi-document format
* such as yaml a source or each document in the resource.
* @param name the root name of the property source. If multiple documents are loaded
* an additional suffix should be added to the name for each source loaded.
* @param resource the resource to load * @param resource the resource to load
* @param profileToLoad the name of the profile to load or {@code null}. The profile * @return a list property sources
* can be used to load multi-document files (such as YAML). Simple property formats
* should {@code null} when asked to load a profile.
* @param acceptsProfiles predicate to determine if a particular profile is accepted
* @return a property source or {@code null}
* @throws IOException if the source cannot be loaded * @throws IOException if the source cannot be loaded
*/ */
PropertySource<?> load(String name, Resource resource, String profileToLoad, List<PropertySource<?>> load(String name, Resource resource) throws IOException;
Predicate<String[]> acceptsProfiles) throws IOException;
} }
/*
* Copyright 2012-2018 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.Map;
import java.util.Properties;
import org.springframework.beans.factory.config.YamlProcessor.DocumentMatcher;
import org.springframework.beans.factory.config.YamlProcessor.MatchStatus;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationProperty;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.boot.origin.OriginTrackedValue;
/**
* Base class for {@link DocumentMatcher DocumentMatchers} that check the
* {@code spring.profiles} property.
*
* @author Phillip Webb
* @see OriginTrackedYamlLoader
*/
abstract class SpringProfilesDocumentMatcher implements DocumentMatcher {
@Override
public final MatchStatus matches(Properties properties) {
Binder binder = new Binder(
new OriginTrackedValueConfigurationPropertySource(properties));
String[] profiles = binder.bind("spring.profiles", Bindable.of(String[].class))
.orElse(null);
return (matches(profiles) ? MatchStatus.ABSTAIN : MatchStatus.NOT_FOUND);
}
protected abstract boolean matches(String[] profiles);
/**
* {@link MapConfigurationPropertySource} that deals with unwrapping
* {@link OriginTrackedValue OriginTrackedValues} from the underlying map.
*/
static class OriginTrackedValueConfigurationPropertySource
extends MapConfigurationPropertySource {
OriginTrackedValueConfigurationPropertySource(Map<?, ?> map) {
super(map);
}
@Override
public ConfigurationProperty getConfigurationProperty(
ConfigurationPropertyName name) {
ConfigurationProperty property = super.getConfigurationProperty(name);
if (property != null && property.getValue() instanceof OriginTrackedValue) {
OriginTrackedValue originTrackedValue = (OriginTrackedValue) property
.getValue();
property = new ConfigurationProperty(property.getName(),
originTrackedValue.getValue(), originTrackedValue.getOrigin());
}
return property;
}
}
}
...@@ -17,8 +17,10 @@ ...@@ -17,8 +17,10 @@
package org.springframework.boot.env; package org.springframework.boot.env;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Predicate;
import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
...@@ -39,18 +41,23 @@ public class YamlPropertySourceLoader implements PropertySourceLoader { ...@@ -39,18 +41,23 @@ public class YamlPropertySourceLoader implements PropertySourceLoader {
} }
@Override @Override
public PropertySource<?> load(String name, Resource resource, String profileToLoad, public List<PropertySource<?>> load(String name, Resource resource)
Predicate<String[]> acceptsProfiles) throws IOException { throws IOException {
if (!ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", null)) { if (!ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", null)) {
throw new IllegalStateException("Attempted to load " + name throw new IllegalStateException("Attempted to load " + name
+ " but snakeyaml was not found on the classpath"); + " but snakeyaml was not found on the classpath");
} }
Map<String, Object> source = new OriginTrackedYamlLoader(resource, profileToLoad, List<Map<String, Object>> loaded = new OriginTrackedYamlLoader(resource).load();
acceptsProfiles).load(); if (loaded.isEmpty()) {
if (!source.isEmpty()) { return Collections.emptyList();
return new OriginTrackedMapPropertySource(name, source);
} }
return null; List<PropertySource<?>> propertySources = new ArrayList<>(loaded.size());
for (int i = 0; i < loaded.size(); i++) {
propertySources.add(new OriginTrackedMapPropertySource(
name + (loaded.size() == 1 ? "" : " (document #" + i + ")"),
loaded.get(i)));
}
return propertySources;
} }
} }
...@@ -540,8 +540,8 @@ public class ConfigFileApplicationListenerTests { ...@@ -540,8 +540,8 @@ public class ConfigFileApplicationListenerTests {
.map(org.springframework.core.env.PropertySource::getName) .map(org.springframework.core.env.PropertySource::getName)
.collect(Collectors.toList()); .collect(Collectors.toList());
assertThat(names).contains( assertThat(names).contains(
"applicationConfig: [classpath:/testsetprofiles.yml]#dev", "applicationConfig: [classpath:/testsetprofiles.yml] (document #0)",
"applicationConfig: [classpath:/testsetprofiles.yml]"); "applicationConfig: [classpath:/testsetprofiles.yml] (document #1)");
} }
@Test @Test
......
...@@ -88,6 +88,48 @@ public class ConfigFileApplicationListenerYamlProfileNegationTests { ...@@ -88,6 +88,48 @@ public class ConfigFileApplicationListenerYamlProfileNegationTests {
assertVersionProperty(this.context, "NOT A", "C", "B"); assertVersionProperty(this.context, "NOT A", "C", "B");
} }
@Test
public void yamlProfileCascading() {
SpringApplication application = new SpringApplication(Config.class);
application.setWebApplicationType(WebApplicationType.NONE);
String configName = "--spring.config.name=cascadingprofiles";
this.context = application.run(configName);
assertVersionProperty(this.context, "E", "D", "C", "E", "A", "B");
assertThat(this.context.getEnvironment().getProperty("not-a")).isNull();
assertThat(this.context.getEnvironment().getProperty("not-b")).isNull();
assertThat(this.context.getEnvironment().getProperty("not-c")).isNull();
assertThat(this.context.getEnvironment().getProperty("not-d")).isNull();
assertThat(this.context.getEnvironment().getProperty("not-e")).isNull();
}
@Test
public void yamlProfileCascadingOverrideProfilesA() {
SpringApplication application = new SpringApplication(Config.class);
application.setWebApplicationType(WebApplicationType.NONE);
String configName = "--spring.config.name=cascadingprofiles";
this.context = application.run(configName, "--spring.profiles.active=A");
assertVersionProperty(this.context, "E", "C", "E", "A");
assertThat(this.context.getEnvironment().getProperty("not-a")).isNull();
assertThat(this.context.getEnvironment().getProperty("not-b")).isEqualTo("true");
assertThat(this.context.getEnvironment().getProperty("not-c")).isNull();
assertThat(this.context.getEnvironment().getProperty("not-d")).isEqualTo("true");
assertThat(this.context.getEnvironment().getProperty("not-e")).isNull();
}
@Test
public void yamlProfileCascadingOverrideProfilesB() {
SpringApplication application = new SpringApplication(Config.class);
application.setWebApplicationType(WebApplicationType.NONE);
String configName = "--spring.config.name=cascadingprofiles";
this.context = application.run(configName, "--spring.profiles.active=B");
assertVersionProperty(this.context, "E", "D", "E", "B");
assertThat(this.context.getEnvironment().getProperty("not-a")).isEqualTo("true");
assertThat(this.context.getEnvironment().getProperty("not-b")).isNull();
assertThat(this.context.getEnvironment().getProperty("not-c")).isEqualTo("true");
assertThat(this.context.getEnvironment().getProperty("not-d")).isNull();
assertThat(this.context.getEnvironment().getProperty("not-e")).isNull();
}
private void assertVersionProperty(ConfigurableApplicationContext context, private void assertVersionProperty(ConfigurableApplicationContext context,
String expectedVersion, String... expectedActiveProfiles) { String expectedVersion, String... expectedActiveProfiles) {
assertThat(context.getEnvironment().getActiveProfiles()) assertThat(context.getEnvironment().getActiveProfiles())
......
/*
* Copyright 2012-2018 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.Properties;
import java.util.function.Predicate;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.config.YamlProcessor.MatchStatus;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
/**
* Tests for {@link AcceptsProfilesDocumentMatcher}.
*
* @author Phillip Webb
*/
public class AcceptsProfilesDocumentMatcherTests {
@Mock
private Predicate<String[]> acceptsProfiles;
private AcceptsProfilesDocumentMatcher matcher;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.matcher = new AcceptsProfilesDocumentMatcher(this.acceptsProfiles);
}
@Test
public void matchesWhenHasNoProfilePropertyShouldReturnAbstain() {
Properties properties = new Properties();
assertThat(this.matcher.matches(properties)).isEqualTo(MatchStatus.ABSTAIN);
}
@Test
public void matchesWhenAcceptsProfileShouldReturnAbstain() {
Properties properties = new Properties();
properties.put("spring.profiles", "foo");
given(this.acceptsProfiles.test(new String[] { "foo" })).willReturn(true);
assertThat(this.matcher.matches(properties)).isEqualTo(MatchStatus.ABSTAIN);
}
@Test
public void matchesWhenDoesNotAcceptProfileShouldReturnNotFound() {
Properties properties = new Properties();
properties.put("spring.profiles", "foo");
given(this.acceptsProfiles.test(new String[] { "foo" })).willReturn(false);
assertThat(this.matcher.matches(properties)).isEqualTo(MatchStatus.NOT_FOUND);
}
}
...@@ -46,7 +46,7 @@ public class NoSnakeYamlPropertySourceLoaderTests { ...@@ -46,7 +46,7 @@ public class NoSnakeYamlPropertySourceLoaderTests {
"Attempted to load resource but snakeyaml was not found on the classpath"); "Attempted to load resource but snakeyaml was not found on the classpath");
ByteArrayResource resource = new ByteArrayResource( ByteArrayResource resource = new ByteArrayResource(
"foo:\n bar: spam".getBytes()); "foo:\n bar: spam".getBytes());
this.loader.load("resource", resource, null, (profile) -> true); this.loader.load("resource", resource);
} }
} }
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package org.springframework.boot.env; package org.springframework.boot.env;
import java.util.List;
import java.util.Map; import java.util.Map;
import org.junit.Before; import org.junit.Before;
...@@ -38,12 +39,12 @@ public class OriginTrackedYamlLoaderTests { ...@@ -38,12 +39,12 @@ public class OriginTrackedYamlLoaderTests {
private OriginTrackedYamlLoader loader; private OriginTrackedYamlLoader loader;
private Map<String, Object> result; private List<Map<String, Object>> result;
@Before @Before
public void setUp() { public void setUp() {
Resource resource = new ClassPathResource("test-yaml.yml", getClass()); Resource resource = new ClassPathResource("test-yaml.yml", getClass());
this.loader = new OriginTrackedYamlLoader(resource, null, (profile) -> true); this.loader = new OriginTrackedYamlLoader(resource);
} }
@Test @Test
...@@ -90,15 +91,6 @@ public class OriginTrackedYamlLoaderTests { ...@@ -90,15 +91,6 @@ public class OriginTrackedYamlLoaderTests {
assertThat(getLocation(education)).isEqualTo("16:12"); assertThat(getLocation(education)).isEqualTo("16:12");
} }
@Test
public void processWithActiveProfile() {
Resource resource = new ClassPathResource("test-yaml.yml", getClass());
this.loader = new OriginTrackedYamlLoader(resource, "development",
(profile) -> true);
Map<String, Object> result = this.loader.load();
assertThat(result.get("name").toString()).isEqualTo("Test Name");
}
@Test @Test
public void processListOfMaps() { public void processListOfMaps() {
OriginTrackedValue name = getValue("example.foo[0].name"); OriginTrackedValue name = getValue("example.foo[0].name");
...@@ -129,7 +121,7 @@ public class OriginTrackedYamlLoaderTests { ...@@ -129,7 +121,7 @@ public class OriginTrackedYamlLoaderTests {
if (this.result == null) { if (this.result == null) {
this.result = this.loader.load(); this.result = this.loader.load();
} }
return (OriginTrackedValue) this.result.get(name); return (OriginTrackedValue) this.result.get(0).get(name);
} }
private String getLocation(OriginTrackedValue value) { private String getLocation(OriginTrackedValue value) {
......
/*
* Copyright 2012-2018 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.Properties;
import org.junit.Test;
import org.springframework.beans.factory.config.YamlProcessor.MatchStatus;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ProfileToLoadDocumentMatcher}.
*
* @author Phillip Webb
*/
public class ProfileToLoadDocumentMatcherTests {
@Test
public void matchesWhenProfilesIsNullAndHasNoProfilePropertiesShouldReturnAbstain() {
ProfileToLoadDocumentMatcher matcher = new ProfileToLoadDocumentMatcher(null);
Properties properties = new Properties();
assertThat(matcher.matches(properties)).isEqualTo(MatchStatus.ABSTAIN);
}
@Test
public void matchesWhenProfileIsNullAndHasOnlyNegativeProfilePropertiesShouldReturnAbstain() {
ProfileToLoadDocumentMatcher matcher = new ProfileToLoadDocumentMatcher(null);
Properties properties = new Properties();
properties.put("spring.profiles", "!foo,!bar");
assertThat(matcher.matches(properties)).isEqualTo(MatchStatus.ABSTAIN);
}
@Test
public void matchesWhenProfileIsNullAndHasProfilePropertyShouldReturnNotFound() {
ProfileToLoadDocumentMatcher matcher = new ProfileToLoadDocumentMatcher(null);
Properties properties = new Properties();
properties.put("spring.profiles", "!foo,!bar,baz");
assertThat(matcher.matches(properties)).isEqualTo(MatchStatus.NOT_FOUND);
}
@Test
public void matchesWhenProfilesIsSetAndHasNoProfilePropertiesShouldReturnNotFound() {
ProfileToLoadDocumentMatcher matcher = new ProfileToLoadDocumentMatcher("bar");
Properties properties = new Properties();
assertThat(matcher.matches(properties)).isEqualTo(MatchStatus.NOT_FOUND);
}
@Test
public void matchesWhenProfileIsSetAndHasOnlyNegativeProfilePropertiesShouldReturnNotFound() {
ProfileToLoadDocumentMatcher matcher = new ProfileToLoadDocumentMatcher("bar");
Properties properties = new Properties();
properties.put("spring.profiles", "!foo,!bar,baz");
assertThat(matcher.matches(properties)).isEqualTo(MatchStatus.NOT_FOUND);
}
@Test
public void matchesWhenProfileIsSetAndHasProfilePropertyShouldReturnAbstain() {
ProfileToLoadDocumentMatcher matcher = new ProfileToLoadDocumentMatcher("bar");
Properties properties = new Properties();
properties.put("spring.profiles", "!foo,bar,baz");
assertThat(matcher.matches(properties)).isEqualTo(MatchStatus.ABSTAIN);
}
}
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
package org.springframework.boot.env; package org.springframework.boot.env;
import java.util.List;
import org.junit.Test; import org.junit.Test;
import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySource;
...@@ -41,17 +43,17 @@ public class PropertiesPropertySourceLoaderTests { ...@@ -41,17 +43,17 @@ public class PropertiesPropertySourceLoaderTests {
@Test @Test
public void loadProperties() throws Exception { public void loadProperties() throws Exception {
PropertySource<?> source = this.loader.load("test.properties", List<PropertySource<?>> loaded = this.loader.load("test.properties",
new ClassPathResource("test-properties.properties", getClass()), null, new ClassPathResource("test-properties.properties", getClass()));
(profile) -> true); PropertySource<?> source = loaded.get(0);
assertThat(source.getProperty("test")).isEqualTo("properties"); assertThat(source.getProperty("test")).isEqualTo("properties");
} }
@Test @Test
public void loadXml() throws Exception { public void loadXml() throws Exception {
PropertySource<?> source = this.loader.load("test.xml", List<PropertySource<?>> loaded = this.loader.load("test.xml",
new ClassPathResource("test-xml.xml", getClass()), null, new ClassPathResource("test-xml.xml", getClass()));
(profile) -> true); PropertySource<?> source = loaded.get(0);
assertThat(source.getProperty("test")).isEqualTo("xml"); assertThat(source.getProperty("test")).isEqualTo("xml");
} }
......
/*
* Copyright 2012-2018 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.Properties;
import org.junit.Test;
import org.springframework.beans.factory.config.YamlProcessor.MatchStatus;
import org.springframework.boot.origin.OriginTrackedValue;
import org.springframework.util.ObjectUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link SpringProfilesDocumentMatcher}.
*
* @author Phillip Webb
*/
public class SpringProfilesDocumentMatcherTests {
private TestSpringProfilesDocumentMatcher matcher = new TestSpringProfilesDocumentMatcher();
@Test
public void matchesShouldBindAgainstCommaList() {
Properties properties = new Properties();
properties.put("spring.profiles", "foo,bar");
this.matcher.matches(properties);
assertThat(this.matcher.getProfiles()).containsExactly("foo", "bar");
}
@Test
public void matchesShouldBindAgainstYamlList() {
Properties properties = new Properties();
properties.put("spring.profiles[0]", "foo");
properties.put("spring.profiles[1]", "bar");
this.matcher.matches(properties);
assertThat(this.matcher.getProfiles()).containsExactly("foo", "bar");
}
@Test
public void matchesShouldBindAgainstOriginTrackedValue() {
Properties properties = new Properties();
properties.put("spring.profiles", OriginTrackedValue.of("foo,bar"));
this.matcher.matches(properties);
assertThat(this.matcher.getProfiles()).containsExactly("foo", "bar");
}
@Test
public void matchesWhenMatchShouldReturnAbstain() {
Properties properties = new Properties();
properties.put("spring.profiles", "foo,bar");
assertThat(this.matcher.matches(properties)).isEqualTo(MatchStatus.ABSTAIN);
}
@Test
public void matchesWhenNoMatchShouldReturnNotFound() {
Properties properties = new Properties();
assertThat(this.matcher.matches(properties)).isEqualTo(MatchStatus.NOT_FOUND);
}
private static class TestSpringProfilesDocumentMatcher
extends SpringProfilesDocumentMatcher {
private String[] profiles;
@Override
protected boolean matches(String[] profiles) {
this.profiles = profiles;
return !ObjectUtils.isEmpty(profiles);
}
public String[] getProfiles() {
return this.profiles;
}
}
}
...@@ -26,7 +26,6 @@ import org.springframework.core.env.PropertySource; ...@@ -26,7 +26,6 @@ import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
...@@ -46,8 +45,7 @@ public class YamlPropertySourceLoaderTests { ...@@ -46,8 +45,7 @@ public class YamlPropertySourceLoaderTests {
public void load() throws Exception { public void load() throws Exception {
ByteArrayResource resource = new ByteArrayResource( ByteArrayResource resource = new ByteArrayResource(
"foo:\n bar: spam".getBytes()); "foo:\n bar: spam".getBytes());
PropertySource<?> source = this.loader.load("resource", resource, null, PropertySource<?> source = this.loader.load("resource", resource).get(0);
(profile) -> true);
assertThat(source).isNotNull(); assertThat(source).isNotNull();
assertThat(source.getProperty("foo.bar")).isEqualTo("spam"); assertThat(source.getProperty("foo.bar")).isEqualTo("spam");
} }
...@@ -62,7 +60,7 @@ public class YamlPropertySourceLoaderTests { ...@@ -62,7 +60,7 @@ public class YamlPropertySourceLoaderTests {
} }
ByteArrayResource resource = new ByteArrayResource(yaml.toString().getBytes()); ByteArrayResource resource = new ByteArrayResource(yaml.toString().getBytes());
EnumerablePropertySource<?> source = (EnumerablePropertySource<?>) this.loader EnumerablePropertySource<?> source = (EnumerablePropertySource<?>) this.loader
.load("resource", resource, null, (profile) -> true); .load("resource", resource).get(0);
assertThat(source).isNotNull(); assertThat(source).isNotNull();
assertThat(source.getPropertyNames()) assertThat(source.getPropertyNames())
.isEqualTo(StringUtils.toStringArray(expected)); .isEqualTo(StringUtils.toStringArray(expected));
...@@ -75,18 +73,16 @@ public class YamlPropertySourceLoaderTests { ...@@ -75,18 +73,16 @@ public class YamlPropertySourceLoaderTests {
yaml.append("---\n"); yaml.append("---\n");
yaml.append("foo:\n baz: wham\n"); yaml.append("foo:\n baz: wham\n");
ByteArrayResource resource = new ByteArrayResource(yaml.toString().getBytes()); ByteArrayResource resource = new ByteArrayResource(yaml.toString().getBytes());
PropertySource<?> source = this.loader.load("resource", resource, null, List<PropertySource<?>> loaded = this.loader.load("resource", resource);
(profile) -> true); assertThat(loaded).hasSize(2);
assertThat(source).isNotNull(); assertThat(loaded.get(0).getProperty("foo.bar")).isEqualTo("spam");
assertThat(source.getProperty("foo.bar")).isEqualTo("spam"); assertThat(loaded.get(1).getProperty("foo.baz")).isEqualTo("wham");
assertThat(source.getProperty("foo.baz")).isEqualTo("wham");
} }
@Test @Test
public void timestampLikeItemsDoNotBecomeDates() throws Exception { public void timestampLikeItemsDoNotBecomeDates() throws Exception {
ByteArrayResource resource = new ByteArrayResource("foo: 2015-01-28".getBytes()); ByteArrayResource resource = new ByteArrayResource("foo: 2015-01-28".getBytes());
PropertySource<?> source = this.loader.load("resource", resource, null, PropertySource<?> source = this.loader.load("resource", resource).get(0);
(profile) -> true);
assertThat(source).isNotNull(); assertThat(source).isNotNull();
assertThat(source.getProperty("foo")).isEqualTo("2015-01-28"); assertThat(source.getProperty("foo")).isEqualTo("2015-01-28");
} }
...@@ -94,42 +90,13 @@ public class YamlPropertySourceLoaderTests { ...@@ -94,42 +90,13 @@ public class YamlPropertySourceLoaderTests {
@Test @Test
public void loadOriginAware() throws Exception { public void loadOriginAware() throws Exception {
Resource resource = new ClassPathResource("test-yaml.yml", getClass()); Resource resource = new ClassPathResource("test-yaml.yml", getClass());
PropertySource<?> source = this.loader.load("resource", resource, null, List<PropertySource<?>> loaded = this.loader.load("resource", resource);
(profile) -> true); for (PropertySource<?> source : loaded) {
EnumerablePropertySource<?> enumerableSource = (EnumerablePropertySource<?>) source; EnumerablePropertySource<?> enumerableSource = (EnumerablePropertySource<?>) source;
for (String name : enumerableSource.getPropertyNames()) { for (String name : enumerableSource.getPropertyNames()) {
System.out.println(name + " = " + enumerableSource.getProperty(name)); System.out.println(name + " = " + enumerableSource.getProperty(name));
}
} }
} }
@Test
public void loadSpecificProfile() throws Exception {
StringBuilder yaml = new StringBuilder();
yaml.append("foo:\n bar: spam\n");
yaml.append("---\n");
yaml.append("spring:\n profiles: foo\n");
yaml.append("foo:\n bar: wham\n");
ByteArrayResource resource = new ByteArrayResource(yaml.toString().getBytes());
PropertySource<?> source = this.loader.load("resource", resource, "foo",
(profile) -> true);
assertThat(source).isNotNull();
assertThat(source.getProperty("foo.bar")).isEqualTo("wham");
}
@Test
public void loadWithAcceptProfile() throws Exception {
StringBuilder yaml = new StringBuilder();
yaml.append("---\n");
yaml.append("spring:\n profiles: yay,foo\n");
yaml.append("foo:\n bar: bang\n");
yaml.append("---\n");
yaml.append("spring:\n profiles: yay,!foo\n");
yaml.append("foo:\n bar: wham\n");
ByteArrayResource resource = new ByteArrayResource(yaml.toString().getBytes());
PropertySource<?> source = this.loader.load("resource", resource, "yay",
(profiles) -> ObjectUtils.containsElement(profiles, "!foo"));
assertThat(source).isNotNull();
assertThat(source.getProperty("foo.bar")).isEqualTo("wham");
}
} }
spring:
profiles:
active:
- A
- B
---
spring.profiles: A
spring:
profiles:
include:
- C
- E
---
spring.profiles: B
spring:
profiles:
include:
- D
- E
---
spring.profiles: E
version: E
---
spring.profiles: "!A"
not-a: true
---
spring.profiles: "!B"
not-b: true
---
spring.profiles: "!C"
not-c: true
---
spring.profiles: "!D"
not-d: true
---
spring.profiles: "!E"
not-e: true
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