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:
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
gh17001loaded=true
|
||||
Reference in New Issue
Block a user