From c0d79b92735b46010fb922a214b4cbb20c07fda5 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Mon, 26 Feb 2018 12:11:08 -0800 Subject: [PATCH] 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 --- .../EnvironmentPostProcessorExample.java | 8 +- .../PropertiesMigrationReporterTests.java | 8 +- .../config/ConfigFileApplicationListener.java | 295 ++++++++++++++---- .../env/AcceptsProfilesDocumentMatcher.java | 41 --- .../boot/env/OriginTrackedYamlLoader.java | 17 +- .../env/ProfileToLoadDocumentMatcher.java | 50 --- .../env/PropertiesPropertySourceLoader.java | 18 +- .../boot/env/PropertySourceLoader.java | 18 +- .../env/SpringProfilesDocumentMatcher.java | 77 ----- .../boot/env/YamlPropertySourceLoader.java | 23 +- .../ConfigFileApplicationListenerTests.java | 4 +- ...ationListenerYamlProfileNegationTests.java | 42 +++ .../AcceptsProfilesDocumentMatcherTests.java | 72 ----- .../NoSnakeYamlPropertySourceLoaderTests.java | 2 +- .../env/OriginTrackedYamlLoaderTests.java | 16 +- .../ProfileToLoadDocumentMatcherTests.java | 80 ----- .../PropertiesPropertySourceLoaderTests.java | 14 +- .../SpringProfilesDocumentMatcherTests.java | 93 ------ .../env/YamlPropertySourceLoaderTests.java | 59 +--- .../src/test/resources/cascadingprofiles.yml | 53 ++++ 20 files changed, 412 insertions(+), 578 deletions(-) delete mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/AcceptsProfilesDocumentMatcher.java delete mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/ProfileToLoadDocumentMatcher.java delete mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/SpringProfilesDocumentMatcher.java delete mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/AcceptsProfilesDocumentMatcherTests.java delete mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/ProfileToLoadDocumentMatcherTests.java delete mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/SpringProfilesDocumentMatcherTests.java create mode 100644 spring-boot-project/spring-boot/src/test/resources/cascadingprofiles.yml diff --git a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/EnvironmentPostProcessorExample.java b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/EnvironmentPostProcessorExample.java index 994ad98eaf4..e1bd52dddd4 100644 --- a/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/EnvironmentPostProcessorExample.java +++ b/spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/EnvironmentPostProcessorExample.java @@ -22,7 +22,6 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.boot.env.YamlPropertySourceLoader; import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.Environment; import org.springframework.core.env.PropertySource; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; @@ -41,17 +40,16 @@ public class EnvironmentPostProcessorExample implements EnvironmentPostProcessor public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { Resource path = new ClassPathResource("com/example/myapp/config.yml"); - PropertySource propertySource = loadYaml(path, environment); + PropertySource propertySource = loadYaml(path); environment.getPropertySources().addLast(propertySource); } - private PropertySource loadYaml(Resource path, Environment environment) { + private PropertySource loadYaml(Resource path) { if (!path.exists()) { throw new IllegalArgumentException("Resource " + path + " does not exist"); } try { - return this.loader.load("custom-resource", path, null, - environment::acceptsProfiles); + return this.loader.load("custom-resource", path).get(0); } catch (IOException ex) { throw new IllegalStateException( diff --git a/spring-boot-project/spring-boot-properties-migrator/src/test/java/org/springframework/boot/context/properties/migrator/PropertiesMigrationReporterTests.java b/spring-boot-project/spring-boot-properties-migrator/src/test/java/org/springframework/boot/context/properties/migrator/PropertiesMigrationReporterTests.java index 2df3744e5ab..f57fd750c17 100644 --- a/spring-boot-project/spring-boot-properties-migrator/src/test/java/org/springframework/boot/context/properties/migrator/PropertiesMigrationReporterTests.java +++ b/spring-boot-project/spring-boot-properties-migrator/src/test/java/org/springframework/boot/context/properties/migrator/PropertiesMigrationReporterTests.java @@ -180,10 +180,10 @@ public class PropertiesMigrationReporterTests { private PropertySource loadPropertySource(String name, String path) throws IOException { ClassPathResource resource = new ClassPathResource(path); - PropertySource propertySource = new PropertiesPropertySourceLoader().load(name, - resource, null, (profile) -> true); - assertThat(propertySource).isNotNull(); - return propertySource; + List> propertySources = new PropertiesPropertySourceLoader() + .load(name, resource); + assertThat(propertySources).isNotEmpty(); + return propertySources.get(0); } private ConfigurationMetadataRepository loadRepository(String... content) { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java index 8c6e449d5a5..e7b54495fc4 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; @@ -27,6 +28,8 @@ import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Set; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; import org.apache.commons.logging.Log; @@ -36,6 +39,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; 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.PropertySourcesPlaceholdersResolver; import org.springframework.boot.context.properties.source.ConfigurationPropertySources; @@ -58,6 +62,8 @@ import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; import org.springframework.util.ResourceUtils; import org.springframework.util.StringUtils; @@ -101,6 +107,8 @@ public class ConfigFileApplicationListener private static final String DEFAULT_NAMES = "application"; + private static final Set NO_SEARCH_NAMES = Collections.singleton(null); + /** * The "active profiles" property name. */ @@ -302,6 +310,8 @@ public class ConfigFileApplicationListener private Map loaded; + private Map> loadDocumentsCache = new HashMap<>(); + Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { this.environment = environment; this.resourceLoader = resourceLoader == null ? new DefaultResourceLoader() @@ -318,20 +328,12 @@ public class ConfigFileApplicationListener 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); - } - } - } + load(profile, this::getPositiveProfileFilter, + addToLoaded(MutablePropertySources::addLast, false)); this.processedProfiles.add(profile); } + load(null, this::getNegativeProfileFilter, + addToLoaded(MutablePropertySources::addFirst, true)); addLoadedPropertySources(); } @@ -399,24 +401,66 @@ public class ConfigFileApplicationListener return unprocessedActiveProfiles; } - /** - * 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) { + private DocumentFilter getPositiveProfileFilter(Profile profile) { + return (Document document) -> { + if (profile == null) { + return ObjectUtils.isEmpty(document.getProfiles()); + } + return ObjectUtils.containsElement(document.getProfiles(), + 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> 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 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)) { for (PropertySourceLoader loader : this.propertySourceLoaders) { if (canLoadFileExtension(loader, location)) { - load(loader, profile, location, - (profile == null ? null : profile.getName())); + load(loader, location, profile, + filterFactory.getDocumentFilter(profile), consumer); } } } for (PropertySourceLoader loader : this.propertySourceLoaders) { - for (String ext : loader.getFileExtensions()) { - loadForFileExtension(loader, profile, location + name, "." + ext); + for (String fileExtension : loader.getFileExtensions()) { + String prefix = location + name; + fileExtension = "." + fileExtension; + loadForFileExtension(loader, prefix, fileExtension, profile, + filterFactory, consumer); } } } @@ -427,28 +471,31 @@ public class ConfigFileApplicationListener fileExtension)); } - private void loadForFileExtension(PropertySourceLoader loader, Profile profile, - String prefix, String ext) { + private void loadForFileExtension(PropertySourceLoader loader, String prefix, + String fileExtension, Profile profile, + DocumentFilterFactory filterFactory, DocumentConsumer consumer) { + DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null); + DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile); 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 file & profile section in profile file (gh-340) + String profileSpecificFile = prefix + "-" + profile + fileExtension; + load(loader, profileSpecificFile, profile, defaultFilter, consumer); + load(loader, profileSpecificFile, profile, profileFilter, consumer); // 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()); + String previouslyLoaded = prefix + "-" + processedProfile + + fileExtension; + load(loader, previouslyLoaded, profile, profileFilter, consumer); } } } // Also try the profile-specific section (if any) of the normal file - load(loader, profile, prefix + ext, - (profile == null ? null : profile.getName())); + load(loader, prefix + fileExtension, profile, profileFilter, consumer); } - private void load(PropertySourceLoader loader, Profile profile, String location, - String loadProfile) { + private void load(PropertySourceLoader loader, String location, Profile profile, + DocumentFilter filter, DocumentConsumer consumer) { try { Resource resource = this.resourceLoader.getResource(location); String description = getDescription(location, resource); @@ -464,18 +511,25 @@ public class ConfigFileApplicationListener this.logger.trace("Skipped empty config extension " + description); return; } - String name = "applicationConfig: [" + location + "]" - + (loadProfile == null ? "" : "#" + loadProfile); - PropertySource loaded = loader.load(name, resource, loadProfile, - this.environment::acceptsProfiles); - if (loaded == null) { + String name = "applicationConfig: [" + location + "]"; + List documents = loadDocuments(loader, name, resource); + if (CollectionUtils.isEmpty(documents)) { 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); + List loaded = new ArrayList<>(); + for (Document document : documents) { + if (filter.match(document)) { + 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) { throw new IllegalStateException("Failed to load property " @@ -483,6 +537,34 @@ public class ConfigFileApplicationListener } } + private List loadDocuments(PropertySourceLoader loader, String name, + Resource resource) throws IOException { + loader.load(name, resource); + DocumentsCacheKey cacheKey = new DocumentsCacheKey(loader, resource); + List documents = this.loadDocumentsCache.get(cacheKey); + if (documents == null) { + List> loaded = loader.load(name, resource); + documents = asDocuments(loaded); + } + return documents; + } + + private List asDocuments(List> 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) { try { if (resource != null) { @@ -495,15 +577,6 @@ public class ConfigFileApplicationListener return String.format("'%s'", location); } - private void handleProfileProperties(PropertySource propertySource) { - Binder binder = new Binder(ConfigurationPropertySources.from(propertySource), - new PropertySourcesPlaceholdersResolver(this.environment)); - Set active = getProfiles(binder, "spring.profiles.active"); - Set include = getProfiles(binder, "spring.profiles.include"); - maybeActivateProfiles(active); - addProfiles(include); - } - private Set getProfiles(Binder binder, String name) { return binder.bind(name, String[].class).map(this::asProfileSet) .orElse(Collections.emptySet()); @@ -638,6 +711,9 @@ public class ConfigFileApplicationListener } + /** + * A Spring Profile that can be loaded. + */ private static class Profile { private final String name; @@ -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 activeProfiles; + + private final Set includeProfiles; + + Document(PropertySource propertySource, String[] profiles, + Set activeProfiles, Set 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 getActiveProfiles() { + return this.activeProfiles; + } + + public Set 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); + + } + } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/AcceptsProfilesDocumentMatcher.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/AcceptsProfilesDocumentMatcher.java deleted file mode 100644 index 6d2713b926c..00000000000 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/AcceptsProfilesDocumentMatcher.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 acceptsProfiles; - - AcceptsProfilesDocumentMatcher(Predicate acceptsProfiles) { - this.acceptsProfiles = acceptsProfiles; - } - - @Override - protected boolean matches(String[] profiles) { - return ObjectUtils.isEmpty(profiles) || this.acceptsProfiles.test(profiles); - } - -} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/OriginTrackedYamlLoader.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/OriginTrackedYamlLoader.java index c679d5c343d..7606cae8464 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/OriginTrackedYamlLoader.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/OriginTrackedYamlLoader.java @@ -16,9 +16,9 @@ package org.springframework.boot.env; -import java.util.LinkedHashMap; +import java.util.ArrayList; +import java.util.List; import java.util.Map; -import java.util.function.Predicate; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -53,11 +53,8 @@ class OriginTrackedYamlLoader extends YamlProcessor { private final Resource resource; - OriginTrackedYamlLoader(Resource resource, String profileToLoad, - Predicate acceptsProfiles) { + OriginTrackedYamlLoader(Resource resource) { this.resource = resource; - setDocumentMatchers(new ProfileToLoadDocumentMatcher(profileToLoad), - new AcceptsProfilesDocumentMatcher(acceptsProfiles)); setResources(resource); } @@ -70,9 +67,11 @@ class OriginTrackedYamlLoader extends YamlProcessor { return new Yaml(constructor, representer, dumperOptions, resolver); } - public Map load() { - final Map result = new LinkedHashMap<>(); - process((properties, map) -> result.putAll(getFlattenedMap(map))); + public List> load() { + final List> result = new ArrayList<>(); + process((properties, map) -> { + result.add(getFlattenedMap(map)); + }); return result; } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/ProfileToLoadDocumentMatcher.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/ProfileToLoadDocumentMatcher.java deleted file mode 100644 index 02501085893..00000000000 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/ProfileToLoadDocumentMatcher.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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("!"); - } - -} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/PropertiesPropertySourceLoader.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/PropertiesPropertySourceLoader.java index 3af328f8f3e..fd8f228f226 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/PropertiesPropertySourceLoader.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/PropertiesPropertySourceLoader.java @@ -17,8 +17,9 @@ package org.springframework.boot.env; import java.io.IOException; +import java.util.Collections; +import java.util.List; import java.util.Map; -import java.util.function.Predicate; import org.springframework.core.env.PropertySource; import org.springframework.core.io.Resource; @@ -41,15 +42,14 @@ public class PropertiesPropertySourceLoader implements PropertySourceLoader { } @Override - public PropertySource load(String name, Resource resource, String profileToLoad, - Predicate acceptsProfiles) throws IOException { - if (profileToLoad == null) { - Map properties = loadProperties(resource); - if (!properties.isEmpty()) { - return new OriginTrackedMapPropertySource(name, properties); - } + public List> load(String name, Resource resource) + throws IOException { + Map properties = loadProperties(resource); + if (properties.isEmpty()) { + return Collections.emptyList(); } - return null; + return Collections + .singletonList(new OriginTrackedMapPropertySource(name, properties)); } @SuppressWarnings({ "unchecked", "rawtypes" }) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/PropertySourceLoader.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/PropertySourceLoader.java index 8b912318e8c..09ea77e86e6 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/PropertySourceLoader.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/PropertySourceLoader.java @@ -17,7 +17,7 @@ package org.springframework.boot.env; import java.io.IOException; -import java.util.function.Predicate; +import java.util.List; import org.springframework.core.env.PropertySource; import org.springframework.core.io.Resource; @@ -39,17 +39,15 @@ public interface PropertySourceLoader { String[] getFileExtensions(); /** - * Load the resource into a property source. - * @param name the name of the property source + * Load the resource into one or more property sources. Implementations may either + * 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 profileToLoad the name of the profile to load or {@code null}. The profile - * 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} + * @return a list property sources * @throws IOException if the source cannot be loaded */ - PropertySource load(String name, Resource resource, String profileToLoad, - Predicate acceptsProfiles) throws IOException; + List> load(String name, Resource resource) throws IOException; } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/SpringProfilesDocumentMatcher.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/SpringProfilesDocumentMatcher.java deleted file mode 100644 index e70f4fd1a75..00000000000 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/SpringProfilesDocumentMatcher.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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; - } - - } - -} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/YamlPropertySourceLoader.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/YamlPropertySourceLoader.java index 7c5c201a269..7cb0b418f99 100755 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/YamlPropertySourceLoader.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/YamlPropertySourceLoader.java @@ -17,8 +17,10 @@ package org.springframework.boot.env; import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Map; -import java.util.function.Predicate; import org.springframework.core.env.PropertySource; import org.springframework.core.io.Resource; @@ -39,18 +41,23 @@ public class YamlPropertySourceLoader implements PropertySourceLoader { } @Override - public PropertySource load(String name, Resource resource, String profileToLoad, - Predicate acceptsProfiles) throws IOException { + public List> load(String name, Resource resource) + throws IOException { if (!ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", null)) { throw new IllegalStateException("Attempted to load " + name + " but snakeyaml was not found on the classpath"); } - Map source = new OriginTrackedYamlLoader(resource, profileToLoad, - acceptsProfiles).load(); - if (!source.isEmpty()) { - return new OriginTrackedMapPropertySource(name, source); + List> loaded = new OriginTrackedYamlLoader(resource).load(); + if (loaded.isEmpty()) { + return Collections.emptyList(); } - return null; + List> 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; } } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigFileApplicationListenerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigFileApplicationListenerTests.java index e58daeb8975..37b064cdf27 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigFileApplicationListenerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigFileApplicationListenerTests.java @@ -540,8 +540,8 @@ public class ConfigFileApplicationListenerTests { .map(org.springframework.core.env.PropertySource::getName) .collect(Collectors.toList()); assertThat(names).contains( - "applicationConfig: [classpath:/testsetprofiles.yml]#dev", - "applicationConfig: [classpath:/testsetprofiles.yml]"); + "applicationConfig: [classpath:/testsetprofiles.yml] (document #0)", + "applicationConfig: [classpath:/testsetprofiles.yml] (document #1)"); } @Test diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigFileApplicationListenerYamlProfileNegationTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigFileApplicationListenerYamlProfileNegationTests.java index a228a9ba2cf..6f3835fb77b 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigFileApplicationListenerYamlProfileNegationTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigFileApplicationListenerYamlProfileNegationTests.java @@ -88,6 +88,48 @@ public class ConfigFileApplicationListenerYamlProfileNegationTests { 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, String expectedVersion, String... expectedActiveProfiles) { assertThat(context.getEnvironment().getActiveProfiles()) diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/AcceptsProfilesDocumentMatcherTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/AcceptsProfilesDocumentMatcherTests.java deleted file mode 100644 index d8c78c1faa1..00000000000 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/AcceptsProfilesDocumentMatcherTests.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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 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); - } - -} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/NoSnakeYamlPropertySourceLoaderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/NoSnakeYamlPropertySourceLoaderTests.java index 0b9ef378914..ecab68d95fe 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/NoSnakeYamlPropertySourceLoaderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/NoSnakeYamlPropertySourceLoaderTests.java @@ -46,7 +46,7 @@ public class NoSnakeYamlPropertySourceLoaderTests { "Attempted to load resource but snakeyaml was not found on the classpath"); ByteArrayResource resource = new ByteArrayResource( "foo:\n bar: spam".getBytes()); - this.loader.load("resource", resource, null, (profile) -> true); + this.loader.load("resource", resource); } } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/OriginTrackedYamlLoaderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/OriginTrackedYamlLoaderTests.java index d7a30fc3c08..318c5be09e4 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/OriginTrackedYamlLoaderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/OriginTrackedYamlLoaderTests.java @@ -16,6 +16,7 @@ package org.springframework.boot.env; +import java.util.List; import java.util.Map; import org.junit.Before; @@ -38,12 +39,12 @@ public class OriginTrackedYamlLoaderTests { private OriginTrackedYamlLoader loader; - private Map result; + private List> result; @Before public void setUp() { Resource resource = new ClassPathResource("test-yaml.yml", getClass()); - this.loader = new OriginTrackedYamlLoader(resource, null, (profile) -> true); + this.loader = new OriginTrackedYamlLoader(resource); } @Test @@ -90,15 +91,6 @@ public class OriginTrackedYamlLoaderTests { 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 result = this.loader.load(); - assertThat(result.get("name").toString()).isEqualTo("Test Name"); - } - @Test public void processListOfMaps() { OriginTrackedValue name = getValue("example.foo[0].name"); @@ -129,7 +121,7 @@ public class OriginTrackedYamlLoaderTests { if (this.result == null) { this.result = this.loader.load(); } - return (OriginTrackedValue) this.result.get(name); + return (OriginTrackedValue) this.result.get(0).get(name); } private String getLocation(OriginTrackedValue value) { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/ProfileToLoadDocumentMatcherTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/ProfileToLoadDocumentMatcherTests.java deleted file mode 100644 index 66f1763c259..00000000000 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/ProfileToLoadDocumentMatcherTests.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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); - } - -} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/PropertiesPropertySourceLoaderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/PropertiesPropertySourceLoaderTests.java index 1617ab77894..54031233825 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/PropertiesPropertySourceLoaderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/PropertiesPropertySourceLoaderTests.java @@ -16,6 +16,8 @@ package org.springframework.boot.env; +import java.util.List; + import org.junit.Test; import org.springframework.core.env.PropertySource; @@ -41,17 +43,17 @@ public class PropertiesPropertySourceLoaderTests { @Test public void loadProperties() throws Exception { - PropertySource source = this.loader.load("test.properties", - new ClassPathResource("test-properties.properties", getClass()), null, - (profile) -> true); + List> loaded = this.loader.load("test.properties", + new ClassPathResource("test-properties.properties", getClass())); + PropertySource source = loaded.get(0); assertThat(source.getProperty("test")).isEqualTo("properties"); } @Test public void loadXml() throws Exception { - PropertySource source = this.loader.load("test.xml", - new ClassPathResource("test-xml.xml", getClass()), null, - (profile) -> true); + List> loaded = this.loader.load("test.xml", + new ClassPathResource("test-xml.xml", getClass())); + PropertySource source = loaded.get(0); assertThat(source.getProperty("test")).isEqualTo("xml"); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/SpringProfilesDocumentMatcherTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/SpringProfilesDocumentMatcherTests.java deleted file mode 100644 index b49e099bbcb..00000000000 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/SpringProfilesDocumentMatcherTests.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * 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; - } - - } - -} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/YamlPropertySourceLoaderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/YamlPropertySourceLoaderTests.java index 2fb76b6b722..bc1b22e7b13 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/YamlPropertySourceLoaderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/env/YamlPropertySourceLoaderTests.java @@ -26,7 +26,6 @@ import org.springframework.core.env.PropertySource; import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; -import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -46,8 +45,7 @@ public class YamlPropertySourceLoaderTests { public void load() throws Exception { ByteArrayResource resource = new ByteArrayResource( "foo:\n bar: spam".getBytes()); - PropertySource source = this.loader.load("resource", resource, null, - (profile) -> true); + PropertySource source = this.loader.load("resource", resource).get(0); assertThat(source).isNotNull(); assertThat(source.getProperty("foo.bar")).isEqualTo("spam"); } @@ -62,7 +60,7 @@ public class YamlPropertySourceLoaderTests { } ByteArrayResource resource = new ByteArrayResource(yaml.toString().getBytes()); EnumerablePropertySource source = (EnumerablePropertySource) this.loader - .load("resource", resource, null, (profile) -> true); + .load("resource", resource).get(0); assertThat(source).isNotNull(); assertThat(source.getPropertyNames()) .isEqualTo(StringUtils.toStringArray(expected)); @@ -75,18 +73,16 @@ public class YamlPropertySourceLoaderTests { yaml.append("---\n"); yaml.append("foo:\n baz: wham\n"); ByteArrayResource resource = new ByteArrayResource(yaml.toString().getBytes()); - PropertySource source = this.loader.load("resource", resource, null, - (profile) -> true); - assertThat(source).isNotNull(); - assertThat(source.getProperty("foo.bar")).isEqualTo("spam"); - assertThat(source.getProperty("foo.baz")).isEqualTo("wham"); + List> loaded = this.loader.load("resource", resource); + assertThat(loaded).hasSize(2); + assertThat(loaded.get(0).getProperty("foo.bar")).isEqualTo("spam"); + assertThat(loaded.get(1).getProperty("foo.baz")).isEqualTo("wham"); } @Test public void timestampLikeItemsDoNotBecomeDates() throws Exception { ByteArrayResource resource = new ByteArrayResource("foo: 2015-01-28".getBytes()); - PropertySource source = this.loader.load("resource", resource, null, - (profile) -> true); + PropertySource source = this.loader.load("resource", resource).get(0); assertThat(source).isNotNull(); assertThat(source.getProperty("foo")).isEqualTo("2015-01-28"); } @@ -94,42 +90,13 @@ public class YamlPropertySourceLoaderTests { @Test public void loadOriginAware() throws Exception { Resource resource = new ClassPathResource("test-yaml.yml", getClass()); - PropertySource source = this.loader.load("resource", resource, null, - (profile) -> true); - EnumerablePropertySource enumerableSource = (EnumerablePropertySource) source; - for (String name : enumerableSource.getPropertyNames()) { - System.out.println(name + " = " + enumerableSource.getProperty(name)); + List> loaded = this.loader.load("resource", resource); + for (PropertySource source : loaded) { + EnumerablePropertySource enumerableSource = (EnumerablePropertySource) source; + for (String name : enumerableSource.getPropertyNames()) { + 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"); - } - } diff --git a/spring-boot-project/spring-boot/src/test/resources/cascadingprofiles.yml b/spring-boot-project/spring-boot/src/test/resources/cascadingprofiles.yml new file mode 100644 index 00000000000..43982554ed3 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/cascadingprofiles.yml @@ -0,0 +1,53 @@ +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