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 669d2330b1..960ac00ef6 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 @@ -56,7 +56,6 @@ import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; -import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.Profiles; import org.springframework.core.env.PropertySource; @@ -117,6 +116,15 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor, private static final Bindable> STRING_LIST = Bindable.listOf(String.class); + private static final Set LOAD_FILTERED_PROPERTY; + + static { + Set filteredProperties = new HashSet<>(); + filteredProperties.add("spring.profiles.active"); + filteredProperties.add("spring.profiles.include"); + LOAD_FILTERED_PROPERTY = Collections.unmodifiableSet(filteredProperties); + } + /** * The "active profiles" property name. */ @@ -311,32 +319,26 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor, } public void load() { - this.profiles = new LinkedList<>(); - this.processedProfiles = new LinkedList<>(); - this.activatedProfiles = false; - this.loaded = new LinkedHashMap<>(); - MapPropertySource defaultProperties = (MapPropertySource) this.environment.getPropertySources() - .get(DEFAULT_PROPERTIES); - replaceDefaultPropertySourceIfNecessary(defaultProperties); - initializeProfiles(); - while (!this.profiles.isEmpty()) { - Profile profile = this.profiles.poll(); - if (profile != null && !profile.isDefaultProfile()) { - addProfileToEnvironment(profile.getName()); - } - load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false)); - this.processedProfiles.add(profile); - } - load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true)); - addLoadedPropertySources(); - resetEnvironment(defaultProperties); - } - - private void replaceDefaultPropertySourceIfNecessary(MapPropertySource defaultProperties) { - if (defaultProperties != null) { - this.environment.getPropertySources().replace(DEFAULT_PROPERTIES, - new FilteredDefaultPropertySource(DEFAULT_PROPERTIES, defaultProperties.getSource())); - } + FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY, + (defaultProperties) -> { + this.profiles = new LinkedList<>(); + this.processedProfiles = new LinkedList<>(); + this.activatedProfiles = false; + this.loaded = new LinkedHashMap<>(); + initializeProfiles(); + while (!this.profiles.isEmpty()) { + Profile profile = this.profiles.poll(); + if (isDefaultProfile(profile)) { + addProfileToEnvironment(profile.getName()); + } + load(profile, this::getPositiveProfileFilter, + addToLoaded(MutablePropertySources::addLast, false)); + this.processedProfiles.add(profile); + } + load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true)); + addLoadedPropertySources(); + applyActiveProfiles(defaultProperties); + }); } /** @@ -688,27 +690,23 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor, } } - private void resetEnvironment(MapPropertySource defaultProperties) { + private void applyActiveProfiles(PropertySource defaultProperties) { List activeProfiles = new ArrayList<>(); - handleDefaultPropertySource(defaultProperties, activeProfiles); - activeProfiles.addAll( - this.processedProfiles.stream().filter((profile) -> profile != null && !profile.isDefaultProfile()) - .map(Profile::getName).collect(Collectors.toList())); - this.environment.setActiveProfiles(activeProfiles.toArray(new String[0])); - } - - private void handleDefaultPropertySource(MapPropertySource defaultProperties, List activeProfiles) { if (defaultProperties != null) { Binder binder = new Binder(ConfigurationPropertySources.from(defaultProperties), new PropertySourcesPlaceholdersResolver(this.environment)); - List includes = getDefaultProfiles(binder, "spring.profiles.include"); - activeProfiles.addAll(includes); + activeProfiles.addAll(getDefaultProfiles(binder, "spring.profiles.include")); if (!this.activatedProfiles) { - List active = getDefaultProfiles(binder, "spring.profiles.active"); - activeProfiles.addAll(active); + activeProfiles.addAll(getDefaultProfiles(binder, "spring.profiles.active")); } - this.environment.getPropertySources().replace(DEFAULT_PROPERTIES, defaultProperties); } + this.processedProfiles.stream().filter(this::isDefaultProfile).map(Profile::getName) + .forEach(activeProfiles::add); + this.environment.setActiveProfiles(activeProfiles.toArray(new String[0])); + } + + private boolean isDefaultProfile(Profile profile) { + return profile != null && !profile.isDefaultProfile(); } private List getDefaultProfiles(Binder binder, String property) { @@ -717,43 +715,6 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor, } - private static class FilteredDefaultPropertySource extends MapPropertySource { - - private static final List FILTERED_PROPERTY = Arrays.asList("spring.profiles.active", - "spring.profiles.include"); - - FilteredDefaultPropertySource(String name, Map source) { - super(name, source); - } - - @Override - public Object getProperty(String name) { - if (isFilteredProperty(name)) { - return null; - } - return super.getProperty(name); - } - - @Override - public boolean containsProperty(String name) { - if (isFilteredProperty(name)) { - return false; - } - return super.containsProperty(name); - } - - @Override - public String[] getPropertyNames() { - return Arrays.stream(super.getPropertyNames()).filter((name) -> !isFilteredProperty(name)) - .toArray(String[]::new); - } - - protected boolean isFilteredProperty(String name) { - return FILTERED_PROPERTY.contains(name); - } - - } - /** * A Spring Profile that can be loaded. */ diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/FilteredPropertySource.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/FilteredPropertySource.java new file mode 100644 index 0000000000..7887ebed5b --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/FilteredPropertySource.java @@ -0,0 +1,66 @@ +/* + * 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 + * + * https://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.context.config; + +import java.util.Set; +import java.util.function.Consumer; + +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.PropertySource; + +/** + * Internal {@link PropertySource} implementation used by + * {@link ConfigFileApplicationListener} to filter out properties for specific operations. + * + * @author Phillip Webb + */ +class FilteredPropertySource extends PropertySource> { + + private final Set filteredProperties; + + FilteredPropertySource(PropertySource original, Set filteredProperties) { + super(original.getName(), original); + this.filteredProperties = filteredProperties; + } + + @Override + public Object getProperty(String name) { + if (this.filteredProperties.contains(name)) { + return null; + } + return getSource().getProperty(name); + } + + static void apply(ConfigurableEnvironment environment, String propertySourceName, Set filteredProperties, + Consumer> operation) { + MutablePropertySources propertySources = environment.getPropertySources(); + PropertySource original = propertySources.get(propertySourceName); + if (original == null) { + operation.accept(null); + return; + } + propertySources.replace(propertySourceName, new FilteredPropertySource(original, filteredProperties)); + try { + operation.accept(original); + } + finally { + propertySources.replace(propertySourceName, original); + } + } + +} 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 65d5909c36..22c727c59f 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 @@ -960,6 +960,29 @@ class ConfigFileApplicationListenerTests { assertThat(this.environment.getProperty("customloader1")).isEqualTo("true"); } + @Test + public void customDefaultPropertySourceIsNotReplaced() { + // gh-17011 + Map source = new HashMap<>(); + source.put("mapkey", "mapvalue"); + MapPropertySource propertySource = new MapPropertySource("defaultProperties", source) { + + @Override + public Object getProperty(String name) { + if ("spring.config.name".equals(name)) { + return "gh17001"; + } + return super.getProperty(name); + } + + }; + this.environment.getPropertySources().addFirst(propertySource); + this.initializer.setSearchNames("testactiveprofiles"); + this.initializer.postProcessEnvironment(this.environment, this.application); + assertThat(this.environment.getProperty("mapkey")).isEqualTo("mapvalue"); + assertThat(this.environment.getProperty("gh17001loaded")).isEqualTo("true"); + } + private Condition matchingPropertySource(final String sourceName) { return new Condition("environment containing property source " + sourceName) { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/FilteredPropertySourceTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/FilteredPropertySourceTests.java new file mode 100644 index 0000000000..44da672c13 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/FilteredPropertySourceTests.java @@ -0,0 +1,103 @@ +/* + * 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 + * + * https://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.context.config; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Consumer; + +import org.junit.jupiter.api.Test; + +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.PropertySource; +import org.springframework.mock.env.MockEnvironment; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link FilteredPropertySource}. + * + * @author Phillip Webb + */ +class FilteredPropertySourceTests { + + @Test + void applyWhenHasNoSourceShouldRunOperation() { + ConfigurableEnvironment environment = new MockEnvironment(); + TestOperation operation = new TestOperation(); + FilteredPropertySource.apply(environment, "test", Collections.emptySet(), operation); + assertThat(operation.isCalled()).isTrue(); + assertThat(operation.getOriginal()).isNull(); + } + + @Test + void applyWhenHasSourceShouldRunWithReplacedSource() { + ConfigurableEnvironment environment = new MockEnvironment(); + Map map = new LinkedHashMap<>(); + map.put("regular", "regularValue"); + map.put("filtered", "filteredValue"); + PropertySource propertySource = new MapPropertySource("test", map); + environment.getPropertySources().addFirst(propertySource); + TestOperation operation = new TestOperation(() -> { + assertThat(environment.containsProperty("regular")).isTrue(); + assertThat(environment.containsProperty("filtered")).isFalse(); + }); + FilteredPropertySource.apply(environment, "test", Collections.singleton("filtered"), operation); + assertThat(operation.isCalled()).isTrue(); + assertThat(operation.getOriginal()).isSameAs(propertySource); + assertThat(environment.getPropertySources().get("test")).isSameAs(propertySource); + + } + + private static class TestOperation implements Consumer> { + + private boolean called; + + private PropertySource original; + + private Runnable operation; + + TestOperation() { + this(null); + } + + TestOperation(Runnable operation) { + this.operation = operation; + } + + @Override + public void accept(PropertySource original) { + this.called = true; + this.original = original; + if (this.operation != null) { + this.operation.run(); + } + } + + public boolean isCalled() { + return this.called; + } + + public PropertySource getOriginal() { + return this.original; + } + + } + +} diff --git a/spring-boot-project/spring-boot/src/test/resources/gh17001.properties b/spring-boot-project/spring-boot/src/test/resources/gh17001.properties new file mode 100644 index 0000000000..b61799d2cd --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/gh17001.properties @@ -0,0 +1 @@ +gh17001loaded=true