Skip to content

Commit 4bde6b8

Browse files
committed
Refactor YAML profile to deal with "!" profiles
Drop `SpringProfileDocumentMatcher` and replace it with two new implementations that restrict when YAML documents are loaded. YAML sections are now restricted both on the specific profile that is being loaded, and the profiles that are currently accepted. The `PropertySourceLoader` interface has been refined to include a predicate that determines when a profile is accepted. The `ConfigFileApplicationListener` simply delegates the predicate logic to the `Environment`. Fixes gh-8011
1 parent b03fd99 commit 4bde6b8

22 files changed

+542
-438
lines changed

spring-boot-project/spring-boot-docs/src/main/java/org/springframework/boot/docs/context/EnvironmentPostProcessorExample.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.springframework.boot.env.EnvironmentPostProcessor;
2323
import org.springframework.boot.env.YamlPropertySourceLoader;
2424
import org.springframework.core.env.ConfigurableEnvironment;
25+
import org.springframework.core.env.Environment;
2526
import org.springframework.core.env.PropertySource;
2627
import org.springframework.core.io.ClassPathResource;
2728
import org.springframework.core.io.Resource;
@@ -40,16 +41,17 @@ public class EnvironmentPostProcessorExample implements EnvironmentPostProcessor
4041
public void postProcessEnvironment(ConfigurableEnvironment environment,
4142
SpringApplication application) {
4243
Resource path = new ClassPathResource("com/example/myapp/config.yml");
43-
PropertySource<?> propertySource = loadYaml(path);
44+
PropertySource<?> propertySource = loadYaml(path, environment);
4445
environment.getPropertySources().addLast(propertySource);
4546
}
4647

47-
private PropertySource<?> loadYaml(Resource path) {
48+
private PropertySource<?> loadYaml(Resource path, Environment environment) {
4849
if (!path.exists()) {
4950
throw new IllegalArgumentException("Resource " + path + " does not exist");
5051
}
5152
try {
52-
return this.loader.load("custom-resource", path, null);
53+
return this.loader.load("custom-resource", path, null,
54+
environment::acceptsProfiles);
5355
}
5456
catch (IOException ex) {
5557
throw new IllegalStateException(

spring-boot-project/spring-boot-properties-migrator/src/test/java/org/springframework/boot/context/properties/migrator/PropertiesMigrationReporterTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ private PropertySource<?> loadPropertySource(String name, String path)
181181
throws IOException {
182182
ClassPathResource resource = new ClassPathResource(path);
183183
PropertySource<?> propertySource = new PropertiesPropertySourceLoader().load(name,
184-
resource, null);
184+
resource, null, (profile) -> true);
185185
assertThat(propertySource).isNotNull();
186186
return propertySource;
187187
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,8 @@ private void load(PropertySourceLoader loader, Profile profile, String location,
466466
}
467467
String name = "applicationConfig: [" + location + "]"
468468
+ (loadProfile == null ? "" : "#" + loadProfile);
469-
PropertySource<?> loaded = loader.load(name, resource, loadProfile);
469+
PropertySource<?> loaded = loader.load(name, resource, loadProfile,
470+
this.environment::acceptsProfiles);
470471
if (loaded == null) {
471472
this.logger.trace("Skipped unloaded config " + description);
472473
return;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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+
* http://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.env;
18+
19+
import java.util.function.Predicate;
20+
21+
import org.springframework.util.ObjectUtils;
22+
23+
/**
24+
* {@link SpringProfilesDocumentMatcher} that tests if a profile is accepted.
25+
*
26+
* @author Phillip Webb
27+
*/
28+
class AcceptsProfilesDocumentMatcher extends SpringProfilesDocumentMatcher {
29+
30+
private final Predicate<String[]> acceptsProfiles;
31+
32+
AcceptsProfilesDocumentMatcher(Predicate<String[]> acceptsProfiles) {
33+
this.acceptsProfiles = acceptsProfiles;
34+
}
35+
36+
@Override
37+
protected boolean matches(String[] profiles) {
38+
return ObjectUtils.isEmpty(profiles) || this.acceptsProfiles.test(profiles);
39+
}
40+
41+
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/OriginTrackedYamlLoader.java

Lines changed: 7 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2017 the original author or authors.
2+
* Copyright 2012-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,9 +17,8 @@
1717
package org.springframework.boot.env;
1818

1919
import java.util.LinkedHashMap;
20-
import java.util.List;
2120
import java.util.Map;
22-
import java.util.Properties;
21+
import java.util.function.Predicate;
2322
import java.util.regex.Pattern;
2423
import java.util.stream.Collectors;
2524

@@ -41,11 +40,10 @@
4140
import org.springframework.boot.origin.OriginTrackedValue;
4241
import org.springframework.boot.origin.TextResourceOrigin;
4342
import org.springframework.boot.origin.TextResourceOrigin.Location;
44-
import org.springframework.boot.yaml.SpringProfileDocumentMatcher;
4543
import org.springframework.core.io.Resource;
4644

4745
/**
48-
* Class to load {@code .yml} files into a map of {@code String} ->
46+
* Class to load {@code .yml} files into a map of {@code String} to
4947
* {@link OriginTrackedValue}.
5048
*
5149
* @author Madhura Bhave
@@ -55,16 +53,11 @@ class OriginTrackedYamlLoader extends YamlProcessor {
5553

5654
private final Resource resource;
5755

58-
OriginTrackedYamlLoader(Resource resource, String profile) {
56+
OriginTrackedYamlLoader(Resource resource, String profileToLoad,
57+
Predicate<String[]> acceptsProfiles) {
5958
this.resource = resource;
60-
if (profile == null) {
61-
setMatchDefault(true);
62-
setDocumentMatchers(new OriginTrackedSpringProfileDocumentMatcher());
63-
}
64-
else {
65-
setMatchDefault(false);
66-
setDocumentMatchers(new OriginTrackedSpringProfileDocumentMatcher(profile));
67-
}
59+
setDocumentMatchers(new ProfileToLoadDocumentMatcher(profileToLoad),
60+
new AcceptsProfilesDocumentMatcher(acceptsProfiles));
6861
setResources(resource);
6962
}
7063

@@ -164,32 +157,4 @@ public void addImplicitResolver(Tag tag, Pattern regexp, String first) {
164157

165158
}
166159

167-
/**
168-
* {@link SpringProfileDocumentMatcher} that deals with {@link OriginTrackedValue
169-
* OriginTrackedValues}.
170-
*/
171-
private static class OriginTrackedSpringProfileDocumentMatcher
172-
extends SpringProfileDocumentMatcher {
173-
174-
OriginTrackedSpringProfileDocumentMatcher(String... profiles) {
175-
super(profiles);
176-
}
177-
178-
@Override
179-
protected List<String> extractSpringProfiles(Properties properties) {
180-
Properties springProperties = new Properties();
181-
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
182-
if (String.valueOf(entry.getKey()).startsWith("spring.")) {
183-
Object value = entry.getValue();
184-
if (value instanceof OriginTrackedValue) {
185-
value = ((OriginTrackedValue) value).getValue();
186-
}
187-
springProperties.put(entry.getKey(), value);
188-
}
189-
}
190-
return super.extractSpringProfiles(springProperties);
191-
}
192-
193-
}
194-
195160
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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+
* http://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.env;
18+
19+
import java.util.Arrays;
20+
21+
import org.springframework.util.ObjectUtils;
22+
23+
/**
24+
* {@link SpringProfilesDocumentMatcher} that matches a specific profile to load.
25+
*
26+
* @author Phillip Webb
27+
*/
28+
class ProfileToLoadDocumentMatcher extends SpringProfilesDocumentMatcher {
29+
30+
private final String profile;
31+
32+
ProfileToLoadDocumentMatcher(String profile) {
33+
this.profile = profile;
34+
}
35+
36+
@Override
37+
protected boolean matches(String[] profiles) {
38+
String[] positiveProfiles = (profiles == null ? null : Arrays.stream(profiles)
39+
.filter(this::isPositveProfile).toArray(String[]::new));
40+
if (this.profile == null) {
41+
return ObjectUtils.isEmpty(positiveProfiles);
42+
}
43+
return ObjectUtils.containsElement(positiveProfiles, this.profile);
44+
}
45+
46+
private boolean isPositveProfile(String profile) {
47+
return !profile.startsWith("!");
48+
}
49+
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/PropertiesPropertySourceLoader.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2017 the original author or authors.
2+
* Copyright 2012-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@
1818

1919
import java.io.IOException;
2020
import java.util.Map;
21+
import java.util.function.Predicate;
2122

2223
import org.springframework.core.env.PropertySource;
2324
import org.springframework.core.io.Resource;
@@ -40,9 +41,9 @@ public String[] getFileExtensions() {
4041
}
4142

4243
@Override
43-
public PropertySource<?> load(String name, Resource resource, String profile)
44-
throws IOException {
45-
if (profile == null) {
44+
public PropertySource<?> load(String name, Resource resource, String profileToLoad,
45+
Predicate<String[]> acceptsProfiles) throws IOException {
46+
if (profileToLoad == null) {
4647
Map<String, ?> properties = loadProperties(resource);
4748
if (!properties.isEmpty()) {
4849
return new OriginTrackedMapPropertySource(name, properties);

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/PropertySourceLoader.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2017 the original author or authors.
2+
* Copyright 2012-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
1717
package org.springframework.boot.env;
1818

1919
import java.io.IOException;
20+
import java.util.function.Predicate;
2021

2122
import org.springframework.core.env.PropertySource;
2223
import org.springframework.core.io.Resource;
@@ -41,13 +42,14 @@ public interface PropertySourceLoader {
4142
* Load the resource into a property source.
4243
* @param name the name of the property source
4344
* @param resource the resource to load
44-
* @param profile the name of the profile to load or {@code null}. The profile can be
45-
* used to load multi-document files (such as YAML). Simple property formats should
46-
* {@code null} when asked to load a profile.
45+
* @param profileToLoad the name of the profile to load or {@code null}. The profile
46+
* can be used to load multi-document files (such as YAML). Simple property formats
47+
* should {@code null} when asked to load a profile.
48+
* @param acceptsProfiles predicate to determine if a particular profile is accepted
4749
* @return a property source or {@code null}
4850
* @throws IOException if the source cannot be loaded
4951
*/
50-
PropertySource<?> load(String name, Resource resource, String profile)
51-
throws IOException;
52+
PropertySource<?> load(String name, Resource resource, String profileToLoad,
53+
Predicate<String[]> acceptsProfiles) throws IOException;
5254

5355
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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+
* http://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.env;
18+
19+
import java.util.Map;
20+
import java.util.Properties;
21+
22+
import org.springframework.beans.factory.config.YamlProcessor.DocumentMatcher;
23+
import org.springframework.beans.factory.config.YamlProcessor.MatchStatus;
24+
import org.springframework.boot.context.properties.bind.Bindable;
25+
import org.springframework.boot.context.properties.bind.Binder;
26+
import org.springframework.boot.context.properties.source.ConfigurationProperty;
27+
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
28+
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
29+
import org.springframework.boot.origin.OriginTrackedValue;
30+
31+
/**
32+
* Base class for {@link DocumentMatcher DocumentMatchers} that check the
33+
* {@code spring.profiles} property.
34+
*
35+
* @author Phillip Webb
36+
* @see OriginTrackedYamlLoader
37+
*/
38+
abstract class SpringProfilesDocumentMatcher implements DocumentMatcher {
39+
40+
@Override
41+
public final MatchStatus matches(Properties properties) {
42+
Binder binder = new Binder(
43+
new OriginTrackedValueConfigurationPropertySource(properties));
44+
String[] profiles = binder.bind("spring.profiles", Bindable.of(String[].class))
45+
.orElse(null);
46+
return (matches(profiles) ? MatchStatus.ABSTAIN : MatchStatus.NOT_FOUND);
47+
}
48+
49+
protected abstract boolean matches(String[] profiles);
50+
51+
/**
52+
* {@link MapConfigurationPropertySource} that deals with unwrapping
53+
* {@link OriginTrackedValue OriginTrackedValues} from the underlying map.
54+
*/
55+
static class OriginTrackedValueConfigurationPropertySource
56+
extends MapConfigurationPropertySource {
57+
58+
OriginTrackedValueConfigurationPropertySource(Map<?, ?> map) {
59+
super(map);
60+
}
61+
62+
@Override
63+
public ConfigurationProperty getConfigurationProperty(
64+
ConfigurationPropertyName name) {
65+
ConfigurationProperty property = super.getConfigurationProperty(name);
66+
if (property != null && property.getValue() instanceof OriginTrackedValue) {
67+
OriginTrackedValue originTrackedValue = (OriginTrackedValue) property
68+
.getValue();
69+
property = new ConfigurationProperty(property.getName(),
70+
originTrackedValue.getValue(), originTrackedValue.getOrigin());
71+
}
72+
return property;
73+
}
74+
75+
}
76+
77+
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/YamlPropertySourceLoader.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2017 the original author or authors.
2+
* Copyright 2012-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@
1818

1919
import java.io.IOException;
2020
import java.util.Map;
21+
import java.util.function.Predicate;
2122

2223
import org.springframework.core.env.PropertySource;
2324
import org.springframework.core.io.Resource;
@@ -38,14 +39,14 @@ public String[] getFileExtensions() {
3839
}
3940

4041
@Override
41-
public PropertySource<?> load(String name, Resource resource, String profile)
42-
throws IOException {
42+
public PropertySource<?> load(String name, Resource resource, String profileToLoad,
43+
Predicate<String[]> acceptsProfiles) throws IOException {
4344
if (!ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", null)) {
4445
throw new IllegalStateException("Attempted to load " + name
4546
+ " but snakeyaml was not found on the classpath");
4647
}
47-
Map<String, Object> source = new OriginTrackedYamlLoader(resource, profile)
48-
.load();
48+
Map<String, Object> source = new OriginTrackedYamlLoader(resource, profileToLoad,
49+
acceptsProfiles).load();
4950
if (!source.isEmpty()) {
5051
return new OriginTrackedMapPropertySource(name, source);
5152
}

0 commit comments

Comments
 (0)