Merge programmatically set active profiles

Update `Profiles` so that any profiles set programmatically on the
`Environment` are merged with `spring.profiles.active` properties.

Fixes gh-26151

Co-authored-by: Phillip Webb <pwebb@vmware.com>
pull/26691/head
Madhura Bhave 4 years ago committed by Phillip Webb
parent e8950c7808
commit cfa26735d2

@ -26,8 +26,9 @@ import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import java.util.function.Function;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
@ -61,9 +62,7 @@ public class Profiles implements Iterable<String> {
private static final Bindable<MultiValueMap<String, String>> STRING_STRINGS_MAP = Bindable
.of(ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, String.class));
private static final Set<String> UNSET_ACTIVE = Collections.emptySet();
private static final Set<String> UNSET_DEFAULT = Collections.singleton("default");
private static final Bindable<Set<String>> STRING_SET = Bindable.setOf(String.class);
private final MultiValueMap<String, String> groups;
@ -86,32 +85,42 @@ public class Profiles implements Iterable<String> {
private List<String> getActivatedProfiles(Environment environment, Binder binder,
Collection<String> additionalProfiles) {
return asUniqueItemList(get(environment, binder, environment::getActiveProfiles,
AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, UNSET_ACTIVE), additionalProfiles);
return asUniqueItemList(getProfiles(environment, binder, Type.ACTIVE), additionalProfiles);
}
private List<String> getDefaultProfiles(Environment environment, Binder binder) {
return asUniqueItemList(get(environment, binder, environment::getDefaultProfiles,
AbstractEnvironment.DEFAULT_PROFILES_PROPERTY_NAME, UNSET_DEFAULT));
return asUniqueItemList(getProfiles(environment, binder, Type.DEFAULT));
}
private String[] get(Environment environment, Binder binder, Supplier<String[]> supplier, String propertyName,
Set<String> unset) {
String propertyValue = environment.getProperty(propertyName);
if (hasExplicit(supplier, propertyValue, unset)) {
return supplier.get();
private Collection<String> getProfiles(Environment environment, Binder binder, Type type) {
String environmentPropertyValue = environment.getProperty(type.getName());
Set<String> environmentPropertyProfiles = (!StringUtils.hasLength(environmentPropertyValue))
? Collections.emptySet()
: StringUtils.commaDelimitedListToSet(StringUtils.trimAllWhitespace(environmentPropertyValue));
Set<String> environmentProfiles = new LinkedHashSet<>(Arrays.asList(type.get(environment)));
BindResult<Set<String>> boundProfiles = binder.bind(type.getName(), STRING_SET);
if (hasProgrammaticallySetProfiles(type, environmentPropertyValue, environmentPropertyProfiles,
environmentProfiles)) {
if (!type.isMergeWithEnvironmentProfiles() || !boundProfiles.isBound()) {
return environmentProfiles;
}
return boundProfiles.map((bound) -> merge(environmentProfiles, bound)).get();
}
return binder.bind(propertyName, String[].class).orElseGet(() -> StringUtils.toStringArray(unset));
return boundProfiles.orElse(type.getDefaultValue());
}
private boolean hasExplicit(Supplier<String[]> supplier, String propertyValue, Set<String> unset) {
Set<String> profiles = new LinkedHashSet<>(Arrays.asList(supplier.get()));
if (!StringUtils.hasLength(propertyValue)) {
return !unset.equals(profiles);
private boolean hasProgrammaticallySetProfiles(Type type, String environmentPropertyValue,
Set<String> environmentPropertyProfiles, Set<String> environmentProfiles) {
if (!StringUtils.hasLength(environmentPropertyValue)) {
return !type.getDefaultValue().equals(environmentProfiles);
}
Set<String> propertyProfiles = StringUtils
.commaDelimitedListToSet(StringUtils.trimAllWhitespace(propertyValue));
return !propertyProfiles.equals(profiles);
return !environmentPropertyProfiles.equals(environmentProfiles);
}
private Set<String> merge(Set<String> environmentProfiles, Set<String> bound) {
Set<String> result = new LinkedHashSet<>(environmentProfiles);
result.addAll(bound);
return result;
}
private List<String> expandProfiles(List<String> profiles) {
@ -124,7 +133,7 @@ public class Profiles implements Iterable<String> {
asReversedList(this.groups.get(current)).forEach(stack::push);
}
}
return asUniqueItemList(StringUtils.toStringArray(expandedProfiles));
return asUniqueItemList(expandedProfiles);
}
private List<String> asReversedList(List<String> list) {
@ -136,12 +145,12 @@ public class Profiles implements Iterable<String> {
return reversed;
}
private List<String> asUniqueItemList(String[] array) {
return asUniqueItemList(array, null);
private List<String> asUniqueItemList(Collection<String> strings) {
return asUniqueItemList(strings, null);
}
private List<String> asUniqueItemList(String[] array, Collection<String> additional) {
LinkedHashSet<String> uniqueItems = new LinkedHashSet<>(Arrays.asList(array));
private List<String> asUniqueItemList(Collection<String> strings, Collection<String> additional) {
LinkedHashSet<String> uniqueItems = new LinkedHashSet<>(strings);
if (!CollectionUtils.isEmpty(additional)) {
uniqueItems.addAll(additional);
}
@ -198,4 +207,49 @@ public class Profiles implements Iterable<String> {
return creator.toString();
}
/**
* A profiles type that can be obtained.
*/
private enum Type {
ACTIVE(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, Environment::getActiveProfiles, true,
Collections.emptySet()),
DEFAULT(AbstractEnvironment.DEFAULT_PROFILES_PROPERTY_NAME, Environment::getDefaultProfiles, false,
Collections.singleton("default"));
private final Function<Environment, String[]> getter;
private final boolean mergeWithEnvironmentProfiles;
private final String name;
private final Set<String> defaultValue;
Type(String name, Function<Environment, String[]> getter, boolean mergeWithEnvironmentProfiles,
Set<String> defaultValue) {
this.name = name;
this.getter = getter;
this.mergeWithEnvironmentProfiles = mergeWithEnvironmentProfiles;
this.defaultValue = defaultValue;
}
String getName() {
return this.name;
}
String[] get(Environment environment) {
return this.getter.apply(environment);
}
Set<String> getDefaultValue() {
return this.defaultValue;
}
boolean isMergeWithEnvironmentProfiles() {
return this.mergeWithEnvironmentProfiles;
}
}
}

@ -16,12 +16,16 @@
package org.springframework.boot.context.config;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.core.env.Environment;
import org.springframework.mock.env.MockEnvironment;
@ -69,6 +73,18 @@ class ProfilesTests {
Binder binder = new Binder(
new MapConfigurationPropertySource(Collections.singletonMap("spring.profiles.active", "d,e,f")));
Profiles profiles = new Profiles(environment, binder, null);
assertThat(profiles.getActive()).containsExactly("a", "b", "c", "d", "e", "f");
}
@Test
void getActiveWhenEnvironmentProfilesAndBinderPropertyShouldReturnEnvironmentProperty() {
MockEnvironment environment = new MockEnvironment();
environment.setProperty("spring.profiles.active", "a,b,c");
List<ConfigurationPropertySource> sources = new ArrayList<>();
ConfigurationPropertySources.get(environment).forEach(sources::add);
sources.add(new MapConfigurationPropertySource(Collections.singletonMap("spring.profiles.active", "d,e,f")));
Binder binder = new Binder(sources);
Profiles profiles = new Profiles(environment, binder, null);
assertThat(profiles.getActive()).containsExactly("a", "b", "c");
}
@ -79,7 +95,7 @@ class ProfilesTests {
environment.setProperty("spring.profiles.active", "d,e,f");
Binder binder = Binder.get(environment);
Profiles profiles = new Profiles(environment, binder, null);
assertThat(profiles.getActive()).containsExactly("a", "b", "c");
assertThat(profiles.getActive()).containsExactly("a", "b", "c", "d", "e", "f");
}
@Test
@ -102,7 +118,7 @@ class ProfilesTests {
environment.setProperty("spring.profiles.active[2]", "f");
Binder binder = Binder.get(environment);
Profiles profiles = new Profiles(environment, binder, null);
assertThat(profiles.getActive()).containsExactly("a", "b", "c");
assertThat(profiles.getActive()).containsExactly("a", "b", "c", "d", "e", "f");
}
@Test
@ -150,6 +166,18 @@ class ProfilesTests {
assertThat(profiles.getDefault()).containsExactly("a", "b", "c");
}
@Test
void getDefaultWhenDefaultEnvironmentProfileAndBinderProperty() {
MockEnvironment environment = new MockEnvironment();
environment.setProperty("spring.profiles.default", "default");
List<ConfigurationPropertySource> sources = new ArrayList<>();
ConfigurationPropertySources.get(environment).forEach(sources::add);
sources.add(new MapConfigurationPropertySource(Collections.singletonMap("spring.profiles.default", "a,b,c")));
Binder binder = new Binder(sources);
Profiles profiles = new Profiles(environment, binder, null);
assertThat(profiles.getDefault()).containsExactly("default");
}
@Test
void getDefaultWhenNoEnvironmentProfilesAndEnvironmentProperty() {
MockEnvironment environment = new MockEnvironment();
@ -210,7 +238,7 @@ class ProfilesTests {
}
@Test
void getDefaultWhenEnvironmentProfilesInBindNotationAndEnvironmentPropertyReturnsEnvironmentProfiles() {
void getDefaultWhenEnvironmentProfilesInBindNotationAndEnvironmentPropertyReturnsBoth() {
MockEnvironment environment = new MockEnvironment();
environment.setDefaultProfiles("a", "b", "c");
environment.setProperty("spring.profiles.default[0]", "d");

Loading…
Cancel
Save