Log included profiles according to the processing order

This commit includes some refactoring of active profiles
processing. Previously, there was a LIFO Queue for adding active
profiles. Profiles that were added last, were processed first.
Because of this reverse ordering, profiles were prepended to the
environment to preserve the order in which they were logged.
This however didn't work for "included" profiles as they were
prepended to the environment even though they were processed after
the active profile. In this commit, profiles are processed in a FIFO manner
and processed as they're found.

Fixes gh-11380
pull/13066/head
Madhura Bhave 7 years ago
parent 4ef7ea324f
commit cab9bff4f4

@ -26,7 +26,6 @@ import java.util.LinkedHashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Queue;
import java.util.Set; import java.util.Set;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -302,7 +301,7 @@ public class ConfigFileApplicationListener
private final List<PropertySourceLoader> propertySourceLoaders; private final List<PropertySourceLoader> propertySourceLoaders;
private Queue<Profile> profiles; private LinkedList<Profile> profiles;
private List<Profile> processedProfiles; private List<Profile> processedProfiles;
@ -321,7 +320,7 @@ public class ConfigFileApplicationListener
} }
public void load() { public void load() {
this.profiles = Collections.asLifoQueue(new LinkedList<Profile>()); this.profiles = new LinkedList<>();
this.processedProfiles = new LinkedList<>(); this.processedProfiles = new LinkedList<>();
this.activatedProfiles = false; this.activatedProfiles = false;
this.loaded = new LinkedHashMap<>(); this.loaded = new LinkedHashMap<>();
@ -343,62 +342,62 @@ public class ConfigFileApplicationListener
* properties that are already set. * properties that are already set.
*/ */
private void initializeProfiles() { private void initializeProfiles() {
Set<Profile> initialActiveProfiles = initializeActiveProfiles(); //The default profile for these purposes is represented as null. We add it
this.profiles.addAll(getUnprocessedActiveProfiles(initialActiveProfiles)); // first so that it is processed first and has lowest priority.
if (this.profiles.isEmpty()) { this.profiles.add(null);
Set<Profile> activatedViaProperty = getProfilesActivatedViaActiveProfileProperty();
processOtherActiveProfiles(activatedViaProperty);
// Any pre-existing active activeProfiles set via property sources (e.g. System
// properties) take precedence over those added in config files.
addActiveProfiles(activatedViaProperty);
if (this.profiles.size() == 1) { //only has null profile
for (String defaultProfileName : this.environment.getDefaultProfiles()) { for (String defaultProfileName : this.environment.getDefaultProfiles()) {
Profile defaultProfile = new Profile(defaultProfileName, true); ConfigFileApplicationListener.Profile defaultProfile = new ConfigFileApplicationListener.Profile(
if (!this.profiles.contains(defaultProfile)) { defaultProfileName, true);
this.profiles.add(defaultProfile); this.profiles.add(defaultProfile);
}
} }
} }
// The default profile for these purposes is represented as null. We add it
// last so that it is first out of the queue (active profiles will then
// override any settings in the defaults when the list is reversed later).
this.profiles.add(null);
} }
private Set<Profile> initializeActiveProfiles() { private Set<Profile> getProfilesActivatedViaActiveProfileProperty() {
if (!this.environment.containsProperty(ACTIVE_PROFILES_PROPERTY) if (!this.environment.containsProperty(ACTIVE_PROFILES_PROPERTY)
&& !this.environment.containsProperty(INCLUDE_PROFILES_PROPERTY)) { && !this.environment.containsProperty(INCLUDE_PROFILES_PROPERTY)) {
return Collections.emptySet(); return Collections.emptySet();
} }
// Any pre-existing active profiles set via property sources (e.g. System
// properties) take precedence over those added in config files.
Binder binder = Binder.get(this.environment); Binder binder = Binder.get(this.environment);
Set<Profile> activeProfiles = new LinkedHashSet<>(); Set<Profile> activeProfiles = new LinkedHashSet<>();
activeProfiles.addAll(getProfiles(binder, ACTIVE_PROFILES_PROPERTY)); activeProfiles.addAll(getProfiles(binder, ACTIVE_PROFILES_PROPERTY));
activeProfiles.addAll(getProfiles(binder, INCLUDE_PROFILES_PROPERTY)); activeProfiles.addAll(getProfiles(binder, INCLUDE_PROFILES_PROPERTY));
maybeActivateProfiles(activeProfiles);
return activeProfiles; return activeProfiles;
} }
/** private void processOtherActiveProfiles(Set<Profile> activatedViaProperty) {
* Return the active profiles that have not been processed yet. If a profile is List<Profile> otherActiveProfiles = Arrays.stream(this.environment.getActiveProfiles())
* enabled via both {@link #ACTIVE_PROFILES_PROPERTY} and .map(Profile::new)
* {@link ConfigurableEnvironment#addActiveProfile(String)} it needs to be .filter(o -> !activatedViaProperty.contains(o)).collect(Collectors.toList());
* filtered so that the {@link #ACTIVE_PROFILES_PROPERTY} value takes precedence. this.profiles.addAll(otherActiveProfiles);
* <p> }
* Concretely, if the "cloud" profile is enabled via the environment, it will take
* less precedence that any profile set via the {@link #ACTIVE_PROFILES_PROPERTY}. void addActiveProfiles(Set<Profile> profiles) {
* @param initialActiveProfiles the profiles that have been enabled via if (this.activatedProfiles || profiles.isEmpty()) {
* {@link #ACTIVE_PROFILES_PROPERTY} return;
* @return the unprocessed active profiles from the environment to enable
*/
private List<Profile> getUnprocessedActiveProfiles(
Set<Profile> initialActiveProfiles) {
List<Profile> unprocessedActiveProfiles = new ArrayList<>();
for (String profileName : this.environment.getActiveProfiles()) {
Profile profile = new Profile(profileName);
if (!initialActiveProfiles.contains(profile)) {
unprocessedActiveProfiles.add(profile);
}
} }
// Reverse them so the order is the same as from getProfilesForValue() addProfiles(profiles);
// (last one wins when properties are eventually resolved) this.logger.debug("Activated activeProfiles "
Collections.reverse(unprocessedActiveProfiles); + StringUtils.collectionToCommaDelimitedString(profiles));
return unprocessedActiveProfiles; this.activatedProfiles = true;
removeUnprocessedDefaultProfiles();
}
void addProfiles(Set<Profile> profiles) {
for (Profile profile : profiles) {
this.profiles.add(profile);
addProfileToEnvironment(profile.getName());
}
}
private void removeUnprocessedDefaultProfiles() {
this.profiles.removeIf(profile -> (profile != null && profile.isDefaultProfile()));
} }
private DocumentFilter getPositiveProfileFilter(Profile profile) { private DocumentFilter getPositiveProfileFilter(Profile profile) {
@ -520,7 +519,7 @@ public class ConfigFileApplicationListener
List<Document> loaded = new ArrayList<>(); List<Document> loaded = new ArrayList<>();
for (Document document : documents) { for (Document document : documents) {
if (filter.match(document)) { if (filter.match(document)) {
maybeActivateProfiles(document.getActiveProfiles()); addActiveProfiles(document.getActiveProfiles());
addProfiles(document.getIncludeProfiles()); addProfiles(document.getIncludeProfiles());
loaded.add(document); loaded.add(document);
} }
@ -587,58 +586,16 @@ public class ConfigFileApplicationListener
for (String profileName : profileNames) { for (String profileName : profileNames) {
profiles.add(new Profile(profileName)); profiles.add(new Profile(profileName));
} }
Collections.reverse(profiles);
return new LinkedHashSet<>(profiles); return new LinkedHashSet<>(profiles);
} }
private void maybeActivateProfiles(Set<Profile> profiles) { private void addProfileToEnvironment(String profile) {
if (profiles.isEmpty()) {
return;
}
if (this.activatedProfiles) {
this.logger.debug("Profiles already activated, '" + profiles
+ "' will not be applied");
return;
}
addProfiles(profiles);
this.logger.debug("Activated profiles "
+ StringUtils.collectionToCommaDelimitedString(profiles));
this.activatedProfiles = true;
removeUnprocessedDefaultProfiles();
}
private void removeUnprocessedDefaultProfiles() {
this.profiles.removeIf(Profile::isDefaultProfile);
}
private void addProfiles(Set<Profile> profiles) {
for (Profile profile : profiles) {
this.profiles.add(profile);
if (!environmentHasActiveProfile(profile.getName())) {
// If it's already accepted we assume the order was set
// intentionally
prependProfile(this.environment, profile);
}
}
}
private boolean environmentHasActiveProfile(String profile) {
for (String activeProfile : this.environment.getActiveProfiles()) { for (String activeProfile : this.environment.getActiveProfiles()) {
if (activeProfile.equals(profile)) { if (activeProfile.equals(profile)) {
return true; return;
} }
} }
return false; this.environment.addActiveProfile(profile);
}
private void prependProfile(ConfigurableEnvironment environment,
Profile profile) {
Set<String> profiles = new LinkedHashSet<>();
environment.getActiveProfiles(); // ensure they are initialized
// But this one should go first (last wins in a property key clash)
profiles.add(profile.getName());
profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
environment.setActiveProfiles(StringUtils.toStringArray(profiles));
} }
private Set<String> getSearchLocations() { private Set<String> getSearchLocations() {

@ -758,6 +758,8 @@ public class ConfigFileApplicationListenerTests {
assertThat(environment).has(matchingProfile("morespecific")); assertThat(environment).has(matchingProfile("morespecific"));
assertThat(environment).has(matchingProfile("yetmorespecific")); assertThat(environment).has(matchingProfile("yetmorespecific"));
assertThat(environment).doesNotHave(matchingProfile("missing")); assertThat(environment).doesNotHave(matchingProfile("missing"));
assertThat(this.out.toString())
.contains("The following profiles are active: includeprofile,specific,morespecific,yetmorespecific");
} }
@Test @Test

Loading…
Cancel
Save