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
This commit is contained in:
Phillip Webb
2019-06-10 09:55:30 -07:00
parent 75e45fd239
commit 8d44e31898
5 changed files with 232 additions and 78 deletions

View File

@@ -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<List<String>> STRING_LIST = Bindable.listOf(String.class);
private static final Set<String> LOAD_FILTERED_PROPERTY;
static {
Set<String> 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<String> 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<String> activeProfiles) {
if (defaultProperties != null) {
Binder binder = new Binder(ConfigurationPropertySources.from(defaultProperties),
new PropertySourcesPlaceholdersResolver(this.environment));
List<String> includes = getDefaultProfiles(binder, "spring.profiles.include");
activeProfiles.addAll(includes);
activeProfiles.addAll(getDefaultProfiles(binder, "spring.profiles.include"));
if (!this.activatedProfiles) {
List<String> 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<String> getDefaultProfiles(Binder binder, String property) {
@@ -717,43 +715,6 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
}
private static class FilteredDefaultPropertySource extends MapPropertySource {
private static final List<String> FILTERED_PROPERTY = Arrays.asList("spring.profiles.active",
"spring.profiles.include");
FilteredDefaultPropertySource(String name, Map<String, Object> 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.
*/

View File

@@ -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<PropertySource<?>> {
private final Set<String> filteredProperties;
FilteredPropertySource(PropertySource<?> original, Set<String> 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<String> filteredProperties,
Consumer<PropertySource<?>> 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);
}
}
}

View File

@@ -960,6 +960,29 @@ class ConfigFileApplicationListenerTests {
assertThat(this.environment.getProperty("customloader1")).isEqualTo("true");
}
@Test
public void customDefaultPropertySourceIsNotReplaced() {
// gh-17011
Map<String, Object> 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<ConfigurableEnvironment> matchingPropertySource(final String sourceName) {
return new Condition<ConfigurableEnvironment>("environment containing property source " + sourceName) {

View File

@@ -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<String, Object> 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<PropertySource<?>> {
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;
}
}
}

View File

@@ -0,0 +1 @@
gh17001loaded=true