From cfa26735d26bd94c885787aa0bba3599b233e846 Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Wed, 12 May 2021 16:28:28 -0700 Subject: [PATCH] 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 --- .../boot/context/config/Profiles.java | 106 +++++++++++++----- .../boot/context/config/ProfilesTests.java | 34 +++++- 2 files changed, 111 insertions(+), 29 deletions(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/Profiles.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/Profiles.java index 4f41785cdf2..c0795ee879a 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/Profiles.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/Profiles.java @@ -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 { private static final Bindable> STRING_STRINGS_MAP = Bindable .of(ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, String.class)); - private static final Set UNSET_ACTIVE = Collections.emptySet(); - - private static final Set UNSET_DEFAULT = Collections.singleton("default"); + private static final Bindable> STRING_SET = Bindable.setOf(String.class); private final MultiValueMap groups; @@ -86,32 +85,42 @@ public class Profiles implements Iterable { private List getActivatedProfiles(Environment environment, Binder binder, Collection 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 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 supplier, String propertyName, - Set unset) { - String propertyValue = environment.getProperty(propertyName); - if (hasExplicit(supplier, propertyValue, unset)) { - return supplier.get(); + private Collection getProfiles(Environment environment, Binder binder, Type type) { + String environmentPropertyValue = environment.getProperty(type.getName()); + Set environmentPropertyProfiles = (!StringUtils.hasLength(environmentPropertyValue)) + ? Collections.emptySet() + : StringUtils.commaDelimitedListToSet(StringUtils.trimAllWhitespace(environmentPropertyValue)); + Set environmentProfiles = new LinkedHashSet<>(Arrays.asList(type.get(environment))); + BindResult> 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 supplier, String propertyValue, Set unset) { - Set profiles = new LinkedHashSet<>(Arrays.asList(supplier.get())); - if (!StringUtils.hasLength(propertyValue)) { - return !unset.equals(profiles); + private boolean hasProgrammaticallySetProfiles(Type type, String environmentPropertyValue, + Set environmentPropertyProfiles, Set environmentProfiles) { + if (!StringUtils.hasLength(environmentPropertyValue)) { + return !type.getDefaultValue().equals(environmentProfiles); } - Set propertyProfiles = StringUtils - .commaDelimitedListToSet(StringUtils.trimAllWhitespace(propertyValue)); - return !propertyProfiles.equals(profiles); + return !environmentPropertyProfiles.equals(environmentProfiles); + } + + private Set merge(Set environmentProfiles, Set bound) { + Set result = new LinkedHashSet<>(environmentProfiles); + result.addAll(bound); + return result; } private List expandProfiles(List profiles) { @@ -124,7 +133,7 @@ public class Profiles implements Iterable { asReversedList(this.groups.get(current)).forEach(stack::push); } } - return asUniqueItemList(StringUtils.toStringArray(expandedProfiles)); + return asUniqueItemList(expandedProfiles); } private List asReversedList(List list) { @@ -136,12 +145,12 @@ public class Profiles implements Iterable { return reversed; } - private List asUniqueItemList(String[] array) { - return asUniqueItemList(array, null); + private List asUniqueItemList(Collection strings) { + return asUniqueItemList(strings, null); } - private List asUniqueItemList(String[] array, Collection additional) { - LinkedHashSet uniqueItems = new LinkedHashSet<>(Arrays.asList(array)); + private List asUniqueItemList(Collection strings, Collection additional) { + LinkedHashSet uniqueItems = new LinkedHashSet<>(strings); if (!CollectionUtils.isEmpty(additional)) { uniqueItems.addAll(additional); } @@ -198,4 +207,49 @@ public class Profiles implements Iterable { 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 getter; + + private final boolean mergeWithEnvironmentProfiles; + + private final String name; + + private final Set defaultValue; + + Type(String name, Function getter, boolean mergeWithEnvironmentProfiles, + Set 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 getDefaultValue() { + return this.defaultValue; + } + + boolean isMergeWithEnvironmentProfiles() { + return this.mergeWithEnvironmentProfiles; + } + + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ProfilesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ProfilesTests.java index e0a9bb456c3..015aedd4c03 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ProfilesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ProfilesTests.java @@ -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 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 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");