Fix spring.profiles.default with profile enabled by configuration file

Previously, if the user configured a custom default profile and then
enabled another profile using a configuration file, the custom default
profile would be activated when it should not have been.

This commit updates ConfigFileApplicationListener so that when a
profile is activated via a configuration file, any profiles
that are queued purely because they are a default profile are removed
from the queue. This ensures that a default profile is not active
when another profile is activated via a configuration file.

Closes gh-5998
pull/6323/head
Phillip Webb 8 years ago committed by Andy Wilkinson
parent 0545231049
commit f10286caf1

@ -21,6 +21,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -326,9 +327,9 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
private PropertySourcesLoader propertiesLoader; private PropertySourcesLoader propertiesLoader;
private Queue<String> profiles; private Queue<Profile> profiles;
private List<String> processedProfiles; private List<Profile> processedProfiles;
private boolean activatedProfiles; private boolean activatedProfiles;
@ -341,16 +342,17 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
public void load() throws IOException { public void load() throws IOException {
this.propertiesLoader = new PropertySourcesLoader(); this.propertiesLoader = new PropertySourcesLoader();
this.activatedProfiles = false; this.activatedProfiles = false;
this.profiles = Collections.asLifoQueue(new LinkedList<String>()); this.profiles = Collections.asLifoQueue(new LinkedList<Profile>());
this.processedProfiles = new LinkedList<String>(); this.processedProfiles = new LinkedList<Profile>();
// Pre-existing active profiles set via Environment.setActiveProfiles() // Pre-existing active profiles set via Environment.setActiveProfiles()
// are additional profiles and config files are allowed to add more if // are additional profiles and config files are allowed to add more if
// they want to, so don't call addActiveProfiles() here. // they want to, so don't call addActiveProfiles() here.
Set<String> initialActiveProfiles = initializeActiveProfiles(); Set<Profile> initialActiveProfiles = initializeActiveProfiles();
this.profiles.addAll(getUnprocessedActiveProfiles(initialActiveProfiles)); this.profiles.addAll(getUnprocessedActiveProfiles(initialActiveProfiles));
if (this.profiles.isEmpty()) { if (this.profiles.isEmpty()) {
for (String defaultProfile : this.environment.getDefaultProfiles()) { for (String defaultProfileName : this.environment.getDefaultProfiles()) {
Profile defaultProfile = new Profile(defaultProfileName, true);
if (!this.profiles.contains(defaultProfile)) { if (!this.profiles.contains(defaultProfile)) {
this.profiles.add(defaultProfile); this.profiles.add(defaultProfile);
} }
@ -363,7 +365,7 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
this.profiles.add(null); this.profiles.add(null);
while (!this.profiles.isEmpty()) { while (!this.profiles.isEmpty()) {
String profile = this.profiles.poll(); Profile profile = this.profiles.poll();
for (String location : getSearchLocations()) { for (String location : getSearchLocations()) {
if (!location.endsWith("/")) { if (!location.endsWith("/")) {
// location is a filename already, so don't search for more // location is a filename already, so don't search for more
@ -382,13 +384,13 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
addConfigurationProperties(this.propertiesLoader.getPropertySources()); addConfigurationProperties(this.propertiesLoader.getPropertySources());
} }
private Set<String> initializeActiveProfiles() { private Set<Profile> initializeActiveProfiles() {
if (!this.environment.containsProperty(ACTIVE_PROFILES_PROPERTY)) { if (!this.environment.containsProperty(ACTIVE_PROFILES_PROPERTY)) {
return Collections.emptySet(); return Collections.emptySet();
} }
// Any pre-existing active profiles set via property sources (e.g. System // Any pre-existing active profiles set via property sources (e.g. System
// properties) take precedence over those added in config files. // properties) take precedence over those added in config files.
Set<String> activeProfiles = getProfilesForValue( Set<Profile> activeProfiles = getProfilesForValue(
this.environment.getProperty(ACTIVE_PROFILES_PROPERTY)); this.environment.getProperty(ACTIVE_PROFILES_PROPERTY));
maybeActivateProfiles(activeProfiles); maybeActivateProfiles(activeProfiles);
return activeProfiles; return activeProfiles;
@ -406,10 +408,11 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
* {@link #ACTIVE_PROFILES_PROPERTY} * {@link #ACTIVE_PROFILES_PROPERTY}
* @return the unprocessed active profiles from the environment to enable * @return the unprocessed active profiles from the environment to enable
*/ */
private List<String> getUnprocessedActiveProfiles( private List<Profile> getUnprocessedActiveProfiles(
Set<String> initialActiveProfiles) { Set<Profile> initialActiveProfiles) {
List<String> unprocessedActiveProfiles = new ArrayList<String>(); List<Profile> unprocessedActiveProfiles = new ArrayList<Profile>();
for (String profile : this.environment.getActiveProfiles()) { for (String profileName : this.environment.getActiveProfiles()) {
Profile profile = new Profile(profileName);
if (!initialActiveProfiles.contains(profile)) { if (!initialActiveProfiles.contains(profile)) {
unprocessedActiveProfiles.add(profile); unprocessedActiveProfiles.add(profile);
} }
@ -420,7 +423,7 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
return unprocessedActiveProfiles; return unprocessedActiveProfiles;
} }
private void load(String location, String name, String profile) private void load(String location, String name, Profile profile)
throws IOException { throws IOException {
String group = "profile=" + (profile == null ? "" : profile); String group = "profile=" + (profile == null ? "" : profile);
if (!StringUtils.hasText(name)) { if (!StringUtils.hasText(name)) {
@ -434,7 +437,7 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
// Try the profile-specific file // Try the profile-specific file
loadIntoGroup(group, location + name + "-" + profile + "." + ext, loadIntoGroup(group, location + name + "-" + profile + "." + ext,
null); null);
for (String processedProfile : this.processedProfiles) { for (Profile processedProfile : this.processedProfiles) {
if (processedProfile != null) { if (processedProfile != null) {
loadIntoGroup(group, location + name + "-" loadIntoGroup(group, location + name + "-"
+ processedProfile + "." + ext, profile); + processedProfile + "." + ext, profile);
@ -453,7 +456,7 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
} }
private PropertySource<?> loadIntoGroup(String identifier, String location, private PropertySource<?> loadIntoGroup(String identifier, String location,
String profile) throws IOException { Profile profile) throws IOException {
Resource resource = this.resourceLoader.getResource(location); Resource resource = this.resourceLoader.getResource(location);
PropertySource<?> propertySource = null; PropertySource<?> propertySource = null;
StringBuilder msg = new StringBuilder(); StringBuilder msg = new StringBuilder();
@ -461,7 +464,7 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
String name = "applicationConfig: [" + location + "]"; String name = "applicationConfig: [" + location + "]";
String group = "applicationConfig: [" + identifier + "]"; String group = "applicationConfig: [" + identifier + "]";
propertySource = this.propertiesLoader.load(resource, group, name, propertySource = this.propertiesLoader.load(resource, group, name,
profile); (profile == null ? null : profile.getName()));
if (propertySource != null) { if (propertySource != null) {
msg.append("Loaded "); msg.append("Loaded ");
handleProfileProperties(propertySource); handleProfileProperties(propertySource);
@ -475,7 +478,7 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
} }
msg.append("config file "); msg.append("config file ");
msg.append(getResourceDescription(location, resource)); msg.append(getResourceDescription(location, resource));
if (StringUtils.hasLength(profile)) { if (profile != null) {
msg.append(" for profile ").append(profile); msg.append(" for profile ").append(profile);
} }
if (resource == null || !resource.exists()) { if (resource == null || !resource.exists()) {
@ -503,15 +506,15 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
} }
private void handleProfileProperties(PropertySource<?> propertySource) { private void handleProfileProperties(PropertySource<?> propertySource) {
Set<String> activeProfiles = getProfilesForValue( Set<Profile> activeProfiles = getProfilesForValue(
propertySource.getProperty(ACTIVE_PROFILES_PROPERTY)); propertySource.getProperty(ACTIVE_PROFILES_PROPERTY));
maybeActivateProfiles(activeProfiles); maybeActivateProfiles(activeProfiles);
Set<String> includeProfiles = getProfilesForValue( Set<Profile> includeProfiles = getProfilesForValue(
propertySource.getProperty(INCLUDE_PROFILES_PROPERTY)); propertySource.getProperty(INCLUDE_PROFILES_PROPERTY));
addProfiles(includeProfiles); addProfiles(includeProfiles);
} }
private void maybeActivateProfiles(Set<String> profiles) { private void maybeActivateProfiles(Set<Profile> profiles) {
if (this.activatedProfiles) { if (this.activatedProfiles) {
if (!profiles.isEmpty()) { if (!profiles.isEmpty()) {
this.logger.debug("Profiles already activated, '" + profiles this.logger.debug("Profiles already activated, '" + profiles
@ -524,18 +527,33 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
this.logger.debug("Activated profiles " this.logger.debug("Activated profiles "
+ StringUtils.collectionToCommaDelimitedString(profiles)); + StringUtils.collectionToCommaDelimitedString(profiles));
this.activatedProfiles = true; this.activatedProfiles = true;
removeUnprocessedDefaultProfiles();
} }
} }
private Set<String> getProfilesForValue(Object property) { private void removeUnprocessedDefaultProfiles() {
for (Iterator<Profile> iterator = this.profiles.iterator(); iterator
.hasNext();) {
if (iterator.next().isDefaultProfile()) {
iterator.remove();
}
}
}
private Set<Profile> getProfilesForValue(Object property) {
String value = (property == null ? null : property.toString()); String value = (property == null ? null : property.toString());
return asResolvedSet(value, null); Set<String> profileNames = asResolvedSet(value, null);
Set<Profile> profiles = new LinkedHashSet<Profile>();
for (String profileName : profileNames) {
profiles.add(new Profile(profileName));
}
return profiles;
} }
private void addProfiles(Set<String> profiles) { private void addProfiles(Set<Profile> profiles) {
for (String profile : profiles) { for (Profile profile : profiles) {
this.profiles.add(profile); this.profiles.add(profile);
if (!this.environment.acceptsProfiles(profile)) { if (!this.environment.acceptsProfiles(profile.getName())) {
// If it's already accepted we assume the order was set // If it's already accepted we assume the order was set
// intentionally // intentionally
prependProfile(this.environment, profile); prependProfile(this.environment, profile);
@ -543,11 +561,12 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
} }
} }
private void prependProfile(ConfigurableEnvironment environment, String profile) { private void prependProfile(ConfigurableEnvironment environment,
Profile profile) {
Set<String> profiles = new LinkedHashSet<String>(); Set<String> profiles = new LinkedHashSet<String>();
environment.getActiveProfiles(); // ensure they are initialized environment.getActiveProfiles(); // ensure they are initialized
// But this one should go first (last wins in a property key clash) // But this one should go first (last wins in a property key clash)
profiles.add(profile); profiles.add(profile.getName());
profiles.addAll(Arrays.asList(environment.getActiveProfiles())); profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
environment.setActiveProfiles(profiles.toArray(new String[profiles.size()])); environment.setActiveProfiles(profiles.toArray(new String[profiles.size()]));
} }
@ -612,6 +631,53 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
} }
private static class Profile {
private final String name;
private final boolean defaultProfile;
Profile(String name) {
this(name, false);
}
Profile(String name, boolean defaultProfile) {
Assert.notNull(name, "Name must not be null");
this.name = name;
this.defaultProfile = defaultProfile;
}
public String getName() {
return this.name;
}
public boolean isDefaultProfile() {
return this.defaultProfile;
}
@Override
public String toString() {
return this.name;
}
@Override
public int hashCode() {
return this.name.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null || obj.getClass() != getClass()) {
return false;
}
return ((Profile) obj).name.equals(this.name);
}
}
/** /**
* Holds the configuration {@link PropertySource}s as they are loaded can relocate * Holds the configuration {@link PropertySource}s as they are loaded can relocate
* them once configuration classes have been processed. * them once configuration classes have been processed.

@ -793,6 +793,40 @@ public class ConfigFileApplicationListenerTests {
assertThat(property).isEqualTo("frompropertiesfile"); assertThat(property).isEqualTo("frompropertiesfile");
} }
@Test
public void customDefaultProfile() throws Exception {
SpringApplication application = new SpringApplication(Config.class);
application.setWebEnvironment(false);
this.context = application.run("--spring.profiles.default=customdefault");
String property = this.context.getEnvironment().getProperty("customdefault");
assertThat(property).isEqualTo("true");
}
@Test
public void customDefaultProfileAndActive() throws Exception {
SpringApplication application = new SpringApplication(Config.class);
application.setWebEnvironment(false);
this.context = application.run("--spring.profiles.default=customdefault",
"--spring.profiles.active=dev");
String property = this.context.getEnvironment().getProperty("my.property");
assertThat(property).isEqualTo("fromdevpropertiesfile");
assertThat(this.context.getEnvironment().containsProperty("customdefault"))
.isFalse();
}
@Test
public void customDefaultProfileAndActiveFromFile() throws Exception {
// gh-5998
SpringApplication application = new SpringApplication(Config.class);
application.setWebEnvironment(false);
this.context = application.run("--spring.config.name=customprofile",
"--spring.profiles.default=customdefault");
ConfigurableEnvironment environment = this.context.getEnvironment();
assertThat(environment.containsProperty("customprofile")).isTrue();
assertThat(environment.containsProperty("customprofile-specific")).isTrue();
assertThat(environment.containsProperty("customprofile-customdefault")).isFalse();
}
private Condition<ConfigurableEnvironment> matchingPropertySource( private Condition<ConfigurableEnvironment> matchingPropertySource(
final String sourceName) { final String sourceName) {
return new Condition<ConfigurableEnvironment>( return new Condition<ConfigurableEnvironment>(

@ -0,0 +1,2 @@
spring.profiles.active=specific
customprofile=true
Loading…
Cancel
Save