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
pull/12156/head
Phillip Webb 7 years ago
parent b03fd99209
commit 4bde6b80ee

@ -22,6 +22,7 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
@ -40,16 +41,17 @@ public class EnvironmentPostProcessorExample implements EnvironmentPostProcessor
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
Resource path = new ClassPathResource("com/example/myapp/config.yml");
PropertySource<?> propertySource = loadYaml(path);
PropertySource<?> propertySource = loadYaml(path, environment);
environment.getPropertySources().addLast(propertySource);
}
private PropertySource<?> loadYaml(Resource path) {
private PropertySource<?> loadYaml(Resource path, Environment environment) {
if (!path.exists()) {
throw new IllegalArgumentException("Resource " + path + " does not exist");
}
try {
return this.loader.load("custom-resource", path, null);
return this.loader.load("custom-resource", path, null,
environment::acceptsProfiles);
}
catch (IOException ex) {
throw new IllegalStateException(

@ -181,7 +181,7 @@ public class PropertiesMigrationReporterTests {
throws IOException {
ClassPathResource resource = new ClassPathResource(path);
PropertySource<?> propertySource = new PropertiesPropertySourceLoader().load(name,
resource, null);
resource, null, (profile) -> true);
assertThat(propertySource).isNotNull();
return propertySource;
}

@ -466,7 +466,8 @@ public class ConfigFileApplicationListener
}
String name = "applicationConfig: [" + location + "]"
+ (loadProfile == null ? "" : "#" + loadProfile);
PropertySource<?> loaded = loader.load(name, resource, loadProfile);
PropertySource<?> loaded = loader.load(name, resource, loadProfile,
this.environment::acceptsProfiles);
if (loaded == null) {
this.logger.trace("Skipped unloaded config " + description);
return;

@ -0,0 +1,41 @@
/*
* 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
*
* http://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.env;
import java.util.function.Predicate;
import org.springframework.util.ObjectUtils;
/**
* {@link SpringProfilesDocumentMatcher} that tests if a profile is accepted.
*
* @author Phillip Webb
*/
class AcceptsProfilesDocumentMatcher extends SpringProfilesDocumentMatcher {
private final Predicate<String[]> acceptsProfiles;
AcceptsProfilesDocumentMatcher(Predicate<String[]> acceptsProfiles) {
this.acceptsProfiles = acceptsProfiles;
}
@Override
protected boolean matches(String[] profiles) {
return ObjectUtils.isEmpty(profiles) || this.acceptsProfiles.test(profiles);
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* 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.
@ -17,9 +17,8 @@
package org.springframework.boot.env;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@ -41,11 +40,10 @@ import org.springframework.boot.origin.Origin;
import org.springframework.boot.origin.OriginTrackedValue;
import org.springframework.boot.origin.TextResourceOrigin;
import org.springframework.boot.origin.TextResourceOrigin.Location;
import org.springframework.boot.yaml.SpringProfileDocumentMatcher;
import org.springframework.core.io.Resource;
/**
* Class to load {@code .yml} files into a map of {@code String} ->
* Class to load {@code .yml} files into a map of {@code String} to
* {@link OriginTrackedValue}.
*
* @author Madhura Bhave
@ -55,16 +53,11 @@ class OriginTrackedYamlLoader extends YamlProcessor {
private final Resource resource;
OriginTrackedYamlLoader(Resource resource, String profile) {
OriginTrackedYamlLoader(Resource resource, String profileToLoad,
Predicate<String[]> acceptsProfiles) {
this.resource = resource;
if (profile == null) {
setMatchDefault(true);
setDocumentMatchers(new OriginTrackedSpringProfileDocumentMatcher());
}
else {
setMatchDefault(false);
setDocumentMatchers(new OriginTrackedSpringProfileDocumentMatcher(profile));
}
setDocumentMatchers(new ProfileToLoadDocumentMatcher(profileToLoad),
new AcceptsProfilesDocumentMatcher(acceptsProfiles));
setResources(resource);
}
@ -164,32 +157,4 @@ class OriginTrackedYamlLoader extends YamlProcessor {
}
/**
* {@link SpringProfileDocumentMatcher} that deals with {@link OriginTrackedValue
* OriginTrackedValues}.
*/
private static class OriginTrackedSpringProfileDocumentMatcher
extends SpringProfileDocumentMatcher {
OriginTrackedSpringProfileDocumentMatcher(String... profiles) {
super(profiles);
}
@Override
protected List<String> extractSpringProfiles(Properties properties) {
Properties springProperties = new Properties();
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
if (String.valueOf(entry.getKey()).startsWith("spring.")) {
Object value = entry.getValue();
if (value instanceof OriginTrackedValue) {
value = ((OriginTrackedValue) value).getValue();
}
springProperties.put(entry.getKey(), value);
}
}
return super.extractSpringProfiles(springProperties);
}
}
}

@ -0,0 +1,49 @@
/*
* 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
*
* http://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.env;
import java.util.Arrays;
import org.springframework.util.ObjectUtils;
/**
* {@link SpringProfilesDocumentMatcher} that matches a specific profile to load.
*
* @author Phillip Webb
*/
class ProfileToLoadDocumentMatcher extends SpringProfilesDocumentMatcher {
private final String profile;
ProfileToLoadDocumentMatcher(String profile) {
this.profile = profile;
}
@Override
protected boolean matches(String[] profiles) {
String[] positiveProfiles = (profiles == null ? null : Arrays.stream(profiles)
.filter(this::isPositveProfile).toArray(String[]::new));
if (this.profile == null) {
return ObjectUtils.isEmpty(positiveProfiles);
}
return ObjectUtils.containsElement(positiveProfiles, this.profile);
}
private boolean isPositveProfile(String profile) {
return !profile.startsWith("!");
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* 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.
@ -18,6 +18,7 @@ package org.springframework.boot.env;
import java.io.IOException;
import java.util.Map;
import java.util.function.Predicate;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
@ -40,9 +41,9 @@ public class PropertiesPropertySourceLoader implements PropertySourceLoader {
}
@Override
public PropertySource<?> load(String name, Resource resource, String profile)
throws IOException {
if (profile == null) {
public PropertySource<?> load(String name, Resource resource, String profileToLoad,
Predicate<String[]> acceptsProfiles) throws IOException {
if (profileToLoad == null) {
Map<String, ?> properties = loadProperties(resource);
if (!properties.isEmpty()) {
return new OriginTrackedMapPropertySource(name, properties);

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* 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.
@ -17,6 +17,7 @@
package org.springframework.boot.env;
import java.io.IOException;
import java.util.function.Predicate;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
@ -41,13 +42,14 @@ public interface PropertySourceLoader {
* Load the resource into a property source.
* @param name the name of the property source
* @param resource the resource to load
* @param profile the name of the profile to load or {@code null}. The profile can be
* used to load multi-document files (such as YAML). Simple property formats should
* {@code null} when asked to load a profile.
* @param profileToLoad the name of the profile to load or {@code null}. The profile
* can be used to load multi-document files (such as YAML). Simple property formats
* should {@code null} when asked to load a profile.
* @param acceptsProfiles predicate to determine if a particular profile is accepted
* @return a property source or {@code null}
* @throws IOException if the source cannot be loaded
*/
PropertySource<?> load(String name, Resource resource, String profile)
throws IOException;
PropertySource<?> load(String name, Resource resource, String profileToLoad,
Predicate<String[]> acceptsProfiles) throws IOException;
}

@ -0,0 +1,77 @@
/*
* 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
*
* http://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.env;
import java.util.Map;
import java.util.Properties;
import org.springframework.beans.factory.config.YamlProcessor.DocumentMatcher;
import org.springframework.beans.factory.config.YamlProcessor.MatchStatus;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationProperty;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.boot.origin.OriginTrackedValue;
/**
* Base class for {@link DocumentMatcher DocumentMatchers} that check the
* {@code spring.profiles} property.
*
* @author Phillip Webb
* @see OriginTrackedYamlLoader
*/
abstract class SpringProfilesDocumentMatcher implements DocumentMatcher {
@Override
public final MatchStatus matches(Properties properties) {
Binder binder = new Binder(
new OriginTrackedValueConfigurationPropertySource(properties));
String[] profiles = binder.bind("spring.profiles", Bindable.of(String[].class))
.orElse(null);
return (matches(profiles) ? MatchStatus.ABSTAIN : MatchStatus.NOT_FOUND);
}
protected abstract boolean matches(String[] profiles);
/**
* {@link MapConfigurationPropertySource} that deals with unwrapping
* {@link OriginTrackedValue OriginTrackedValues} from the underlying map.
*/
static class OriginTrackedValueConfigurationPropertySource
extends MapConfigurationPropertySource {
OriginTrackedValueConfigurationPropertySource(Map<?, ?> map) {
super(map);
}
@Override
public ConfigurationProperty getConfigurationProperty(
ConfigurationPropertyName name) {
ConfigurationProperty property = super.getConfigurationProperty(name);
if (property != null && property.getValue() instanceof OriginTrackedValue) {
OriginTrackedValue originTrackedValue = (OriginTrackedValue) property
.getValue();
property = new ConfigurationProperty(property.getName(),
originTrackedValue.getValue(), originTrackedValue.getOrigin());
}
return property;
}
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* 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.
@ -18,6 +18,7 @@ package org.springframework.boot.env;
import java.io.IOException;
import java.util.Map;
import java.util.function.Predicate;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
@ -38,14 +39,14 @@ public class YamlPropertySourceLoader implements PropertySourceLoader {
}
@Override
public PropertySource<?> load(String name, Resource resource, String profile)
throws IOException {
public PropertySource<?> load(String name, Resource resource, String profileToLoad,
Predicate<String[]> acceptsProfiles) throws IOException {
if (!ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", null)) {
throw new IllegalStateException("Attempted to load " + name
+ " but snakeyaml was not found on the classpath");
}
Map<String, Object> source = new OriginTrackedYamlLoader(resource, profile)
.load();
Map<String, Object> source = new OriginTrackedYamlLoader(resource, profileToLoad,
acceptsProfiles).load();
if (!source.isEmpty()) {
return new OriginTrackedMapPropertySource(name, source);
}

@ -1,207 +0,0 @@
/*
* 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
*
* http://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.yaml;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import org.springframework.beans.factory.config.YamlProcessor.DocumentMatcher;
import org.springframework.beans.factory.config.YamlProcessor.MatchStatus;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.core.env.Environment;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
* {@link DocumentMatcher} backed by {@link Environment#getActiveProfiles()}. A YAML
* document may define a "spring.profiles" element as a comma-separated list of Spring
* profile names, optionally negated using the {@code !} character. If both negated and
* non-negated profiles are specified for a single document, at least one non-negated
* profile must match and no negated profiles may match.
*
* @author Dave Syer
* @author Matt Benson
* @author Phillip Webb
* @author Andy Wilkinson
* @author Madhura Bhave
*/
public class SpringProfileDocumentMatcher implements DocumentMatcher {
private String[] activeProfiles = new String[0];
public SpringProfileDocumentMatcher(String... profiles) {
addActiveProfiles(profiles);
}
public void addActiveProfiles(String... profiles) {
LinkedHashSet<String> set = new LinkedHashSet<>(
Arrays.asList(this.activeProfiles));
Collections.addAll(set, profiles);
this.activeProfiles = set.toArray(new String[set.size()]);
}
@Override
public MatchStatus matches(Properties properties) {
return matches(extractSpringProfiles(properties));
}
protected List<String> extractSpringProfiles(Properties properties) {
Binder binder = new Binder(new MapConfigurationPropertySource(properties));
return binder.bind("spring.profiles", Bindable.of(String[].class))
.map(Arrays::asList).orElse(Collections.emptyList());
}
private MatchStatus matches(List<String> profiles) {
ProfilesMatcher profilesMatcher = getProfilesMatcher();
Set<String> negative = extractProfiles(profiles, ProfileType.NEGATIVE);
Set<String> positive = extractProfiles(profiles, ProfileType.POSITIVE);
if (!CollectionUtils.isEmpty(negative)) {
if (profilesMatcher.matches(negative) == MatchStatus.FOUND) {
return MatchStatus.NOT_FOUND;
}
if (CollectionUtils.isEmpty(positive)) {
return MatchStatus.FOUND;
}
}
return profilesMatcher.matches(positive);
}
private ProfilesMatcher getProfilesMatcher() {
return this.activeProfiles.length == 0 ? new EmptyProfilesMatcher()
: new ActiveProfilesMatcher(
new HashSet<>(Arrays.asList(this.activeProfiles)));
}
private Set<String> extractProfiles(List<String> profiles, ProfileType type) {
if (CollectionUtils.isEmpty(profiles)) {
return null;
}
Set<String> extractedProfiles = new HashSet<>();
for (String candidate : profiles) {
ProfileType candidateType = ProfileType.POSITIVE;
if (candidate.startsWith("!")) {
candidateType = ProfileType.NEGATIVE;
}
if (candidateType == type) {
extractedProfiles.add(type == ProfileType.POSITIVE ? candidate
: candidate.substring(1));
}
}
return extractedProfiles;
}
/**
* Profile match types.
*/
enum ProfileType {
POSITIVE, NEGATIVE
}
/**
* Base class for profile matchers.
*/
private abstract static class ProfilesMatcher {
public final MatchStatus matches(Set<String> profiles) {
if (CollectionUtils.isEmpty(profiles)) {
return MatchStatus.ABSTAIN;
}
return doMatches(profiles);
}
protected abstract MatchStatus doMatches(Set<String> profiles);
}
/**
* {@link ProfilesMatcher} that matches when a value in {@code spring.profiles} is
* also in {@code spring.profiles.active}.
*/
private static class ActiveProfilesMatcher extends ProfilesMatcher {
private final Set<String> activeProfiles;
ActiveProfilesMatcher(Set<String> activeProfiles) {
this.activeProfiles = activeProfiles;
}
@Override
protected MatchStatus doMatches(Set<String> profiles) {
if (profiles.isEmpty()) {
return MatchStatus.NOT_FOUND;
}
for (String activeProfile : this.activeProfiles) {
if (profiles.contains(activeProfile)) {
return MatchStatus.FOUND;
}
}
return MatchStatus.NOT_FOUND;
}
}
/**
* {@link ProfilesMatcher} that matches when {@code
* spring.profiles} is empty or contains a value with no text.
*
* @see StringUtils#hasText(String)
*/
private static class EmptyProfilesMatcher extends ProfilesMatcher {
@Override
public MatchStatus doMatches(Set<String> springProfiles) {
if (springProfiles.isEmpty()) {
return MatchStatus.FOUND;
}
for (String profile : springProfiles) {
if (!StringUtils.hasText(profile)) {
return MatchStatus.FOUND;
}
}
return MatchStatus.NOT_FOUND;
}
}
/**
* Class for binding {@code spring.profiles} property.
*/
static class SpringProperties {
private List<String> profiles = new ArrayList<>();
public List<String> getProfiles() {
return this.profiles;
}
public void setProfiles(List<String> profiles) {
this.profiles = profiles;
}
}
}

@ -1,23 +0,0 @@
/*
* Copyright 2012-2017 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
*
* http://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.
*/
/**
* Spring Boot extensions to Spring Framework's support for parsing YAML.
*
* @see org.springframework.beans.factory.config.YamlPropertiesFactoryBean
* @see org.springframework.beans.factory.config.YamlMapFactoryBean
*/
package org.springframework.boot.yaml;

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* 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.
@ -504,6 +504,29 @@ public class ConfigFileApplicationListenerTests {
assertThat(property).isEqualTo("notempty");
}
@Test
public void yamlNegatedProfiles() {
// gh-8011
this.initializer.setSearchNames("testnegatedprofiles");
this.initializer.postProcessEnvironment(this.environment, this.application);
String property = this.environment.getProperty("my.property");
assertThat(property).isEqualTo("fromnototherprofile");
property = this.environment.getProperty("my.notother");
assertThat(property).isEqualTo("foo");
}
@Test
public void yamlNegatedProfilesWithProfile() {
// gh-8011
this.initializer.setSearchNames("testnegatedprofiles");
this.environment.setActiveProfiles("other");
this.initializer.postProcessEnvironment(this.environment, this.application);
String property = this.environment.getProperty("my.property");
assertThat(property).isEqualTo("fromotherprofile");
property = this.environment.getProperty("my.notother");
assertThat(property).isNull();
}
@Test
public void yamlSetsProfiles() {
this.initializer.setSearchNames("testsetprofiles");

@ -0,0 +1,72 @@
/*
* 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
*
* http://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.env;
import java.util.Properties;
import java.util.function.Predicate;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.config.YamlProcessor.MatchStatus;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
/**
* Tests for {@link AcceptsProfilesDocumentMatcher}.
*
* @author Phillip Webb
*/
public class AcceptsProfilesDocumentMatcherTests {
@Mock
private Predicate<String[]> acceptsProfiles;
private AcceptsProfilesDocumentMatcher matcher;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.matcher = new AcceptsProfilesDocumentMatcher(this.acceptsProfiles);
}
@Test
public void matchesWhenHasNoProfilePropertyShouldReturnAbstain() {
Properties properties = new Properties();
assertThat(this.matcher.matches(properties)).isEqualTo(MatchStatus.ABSTAIN);
}
@Test
public void matchesWhenAcceptsProfileShouldReturnAbstain() {
Properties properties = new Properties();
properties.put("spring.profiles", "foo");
given(this.acceptsProfiles.test(new String[] { "foo" })).willReturn(true);
assertThat(this.matcher.matches(properties)).isEqualTo(MatchStatus.ABSTAIN);
}
@Test
public void matchesWhenDoesNotAcceptProfileShouldReturnNotFound() {
Properties properties = new Properties();
properties.put("spring.profiles", "foo");
given(this.acceptsProfiles.test(new String[] { "foo" })).willReturn(false);
assertThat(this.matcher.matches(properties)).isEqualTo(MatchStatus.NOT_FOUND);
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* 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.
@ -46,7 +46,7 @@ public class NoSnakeYamlPropertySourceLoaderTests {
"Attempted to load resource but snakeyaml was not found on the classpath");
ByteArrayResource resource = new ByteArrayResource(
"foo:\n bar: spam".getBytes());
this.loader.load("resource", resource, null);
this.loader.load("resource", resource, null, (profile) -> true);
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* 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.
@ -43,7 +43,7 @@ public class OriginTrackedYamlLoaderTests {
@Before
public void setUp() {
Resource resource = new ClassPathResource("test-yaml.yml", getClass());
this.loader = new OriginTrackedYamlLoader(resource, null);
this.loader = new OriginTrackedYamlLoader(resource, null, (profile) -> true);
}
@Test
@ -93,7 +93,8 @@ public class OriginTrackedYamlLoaderTests {
@Test
public void processWithActiveProfile() {
Resource resource = new ClassPathResource("test-yaml.yml", getClass());
this.loader = new OriginTrackedYamlLoader(resource, "development");
this.loader = new OriginTrackedYamlLoader(resource, "development",
(profile) -> true);
Map<String, Object> result = this.loader.load();
assertThat(result.get("name").toString()).isEqualTo("Test Name");
}

@ -0,0 +1,80 @@
/*
* 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
*
* http://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.env;
import java.util.Properties;
import org.junit.Test;
import org.springframework.beans.factory.config.YamlProcessor.MatchStatus;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ProfileToLoadDocumentMatcher}.
*
* @author Phillip Webb
*/
public class ProfileToLoadDocumentMatcherTests {
@Test
public void matchesWhenProfilesIsNullAndHasNoProfilePropertiesShouldReturnAbstain() {
ProfileToLoadDocumentMatcher matcher = new ProfileToLoadDocumentMatcher(null);
Properties properties = new Properties();
assertThat(matcher.matches(properties)).isEqualTo(MatchStatus.ABSTAIN);
}
@Test
public void matchesWhenProfileIsNullAndHasOnlyNegativeProfilePropertiesShouldReturnAbstain() {
ProfileToLoadDocumentMatcher matcher = new ProfileToLoadDocumentMatcher(null);
Properties properties = new Properties();
properties.put("spring.profiles", "!foo,!bar");
assertThat(matcher.matches(properties)).isEqualTo(MatchStatus.ABSTAIN);
}
@Test
public void matchesWhenProfileIsNullAndHasProfilePropertyShouldReturnNotFound() {
ProfileToLoadDocumentMatcher matcher = new ProfileToLoadDocumentMatcher(null);
Properties properties = new Properties();
properties.put("spring.profiles", "!foo,!bar,baz");
assertThat(matcher.matches(properties)).isEqualTo(MatchStatus.NOT_FOUND);
}
@Test
public void matchesWhenProfilesIsSetAndHasNoProfilePropertiesShouldReturnNotFound() {
ProfileToLoadDocumentMatcher matcher = new ProfileToLoadDocumentMatcher("bar");
Properties properties = new Properties();
assertThat(matcher.matches(properties)).isEqualTo(MatchStatus.NOT_FOUND);
}
@Test
public void matchesWhenProfileIsSetAndHasOnlyNegativeProfilePropertiesShouldReturnNotFound() {
ProfileToLoadDocumentMatcher matcher = new ProfileToLoadDocumentMatcher("bar");
Properties properties = new Properties();
properties.put("spring.profiles", "!foo,!bar,baz");
assertThat(matcher.matches(properties)).isEqualTo(MatchStatus.NOT_FOUND);
}
@Test
public void matchesWhenProfileIsSetAndHasProfilePropertyShouldReturnAbstain() {
ProfileToLoadDocumentMatcher matcher = new ProfileToLoadDocumentMatcher("bar");
Properties properties = new Properties();
properties.put("spring.profiles", "!foo,bar,baz");
assertThat(matcher.matches(properties)).isEqualTo(MatchStatus.ABSTAIN);
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* 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.
@ -42,14 +42,16 @@ public class PropertiesPropertySourceLoaderTests {
@Test
public void loadProperties() throws Exception {
PropertySource<?> source = this.loader.load("test.properties",
new ClassPathResource("test-properties.properties", getClass()), null);
new ClassPathResource("test-properties.properties", getClass()), null,
(profile) -> true);
assertThat(source.getProperty("test")).isEqualTo("properties");
}
@Test
public void loadXml() throws Exception {
PropertySource<?> source = this.loader.load("test.xml",
new ClassPathResource("test-xml.xml", getClass()), null);
new ClassPathResource("test-xml.xml", getClass()), null,
(profile) -> true);
assertThat(source.getProperty("test")).isEqualTo("xml");
}

@ -0,0 +1,93 @@
/*
* 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
*
* http://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.env;
import java.util.Properties;
import org.junit.Test;
import org.springframework.beans.factory.config.YamlProcessor.MatchStatus;
import org.springframework.boot.origin.OriginTrackedValue;
import org.springframework.util.ObjectUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link SpringProfilesDocumentMatcher}.
*
* @author Phillip Webb
*/
public class SpringProfilesDocumentMatcherTests {
private TestSpringProfilesDocumentMatcher matcher = new TestSpringProfilesDocumentMatcher();
@Test
public void matchesShouldBindAgainstCommaList() {
Properties properties = new Properties();
properties.put("spring.profiles", "foo,bar");
this.matcher.matches(properties);
assertThat(this.matcher.getProfiles()).containsExactly("foo", "bar");
}
@Test
public void matchesShouldBindAgainstYamlList() {
Properties properties = new Properties();
properties.put("spring.profiles[0]", "foo");
properties.put("spring.profiles[1]", "bar");
this.matcher.matches(properties);
assertThat(this.matcher.getProfiles()).containsExactly("foo", "bar");
}
@Test
public void matchesShouldBindAgainstOriginTrackedValue() {
Properties properties = new Properties();
properties.put("spring.profiles", OriginTrackedValue.of("foo,bar"));
this.matcher.matches(properties);
assertThat(this.matcher.getProfiles()).containsExactly("foo", "bar");
}
@Test
public void matchesWhenMatchShouldReturnAbstain() {
Properties properties = new Properties();
properties.put("spring.profiles", "foo,bar");
assertThat(this.matcher.matches(properties)).isEqualTo(MatchStatus.ABSTAIN);
}
@Test
public void matchesWhenNoMatchShouldReturnNotFound() {
Properties properties = new Properties();
assertThat(this.matcher.matches(properties)).isEqualTo(MatchStatus.NOT_FOUND);
}
private static class TestSpringProfilesDocumentMatcher
extends SpringProfilesDocumentMatcher {
private String[] profiles;
@Override
protected boolean matches(String[] profiles) {
this.profiles = profiles;
return !ObjectUtils.isEmpty(profiles);
}
public String[] getProfiles() {
return this.profiles;
}
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2017 the original author or authors.
* 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.
@ -26,6 +26,7 @@ import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.util.ObjectUtils;
import static org.assertj.core.api.Assertions.assertThat;
@ -44,7 +45,8 @@ public class YamlPropertySourceLoaderTests {
public void load() throws Exception {
ByteArrayResource resource = new ByteArrayResource(
"foo:\n bar: spam".getBytes());
PropertySource<?> source = this.loader.load("resource", resource, null);
PropertySource<?> source = this.loader.load("resource", resource, null,
(profile) -> true);
assertThat(source).isNotNull();
assertThat(source.getProperty("foo.bar")).isEqualTo("spam");
}
@ -59,7 +61,7 @@ public class YamlPropertySourceLoaderTests {
}
ByteArrayResource resource = new ByteArrayResource(yaml.toString().getBytes());
EnumerablePropertySource<?> source = (EnumerablePropertySource<?>) this.loader
.load("resource", resource, null);
.load("resource", resource, null, (profile) -> true);
assertThat(source).isNotNull();
assertThat(source.getPropertyNames())
.isEqualTo(expected.toArray(new String[] {}));
@ -72,7 +74,8 @@ public class YamlPropertySourceLoaderTests {
yaml.append("---\n");
yaml.append("foo:\n baz: wham\n");
ByteArrayResource resource = new ByteArrayResource(yaml.toString().getBytes());
PropertySource<?> source = this.loader.load("resource", resource, null);
PropertySource<?> source = this.loader.load("resource", resource, null,
(profile) -> true);
assertThat(source).isNotNull();
assertThat(source.getProperty("foo.bar")).isEqualTo("spam");
assertThat(source.getProperty("foo.baz")).isEqualTo("wham");
@ -81,7 +84,8 @@ public class YamlPropertySourceLoaderTests {
@Test
public void timestampLikeItemsDoNotBecomeDates() throws Exception {
ByteArrayResource resource = new ByteArrayResource("foo: 2015-01-28".getBytes());
PropertySource<?> source = this.loader.load("resource", resource, null);
PropertySource<?> source = this.loader.load("resource", resource, null,
(profile) -> true);
assertThat(source).isNotNull();
assertThat(source.getProperty("foo")).isEqualTo("2015-01-28");
}
@ -89,11 +93,42 @@ public class YamlPropertySourceLoaderTests {
@Test
public void loadOriginAware() throws Exception {
Resource resource = new ClassPathResource("test-yaml.yml", getClass());
PropertySource<?> source = this.loader.load("resource", resource, null);
PropertySource<?> source = this.loader.load("resource", resource, null,
(profile) -> true);
EnumerablePropertySource<?> enumerableSource = (EnumerablePropertySource<?>) source;
for (String name : enumerableSource.getPropertyNames()) {
System.out.println(name + " = " + enumerableSource.getProperty(name));
}
}
@Test
public void loadSpecificProfile() throws Exception {
StringBuilder yaml = new StringBuilder();
yaml.append("foo:\n bar: spam\n");
yaml.append("---\n");
yaml.append("spring:\n profiles: foo\n");
yaml.append("foo:\n bar: wham\n");
ByteArrayResource resource = new ByteArrayResource(yaml.toString().getBytes());
PropertySource<?> source = this.loader.load("resource", resource, "foo",
(profile) -> true);
assertThat(source).isNotNull();
assertThat(source.getProperty("foo.bar")).isEqualTo("wham");
}
@Test
public void loadWithAcceptProfile() throws Exception {
StringBuilder yaml = new StringBuilder();
yaml.append("---\n");
yaml.append("spring:\n profiles: yay,foo\n");
yaml.append("foo:\n bar: bang\n");
yaml.append("---\n");
yaml.append("spring:\n profiles: yay,!foo\n");
yaml.append("foo:\n bar: wham\n");
ByteArrayResource resource = new ByteArrayResource(yaml.toString().getBytes());
PropertySource<?> source = this.loader.load("resource", resource, "yay",
(profiles) -> ObjectUtils.containsElement(profiles, "!foo"));
assertThat(source).isNotNull();
assertThat(source.getProperty("foo.bar")).isEqualTo("wham");
}
}

@ -1,131 +0,0 @@
/*
* Copyright 2012-2017 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
*
* http://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.yaml;
import java.util.Properties;
import org.junit.Test;
import org.springframework.beans.factory.config.YamlProcessor.DocumentMatcher;
import org.springframework.beans.factory.config.YamlProcessor.MatchStatus;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.core.io.ByteArrayResource;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link SpringProfileDocumentMatcher}.
*
* @author Matt Benson
* @author Andy Wilkinson
*/
public class SpringProfileDocumentMatcherTests {
@Test
public void matchesSingleProfile() {
DocumentMatcher matcher = new SpringProfileDocumentMatcher("foo", "bar");
Properties properties = getProperties("spring.ProfILEs: foo");
assertThat(matcher.matches(properties)).isEqualTo(MatchStatus.FOUND);
}
@Test
public void abstainNoConfiguredProfiles() {
DocumentMatcher matcher = new SpringProfileDocumentMatcher("foo", "bar");
Properties properties = getProperties("some.property: spam");
assertThat(matcher.matches(properties)).isEqualTo(MatchStatus.ABSTAIN);
}
@Test
public void noActiveProfiles() {
DocumentMatcher matcher = new SpringProfileDocumentMatcher();
Properties properties = getProperties("spring.profiles: bar,spam");
assertThat(matcher.matches(properties)).isEqualTo(MatchStatus.NOT_FOUND);
}
@Test
public void matchesCommaSeparatedString() {
DocumentMatcher matcher = new SpringProfileDocumentMatcher("foo", "bar");
Properties properties = getProperties("spring.profiles: bar,spam");
assertThat(matcher.matches(properties)).isEqualTo(MatchStatus.FOUND);
}
@Test
public void matchesCommaSeparatedArray() {
DocumentMatcher matcher = new SpringProfileDocumentMatcher("foo", "bar");
Properties properties = getProperties("spring.profiles: [bar, spam]");
assertThat(matcher.matches(properties)).isEqualTo(MatchStatus.FOUND);
}
@Test
public void matchesList() {
DocumentMatcher matcher = new SpringProfileDocumentMatcher("foo", "bar");
Properties properties = getProperties(
String.format("spring.profiles:%n - bar%n - spam"));
assertThat(matcher.matches(properties)).isEqualTo(MatchStatus.FOUND);
}
@Test
public void noMatchingProfiles() {
DocumentMatcher matcher = new SpringProfileDocumentMatcher("foo", "bar");
Properties properties = getProperties("spring.profiles: baz,blah");
assertThat(matcher.matches(properties)).isEqualTo(MatchStatus.NOT_FOUND);
}
@Test
public void inverseMatchSingle() {
DocumentMatcher matcher = new SpringProfileDocumentMatcher("foo", "bar");
Properties properties = getProperties("spring.profiles: '!baz'");
assertThat(matcher.matches(properties)).isEqualTo(MatchStatus.FOUND);
}
@Test
public void testInverseMatchMulti() {
DocumentMatcher matcher = new SpringProfileDocumentMatcher("foo", "bar");
Properties properties = getProperties("spring.profiles: '!baz,!blah'");
assertThat(matcher.matches(properties)).isEqualTo(MatchStatus.FOUND);
}
@Test
public void negatedWithMatch() {
DocumentMatcher matcher = new SpringProfileDocumentMatcher("foo", "bar", "blah");
Properties properties = getProperties("spring.profiles: '!baz,blah'");
assertThat(matcher.matches(properties)).isEqualTo(MatchStatus.FOUND);
}
@Test
public void negatedWithNoMatch() {
DocumentMatcher matcher = new SpringProfileDocumentMatcher("foo", "bar", "blah");
Properties properties = getProperties("spring.profiles: '!baz,another'");
assertThat(matcher.matches(properties)).isEqualTo(MatchStatus.NOT_FOUND);
}
@Test
public void negatedTrumpsMatching() {
DocumentMatcher matcher = new SpringProfileDocumentMatcher("foo", "baz", "blah");
Properties properties = getProperties("spring.profiles: '!baz,blah'");
assertThat(matcher.matches(properties)).isEqualTo(MatchStatus.NOT_FOUND);
}
private Properties getProperties(String values) {
YamlPropertiesFactoryBean yamlPropertiesFactoryBean = new YamlPropertiesFactoryBean();
ByteArrayResource resource = new ByteArrayResource(values.getBytes());
yamlPropertiesFactoryBean.setResources(resource);
yamlPropertiesFactoryBean.afterPropertiesSet();
return yamlPropertiesFactoryBean.getObject();
}
}

@ -0,0 +1,20 @@
---
my:
property: fromyamlfile
other: notempty
---
spring:
profiles: dev
my:
property: fromdevprofile
---
spring:
profiles: other
my:
property: fromotherprofile
---
spring:
profiles: "!other"
my:
property: fromnototherprofile
notother: foo
Loading…
Cancel
Save