Skip to content

Commit 8d44e31

Browse files
committed
Fix composite property source filtering
Update `ConfigFileApplicationListener` so that property filtering works against the original `PropertySource`, rather than the underling `Map`. Prior to this commit, it was impossible for a `CompositePropertySource` to be used as the `defaultPropertySource`. Closes gh-17011
1 parent 75e45fd commit 8d44e31

File tree

5 files changed

+232
-78
lines changed

5 files changed

+232
-78
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java

Lines changed: 39 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@
5656
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
5757
import org.springframework.core.env.ConfigurableEnvironment;
5858
import org.springframework.core.env.Environment;
59-
import org.springframework.core.env.MapPropertySource;
6059
import org.springframework.core.env.MutablePropertySources;
6160
import org.springframework.core.env.Profiles;
6261
import org.springframework.core.env.PropertySource;
@@ -117,6 +116,15 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
117116

118117
private static final Bindable<List<String>> STRING_LIST = Bindable.listOf(String.class);
119118

119+
private static final Set<String> LOAD_FILTERED_PROPERTY;
120+
121+
static {
122+
Set<String> filteredProperties = new HashSet<>();
123+
filteredProperties.add("spring.profiles.active");
124+
filteredProperties.add("spring.profiles.include");
125+
LOAD_FILTERED_PROPERTY = Collections.unmodifiableSet(filteredProperties);
126+
}
127+
120128
/**
121129
* The "active profiles" property name.
122130
*/
@@ -311,32 +319,26 @@ private class Loader {
311319
}
312320

313321
public void load() {
314-
this.profiles = new LinkedList<>();
315-
this.processedProfiles = new LinkedList<>();
316-
this.activatedProfiles = false;
317-
this.loaded = new LinkedHashMap<>();
318-
MapPropertySource defaultProperties = (MapPropertySource) this.environment.getPropertySources()
319-
.get(DEFAULT_PROPERTIES);
320-
replaceDefaultPropertySourceIfNecessary(defaultProperties);
321-
initializeProfiles();
322-
while (!this.profiles.isEmpty()) {
323-
Profile profile = this.profiles.poll();
324-
if (profile != null && !profile.isDefaultProfile()) {
325-
addProfileToEnvironment(profile.getName());
326-
}
327-
load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false));
328-
this.processedProfiles.add(profile);
329-
}
330-
load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
331-
addLoadedPropertySources();
332-
resetEnvironment(defaultProperties);
333-
}
334-
335-
private void replaceDefaultPropertySourceIfNecessary(MapPropertySource defaultProperties) {
336-
if (defaultProperties != null) {
337-
this.environment.getPropertySources().replace(DEFAULT_PROPERTIES,
338-
new FilteredDefaultPropertySource(DEFAULT_PROPERTIES, defaultProperties.getSource()));
339-
}
322+
FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
323+
(defaultProperties) -> {
324+
this.profiles = new LinkedList<>();
325+
this.processedProfiles = new LinkedList<>();
326+
this.activatedProfiles = false;
327+
this.loaded = new LinkedHashMap<>();
328+
initializeProfiles();
329+
while (!this.profiles.isEmpty()) {
330+
Profile profile = this.profiles.poll();
331+
if (isDefaultProfile(profile)) {
332+
addProfileToEnvironment(profile.getName());
333+
}
334+
load(profile, this::getPositiveProfileFilter,
335+
addToLoaded(MutablePropertySources::addLast, false));
336+
this.processedProfiles.add(profile);
337+
}
338+
load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
339+
addLoadedPropertySources();
340+
applyActiveProfiles(defaultProperties);
341+
});
340342
}
341343

342344
/**
@@ -688,68 +690,27 @@ private void addLoadedPropertySource(MutablePropertySources destination, String
688690
}
689691
}
690692

691-
private void resetEnvironment(MapPropertySource defaultProperties) {
693+
private void applyActiveProfiles(PropertySource<?> defaultProperties) {
692694
List<String> activeProfiles = new ArrayList<>();
693-
handleDefaultPropertySource(defaultProperties, activeProfiles);
694-
activeProfiles.addAll(
695-
this.processedProfiles.stream().filter((profile) -> profile != null && !profile.isDefaultProfile())
696-
.map(Profile::getName).collect(Collectors.toList()));
697-
this.environment.setActiveProfiles(activeProfiles.toArray(new String[0]));
698-
}
699-
700-
private void handleDefaultPropertySource(MapPropertySource defaultProperties, List<String> activeProfiles) {
701695
if (defaultProperties != null) {
702696
Binder binder = new Binder(ConfigurationPropertySources.from(defaultProperties),
703697
new PropertySourcesPlaceholdersResolver(this.environment));
704-
List<String> includes = getDefaultProfiles(binder, "spring.profiles.include");
705-
activeProfiles.addAll(includes);
698+
activeProfiles.addAll(getDefaultProfiles(binder, "spring.profiles.include"));
706699
if (!this.activatedProfiles) {
707-
List<String> active = getDefaultProfiles(binder, "spring.profiles.active");
708-
activeProfiles.addAll(active);
700+
activeProfiles.addAll(getDefaultProfiles(binder, "spring.profiles.active"));
709701
}
710-
this.environment.getPropertySources().replace(DEFAULT_PROPERTIES, defaultProperties);
711-
}
712-
}
713-
714-
private List<String> getDefaultProfiles(Binder binder, String property) {
715-
return binder.bind(property, STRING_LIST).orElse(Collections.emptyList());
716-
}
717-
718-
}
719-
720-
private static class FilteredDefaultPropertySource extends MapPropertySource {
721-
722-
private static final List<String> FILTERED_PROPERTY = Arrays.asList("spring.profiles.active",
723-
"spring.profiles.include");
724-
725-
FilteredDefaultPropertySource(String name, Map<String, Object> source) {
726-
super(name, source);
727-
}
728-
729-
@Override
730-
public Object getProperty(String name) {
731-
if (isFilteredProperty(name)) {
732-
return null;
733702
}
734-
return super.getProperty(name);
735-
}
736-
737-
@Override
738-
public boolean containsProperty(String name) {
739-
if (isFilteredProperty(name)) {
740-
return false;
741-
}
742-
return super.containsProperty(name);
703+
this.processedProfiles.stream().filter(this::isDefaultProfile).map(Profile::getName)
704+
.forEach(activeProfiles::add);
705+
this.environment.setActiveProfiles(activeProfiles.toArray(new String[0]));
743706
}
744707

745-
@Override
746-
public String[] getPropertyNames() {
747-
return Arrays.stream(super.getPropertyNames()).filter((name) -> !isFilteredProperty(name))
748-
.toArray(String[]::new);
708+
private boolean isDefaultProfile(Profile profile) {
709+
return profile != null && !profile.isDefaultProfile();
749710
}
750711

751-
protected boolean isFilteredProperty(String name) {
752-
return FILTERED_PROPERTY.contains(name);
712+
private List<String> getDefaultProfiles(Binder binder, String property) {
713+
return binder.bind(property, STRING_LIST).orElse(Collections.emptyList());
753714
}
754715

755716
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2012-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.context.config;
18+
19+
import java.util.Set;
20+
import java.util.function.Consumer;
21+
22+
import org.springframework.core.env.ConfigurableEnvironment;
23+
import org.springframework.core.env.MutablePropertySources;
24+
import org.springframework.core.env.PropertySource;
25+
26+
/**
27+
* Internal {@link PropertySource} implementation used by
28+
* {@link ConfigFileApplicationListener} to filter out properties for specific operations.
29+
*
30+
* @author Phillip Webb
31+
*/
32+
class FilteredPropertySource extends PropertySource<PropertySource<?>> {
33+
34+
private final Set<String> filteredProperties;
35+
36+
FilteredPropertySource(PropertySource<?> original, Set<String> filteredProperties) {
37+
super(original.getName(), original);
38+
this.filteredProperties = filteredProperties;
39+
}
40+
41+
@Override
42+
public Object getProperty(String name) {
43+
if (this.filteredProperties.contains(name)) {
44+
return null;
45+
}
46+
return getSource().getProperty(name);
47+
}
48+
49+
static void apply(ConfigurableEnvironment environment, String propertySourceName, Set<String> filteredProperties,
50+
Consumer<PropertySource<?>> operation) {
51+
MutablePropertySources propertySources = environment.getPropertySources();
52+
PropertySource<?> original = propertySources.get(propertySourceName);
53+
if (original == null) {
54+
operation.accept(null);
55+
return;
56+
}
57+
propertySources.replace(propertySourceName, new FilteredPropertySource(original, filteredProperties));
58+
try {
59+
operation.accept(original);
60+
}
61+
finally {
62+
propertySources.replace(propertySourceName, original);
63+
}
64+
}
65+
66+
}

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigFileApplicationListenerTests.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -960,6 +960,29 @@ void propertiesFromCustomPropertySourceLoaderShouldBeUsedWithSpecificResource()
960960
assertThat(this.environment.getProperty("customloader1")).isEqualTo("true");
961961
}
962962

963+
@Test
964+
public void customDefaultPropertySourceIsNotReplaced() {
965+
// gh-17011
966+
Map<String, Object> source = new HashMap<>();
967+
source.put("mapkey", "mapvalue");
968+
MapPropertySource propertySource = new MapPropertySource("defaultProperties", source) {
969+
970+
@Override
971+
public Object getProperty(String name) {
972+
if ("spring.config.name".equals(name)) {
973+
return "gh17001";
974+
}
975+
return super.getProperty(name);
976+
}
977+
978+
};
979+
this.environment.getPropertySources().addFirst(propertySource);
980+
this.initializer.setSearchNames("testactiveprofiles");
981+
this.initializer.postProcessEnvironment(this.environment, this.application);
982+
assertThat(this.environment.getProperty("mapkey")).isEqualTo("mapvalue");
983+
assertThat(this.environment.getProperty("gh17001loaded")).isEqualTo("true");
984+
}
985+
963986
private Condition<ConfigurableEnvironment> matchingPropertySource(final String sourceName) {
964987
return new Condition<ConfigurableEnvironment>("environment containing property source " + sourceName) {
965988

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright 2012-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.context.config;
18+
19+
import java.util.Collections;
20+
import java.util.LinkedHashMap;
21+
import java.util.Map;
22+
import java.util.function.Consumer;
23+
24+
import org.junit.jupiter.api.Test;
25+
26+
import org.springframework.core.env.ConfigurableEnvironment;
27+
import org.springframework.core.env.MapPropertySource;
28+
import org.springframework.core.env.PropertySource;
29+
import org.springframework.mock.env.MockEnvironment;
30+
31+
import static org.assertj.core.api.Assertions.assertThat;
32+
33+
/**
34+
* Tests for {@link FilteredPropertySource}.
35+
*
36+
* @author Phillip Webb
37+
*/
38+
class FilteredPropertySourceTests {
39+
40+
@Test
41+
void applyWhenHasNoSourceShouldRunOperation() {
42+
ConfigurableEnvironment environment = new MockEnvironment();
43+
TestOperation operation = new TestOperation();
44+
FilteredPropertySource.apply(environment, "test", Collections.emptySet(), operation);
45+
assertThat(operation.isCalled()).isTrue();
46+
assertThat(operation.getOriginal()).isNull();
47+
}
48+
49+
@Test
50+
void applyWhenHasSourceShouldRunWithReplacedSource() {
51+
ConfigurableEnvironment environment = new MockEnvironment();
52+
Map<String, Object> map = new LinkedHashMap<>();
53+
map.put("regular", "regularValue");
54+
map.put("filtered", "filteredValue");
55+
PropertySource<?> propertySource = new MapPropertySource("test", map);
56+
environment.getPropertySources().addFirst(propertySource);
57+
TestOperation operation = new TestOperation(() -> {
58+
assertThat(environment.containsProperty("regular")).isTrue();
59+
assertThat(environment.containsProperty("filtered")).isFalse();
60+
});
61+
FilteredPropertySource.apply(environment, "test", Collections.singleton("filtered"), operation);
62+
assertThat(operation.isCalled()).isTrue();
63+
assertThat(operation.getOriginal()).isSameAs(propertySource);
64+
assertThat(environment.getPropertySources().get("test")).isSameAs(propertySource);
65+
66+
}
67+
68+
private static class TestOperation implements Consumer<PropertySource<?>> {
69+
70+
private boolean called;
71+
72+
private PropertySource<?> original;
73+
74+
private Runnable operation;
75+
76+
TestOperation() {
77+
this(null);
78+
}
79+
80+
TestOperation(Runnable operation) {
81+
this.operation = operation;
82+
}
83+
84+
@Override
85+
public void accept(PropertySource<?> original) {
86+
this.called = true;
87+
this.original = original;
88+
if (this.operation != null) {
89+
this.operation.run();
90+
}
91+
}
92+
93+
public boolean isCalled() {
94+
return this.called;
95+
}
96+
97+
public PropertySource<?> getOriginal() {
98+
return this.original;
99+
}
100+
101+
}
102+
103+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
gh17001loaded=true

0 commit comments

Comments
 (0)