Commit cfa26735 authored by Madhura Bhave's avatar Madhura Bhave Committed by Phillip Webb

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: 's avatarPhillip Webb <pwebb@vmware.com>
parent e8950c78
...@@ -26,8 +26,9 @@ import java.util.Iterator; ...@@ -26,8 +26,9 @@ import java.util.Iterator;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Set; 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.Bindable;
import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
...@@ -61,9 +62,7 @@ public class Profiles implements Iterable<String> { ...@@ -61,9 +62,7 @@ public class Profiles implements Iterable<String> {
private static final Bindable<MultiValueMap<String, String>> STRING_STRINGS_MAP = Bindable private static final Bindable<MultiValueMap<String, String>> STRING_STRINGS_MAP = Bindable
.of(ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, String.class)); .of(ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, String.class));
private static final Set<String> UNSET_ACTIVE = Collections.emptySet(); private static final Bindable<Set<String>> STRING_SET = Bindable.setOf(String.class);
private static final Set<String> UNSET_DEFAULT = Collections.singleton("default");
private final MultiValueMap<String, String> groups; private final MultiValueMap<String, String> groups;
...@@ -86,32 +85,42 @@ public class Profiles implements Iterable<String> { ...@@ -86,32 +85,42 @@ public class Profiles implements Iterable<String> {
private List<String> getActivatedProfiles(Environment environment, Binder binder, private List<String> getActivatedProfiles(Environment environment, Binder binder,
Collection<String> additionalProfiles) { Collection<String> additionalProfiles) {
return asUniqueItemList(get(environment, binder, environment::getActiveProfiles, return asUniqueItemList(getProfiles(environment, binder, Type.ACTIVE), additionalProfiles);
AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, UNSET_ACTIVE), additionalProfiles);
} }
private List<String> getDefaultProfiles(Environment environment, Binder binder) { private List<String> getDefaultProfiles(Environment environment, Binder binder) {
return asUniqueItemList(get(environment, binder, environment::getDefaultProfiles, return asUniqueItemList(getProfiles(environment, binder, Type.DEFAULT));
AbstractEnvironment.DEFAULT_PROFILES_PROPERTY_NAME, UNSET_DEFAULT));
} }
private String[] get(Environment environment, Binder binder, Supplier<String[]> supplier, String propertyName, private Collection<String> getProfiles(Environment environment, Binder binder, Type type) {
Set<String> unset) { String environmentPropertyValue = environment.getProperty(type.getName());
String propertyValue = environment.getProperty(propertyName); Set<String> environmentPropertyProfiles = (!StringUtils.hasLength(environmentPropertyValue))
if (hasExplicit(supplier, propertyValue, unset)) { ? Collections.emptySet()
return supplier.get(); : 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) { private boolean hasProgrammaticallySetProfiles(Type type, String environmentPropertyValue,
Set<String> profiles = new LinkedHashSet<>(Arrays.asList(supplier.get())); Set<String> environmentPropertyProfiles, Set<String> environmentProfiles) {
if (!StringUtils.hasLength(propertyValue)) { if (!StringUtils.hasLength(environmentPropertyValue)) {
return !unset.equals(profiles); return !type.getDefaultValue().equals(environmentProfiles);
} }
Set<String> propertyProfiles = StringUtils return !environmentPropertyProfiles.equals(environmentProfiles);
.commaDelimitedListToSet(StringUtils.trimAllWhitespace(propertyValue)); }
return !propertyProfiles.equals(profiles);
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) { private List<String> expandProfiles(List<String> profiles) {
...@@ -124,7 +133,7 @@ public class Profiles implements Iterable<String> { ...@@ -124,7 +133,7 @@ public class Profiles implements Iterable<String> {
asReversedList(this.groups.get(current)).forEach(stack::push); asReversedList(this.groups.get(current)).forEach(stack::push);
} }
} }
return asUniqueItemList(StringUtils.toStringArray(expandedProfiles)); return asUniqueItemList(expandedProfiles);
} }
private List<String> asReversedList(List<String> list) { private List<String> asReversedList(List<String> list) {
...@@ -136,12 +145,12 @@ public class Profiles implements Iterable<String> { ...@@ -136,12 +145,12 @@ public class Profiles implements Iterable<String> {
return reversed; return reversed;
} }
private List<String> asUniqueItemList(String[] array) { private List<String> asUniqueItemList(Collection<String> strings) {
return asUniqueItemList(array, null); return asUniqueItemList(strings, null);
} }
private List<String> asUniqueItemList(String[] array, Collection<String> additional) { private List<String> asUniqueItemList(Collection<String> strings, Collection<String> additional) {
LinkedHashSet<String> uniqueItems = new LinkedHashSet<>(Arrays.asList(array)); LinkedHashSet<String> uniqueItems = new LinkedHashSet<>(strings);
if (!CollectionUtils.isEmpty(additional)) { if (!CollectionUtils.isEmpty(additional)) {
uniqueItems.addAll(additional); uniqueItems.addAll(additional);
} }
...@@ -198,4 +207,49 @@ public class Profiles implements Iterable<String> { ...@@ -198,4 +207,49 @@ public class Profiles implements Iterable<String> {
return creator.toString(); 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 @@ ...@@ -16,12 +16,16 @@
package org.springframework.boot.context.config; package org.springframework.boot.context.config;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.context.properties.bind.Binder; 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.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.mock.env.MockEnvironment; import org.springframework.mock.env.MockEnvironment;
...@@ -69,6 +73,18 @@ class ProfilesTests { ...@@ -69,6 +73,18 @@ class ProfilesTests {
Binder binder = new Binder( Binder binder = new Binder(
new MapConfigurationPropertySource(Collections.singletonMap("spring.profiles.active", "d,e,f"))); new MapConfigurationPropertySource(Collections.singletonMap("spring.profiles.active", "d,e,f")));
Profiles profiles = new Profiles(environment, binder, null); 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"); assertThat(profiles.getActive()).containsExactly("a", "b", "c");
} }
...@@ -79,7 +95,7 @@ class ProfilesTests { ...@@ -79,7 +95,7 @@ class ProfilesTests {
environment.setProperty("spring.profiles.active", "d,e,f"); environment.setProperty("spring.profiles.active", "d,e,f");
Binder binder = Binder.get(environment); Binder binder = Binder.get(environment);
Profiles profiles = new Profiles(environment, binder, null); 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 @Test
...@@ -102,7 +118,7 @@ class ProfilesTests { ...@@ -102,7 +118,7 @@ class ProfilesTests {
environment.setProperty("spring.profiles.active[2]", "f"); environment.setProperty("spring.profiles.active[2]", "f");
Binder binder = Binder.get(environment); Binder binder = Binder.get(environment);
Profiles profiles = new Profiles(environment, binder, null); 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 @Test
...@@ -150,6 +166,18 @@ class ProfilesTests { ...@@ -150,6 +166,18 @@ class ProfilesTests {
assertThat(profiles.getDefault()).containsExactly("a", "b", "c"); 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 @Test
void getDefaultWhenNoEnvironmentProfilesAndEnvironmentProperty() { void getDefaultWhenNoEnvironmentProfilesAndEnvironmentProperty() {
MockEnvironment environment = new MockEnvironment(); MockEnvironment environment = new MockEnvironment();
...@@ -210,7 +238,7 @@ class ProfilesTests { ...@@ -210,7 +238,7 @@ class ProfilesTests {
} }
@Test @Test
void getDefaultWhenEnvironmentProfilesInBindNotationAndEnvironmentPropertyReturnsEnvironmentProfiles() { void getDefaultWhenEnvironmentProfilesInBindNotationAndEnvironmentPropertyReturnsBoth() {
MockEnvironment environment = new MockEnvironment(); MockEnvironment environment = new MockEnvironment();
environment.setDefaultProfiles("a", "b", "c"); environment.setDefaultProfiles("a", "b", "c");
environment.setProperty("spring.profiles.default[0]", "d"); environment.setProperty("spring.profiles.default[0]", "d");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment