Skip to content

Commit cfa2673

Browse files
mbhavephilwebb
andcommitted
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 <[email protected]>
1 parent e8950c7 commit cfa2673

File tree

2 files changed

+111
-29
lines changed
  • spring-boot-project/spring-boot/src

2 files changed

+111
-29
lines changed

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

Lines changed: 80 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@
2626
import java.util.LinkedHashSet;
2727
import java.util.List;
2828
import java.util.Set;
29-
import java.util.function.Supplier;
29+
import java.util.function.Function;
3030

31+
import org.springframework.boot.context.properties.bind.BindResult;
3132
import org.springframework.boot.context.properties.bind.Bindable;
3233
import org.springframework.boot.context.properties.bind.Binder;
3334
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
@@ -61,9 +62,7 @@ public class Profiles implements Iterable<String> {
6162
private static final Bindable<MultiValueMap<String, String>> STRING_STRINGS_MAP = Bindable
6263
.of(ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, String.class));
6364

64-
private static final Set<String> UNSET_ACTIVE = Collections.emptySet();
65-
66-
private static final Set<String> UNSET_DEFAULT = Collections.singleton("default");
65+
private static final Bindable<Set<String>> STRING_SET = Bindable.setOf(String.class);
6766

6867
private final MultiValueMap<String, String> groups;
6968

@@ -86,32 +85,42 @@ public class Profiles implements Iterable<String> {
8685

8786
private List<String> getActivatedProfiles(Environment environment, Binder binder,
8887
Collection<String> additionalProfiles) {
89-
return asUniqueItemList(get(environment, binder, environment::getActiveProfiles,
90-
AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, UNSET_ACTIVE), additionalProfiles);
88+
return asUniqueItemList(getProfiles(environment, binder, Type.ACTIVE), additionalProfiles);
9189
}
9290

9391
private List<String> getDefaultProfiles(Environment environment, Binder binder) {
94-
return asUniqueItemList(get(environment, binder, environment::getDefaultProfiles,
95-
AbstractEnvironment.DEFAULT_PROFILES_PROPERTY_NAME, UNSET_DEFAULT));
92+
return asUniqueItemList(getProfiles(environment, binder, Type.DEFAULT));
9693
}
9794

98-
private String[] get(Environment environment, Binder binder, Supplier<String[]> supplier, String propertyName,
99-
Set<String> unset) {
100-
String propertyValue = environment.getProperty(propertyName);
101-
if (hasExplicit(supplier, propertyValue, unset)) {
102-
return supplier.get();
95+
private Collection<String> getProfiles(Environment environment, Binder binder, Type type) {
96+
String environmentPropertyValue = environment.getProperty(type.getName());
97+
Set<String> environmentPropertyProfiles = (!StringUtils.hasLength(environmentPropertyValue))
98+
? Collections.emptySet()
99+
: StringUtils.commaDelimitedListToSet(StringUtils.trimAllWhitespace(environmentPropertyValue));
100+
Set<String> environmentProfiles = new LinkedHashSet<>(Arrays.asList(type.get(environment)));
101+
BindResult<Set<String>> boundProfiles = binder.bind(type.getName(), STRING_SET);
102+
if (hasProgrammaticallySetProfiles(type, environmentPropertyValue, environmentPropertyProfiles,
103+
environmentProfiles)) {
104+
if (!type.isMergeWithEnvironmentProfiles() || !boundProfiles.isBound()) {
105+
return environmentProfiles;
106+
}
107+
return boundProfiles.map((bound) -> merge(environmentProfiles, bound)).get();
103108
}
104-
return binder.bind(propertyName, String[].class).orElseGet(() -> StringUtils.toStringArray(unset));
109+
return boundProfiles.orElse(type.getDefaultValue());
105110
}
106111

107-
private boolean hasExplicit(Supplier<String[]> supplier, String propertyValue, Set<String> unset) {
108-
Set<String> profiles = new LinkedHashSet<>(Arrays.asList(supplier.get()));
109-
if (!StringUtils.hasLength(propertyValue)) {
110-
return !unset.equals(profiles);
112+
private boolean hasProgrammaticallySetProfiles(Type type, String environmentPropertyValue,
113+
Set<String> environmentPropertyProfiles, Set<String> environmentProfiles) {
114+
if (!StringUtils.hasLength(environmentPropertyValue)) {
115+
return !type.getDefaultValue().equals(environmentProfiles);
111116
}
112-
Set<String> propertyProfiles = StringUtils
113-
.commaDelimitedListToSet(StringUtils.trimAllWhitespace(propertyValue));
114-
return !propertyProfiles.equals(profiles);
117+
return !environmentPropertyProfiles.equals(environmentProfiles);
118+
}
119+
120+
private Set<String> merge(Set<String> environmentProfiles, Set<String> bound) {
121+
Set<String> result = new LinkedHashSet<>(environmentProfiles);
122+
result.addAll(bound);
123+
return result;
115124
}
116125

117126
private List<String> expandProfiles(List<String> profiles) {
@@ -124,7 +133,7 @@ private List<String> expandProfiles(List<String> profiles) {
124133
asReversedList(this.groups.get(current)).forEach(stack::push);
125134
}
126135
}
127-
return asUniqueItemList(StringUtils.toStringArray(expandedProfiles));
136+
return asUniqueItemList(expandedProfiles);
128137
}
129138

130139
private List<String> asReversedList(List<String> list) {
@@ -136,12 +145,12 @@ private List<String> asReversedList(List<String> list) {
136145
return reversed;
137146
}
138147

139-
private List<String> asUniqueItemList(String[] array) {
140-
return asUniqueItemList(array, null);
148+
private List<String> asUniqueItemList(Collection<String> strings) {
149+
return asUniqueItemList(strings, null);
141150
}
142151

143-
private List<String> asUniqueItemList(String[] array, Collection<String> additional) {
144-
LinkedHashSet<String> uniqueItems = new LinkedHashSet<>(Arrays.asList(array));
152+
private List<String> asUniqueItemList(Collection<String> strings, Collection<String> additional) {
153+
LinkedHashSet<String> uniqueItems = new LinkedHashSet<>(strings);
145154
if (!CollectionUtils.isEmpty(additional)) {
146155
uniqueItems.addAll(additional);
147156
}
@@ -198,4 +207,49 @@ public String toString() {
198207
return creator.toString();
199208
}
200209

210+
/**
211+
* A profiles type that can be obtained.
212+
*/
213+
private enum Type {
214+
215+
ACTIVE(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, Environment::getActiveProfiles, true,
216+
Collections.emptySet()),
217+
218+
DEFAULT(AbstractEnvironment.DEFAULT_PROFILES_PROPERTY_NAME, Environment::getDefaultProfiles, false,
219+
Collections.singleton("default"));
220+
221+
private final Function<Environment, String[]> getter;
222+
223+
private final boolean mergeWithEnvironmentProfiles;
224+
225+
private final String name;
226+
227+
private final Set<String> defaultValue;
228+
229+
Type(String name, Function<Environment, String[]> getter, boolean mergeWithEnvironmentProfiles,
230+
Set<String> defaultValue) {
231+
this.name = name;
232+
this.getter = getter;
233+
this.mergeWithEnvironmentProfiles = mergeWithEnvironmentProfiles;
234+
this.defaultValue = defaultValue;
235+
}
236+
237+
String getName() {
238+
return this.name;
239+
}
240+
241+
String[] get(Environment environment) {
242+
return this.getter.apply(environment);
243+
}
244+
245+
Set<String> getDefaultValue() {
246+
return this.defaultValue;
247+
}
248+
249+
boolean isMergeWithEnvironmentProfiles() {
250+
return this.mergeWithEnvironmentProfiles;
251+
}
252+
253+
}
254+
201255
}

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ProfilesTests.java

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,16 @@
1616

1717
package org.springframework.boot.context.config;
1818

19+
import java.util.ArrayList;
1920
import java.util.Arrays;
2021
import java.util.Collections;
22+
import java.util.List;
2123

2224
import org.junit.jupiter.api.Test;
2325

2426
import org.springframework.boot.context.properties.bind.Binder;
27+
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
28+
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
2529
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
2630
import org.springframework.core.env.Environment;
2731
import org.springframework.mock.env.MockEnvironment;
@@ -69,6 +73,18 @@ void getActiveWhenEnvironmentProfilesAndBinderProperty() {
6973
Binder binder = new Binder(
7074
new MapConfigurationPropertySource(Collections.singletonMap("spring.profiles.active", "d,e,f")));
7175
Profiles profiles = new Profiles(environment, binder, null);
76+
assertThat(profiles.getActive()).containsExactly("a", "b", "c", "d", "e", "f");
77+
}
78+
79+
@Test
80+
void getActiveWhenEnvironmentProfilesAndBinderPropertyShouldReturnEnvironmentProperty() {
81+
MockEnvironment environment = new MockEnvironment();
82+
environment.setProperty("spring.profiles.active", "a,b,c");
83+
List<ConfigurationPropertySource> sources = new ArrayList<>();
84+
ConfigurationPropertySources.get(environment).forEach(sources::add);
85+
sources.add(new MapConfigurationPropertySource(Collections.singletonMap("spring.profiles.active", "d,e,f")));
86+
Binder binder = new Binder(sources);
87+
Profiles profiles = new Profiles(environment, binder, null);
7288
assertThat(profiles.getActive()).containsExactly("a", "b", "c");
7389
}
7490

@@ -79,7 +95,7 @@ void getActiveWhenEnvironmentProfilesAndEnvironmentProperty() {
7995
environment.setProperty("spring.profiles.active", "d,e,f");
8096
Binder binder = Binder.get(environment);
8197
Profiles profiles = new Profiles(environment, binder, null);
82-
assertThat(profiles.getActive()).containsExactly("a", "b", "c");
98+
assertThat(profiles.getActive()).containsExactly("a", "b", "c", "d", "e", "f");
8399
}
84100

85101
@Test
@@ -102,7 +118,7 @@ void getActiveWhenEnvironmentProfilesInBindNotationAndEnvironmentPropertyReturns
102118
environment.setProperty("spring.profiles.active[2]", "f");
103119
Binder binder = Binder.get(environment);
104120
Profiles profiles = new Profiles(environment, binder, null);
105-
assertThat(profiles.getActive()).containsExactly("a", "b", "c");
121+
assertThat(profiles.getActive()).containsExactly("a", "b", "c", "d", "e", "f");
106122
}
107123

108124
@Test
@@ -150,6 +166,18 @@ void getDefaultWhenNoEnvironmentProfilesAndBinderProperty() {
150166
assertThat(profiles.getDefault()).containsExactly("a", "b", "c");
151167
}
152168

169+
@Test
170+
void getDefaultWhenDefaultEnvironmentProfileAndBinderProperty() {
171+
MockEnvironment environment = new MockEnvironment();
172+
environment.setProperty("spring.profiles.default", "default");
173+
List<ConfigurationPropertySource> sources = new ArrayList<>();
174+
ConfigurationPropertySources.get(environment).forEach(sources::add);
175+
sources.add(new MapConfigurationPropertySource(Collections.singletonMap("spring.profiles.default", "a,b,c")));
176+
Binder binder = new Binder(sources);
177+
Profiles profiles = new Profiles(environment, binder, null);
178+
assertThat(profiles.getDefault()).containsExactly("default");
179+
}
180+
153181
@Test
154182
void getDefaultWhenNoEnvironmentProfilesAndEnvironmentProperty() {
155183
MockEnvironment environment = new MockEnvironment();
@@ -210,7 +238,7 @@ void getDefaultWithProfileGroups() {
210238
}
211239

212240
@Test
213-
void getDefaultWhenEnvironmentProfilesInBindNotationAndEnvironmentPropertyReturnsEnvironmentProfiles() {
241+
void getDefaultWhenEnvironmentProfilesInBindNotationAndEnvironmentPropertyReturnsBoth() {
214242
MockEnvironment environment = new MockEnvironment();
215243
environment.setDefaultProfiles("a", "b", "c");
216244
environment.setProperty("spring.profiles.default[0]", "d");

0 commit comments

Comments
 (0)