From c1be5cb5e02870cb0c4c12f2f33e4e4742a843f8 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 26 Aug 2020 11:45:53 -0700 Subject: [PATCH] Add limited support for spring.profiles.include Restore support for the `spring.profiles.include` property but only for non-profile specific documents. Closes gh-22944 --- .../context/config/ConfigDataEnvironment.java | 37 ++++++++++++++++++- .../InvalidConfigDataPropertyException.java | 18 ++------- .../boot/context/config/Profiles.java | 7 +++- ...ironmentPostProcessorIntegrationTests.java | 22 +++++++++++ ...validConfigDataPropertyExceptionTests.java | 11 ------ ...de-profiles-in-profile-specific.properties | 6 +++ ...clude-profiles-with-placeholder.properties | 9 +++++ .../application-include-profiles.properties | 7 ++++ 8 files changed, 90 insertions(+), 27 deletions(-) create mode 100644 spring-boot-project/spring-boot/src/test/resources/application-include-profiles-in-profile-specific.properties create mode 100644 spring-boot-project/spring-boot/src/test/resources/application-include-profiles-with-placeholder.properties create mode 100644 spring-boot-project/spring-boot/src/test/resources/application-include-profiles.properties diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java index 7e108fb702e..410b7f27946 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java @@ -18,13 +18,20 @@ package org.springframework.boot.context.config; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; import org.apache.commons.logging.Log; import org.springframework.boot.context.config.ConfigDataEnvironmentContributors.BinderOption; import org.springframework.boot.context.properties.bind.BindException; +import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.boot.context.properties.bind.PlaceholdersResolver; +import org.springframework.boot.context.properties.source.ConfigurationPropertyName; +import org.springframework.boot.context.properties.source.ConfigurationPropertySource; import org.springframework.boot.env.BootstrapRegistry; import org.springframework.boot.env.DefaultPropertiesPropertySource; import org.springframework.boot.logging.DeferredLogFactory; @@ -74,6 +81,11 @@ class ConfigDataEnvironment { private static final String[] EMPTY_LOCATIONS = new String[0]; + private static final ConfigurationPropertyName INCLUDE_PROFILES = ConfigurationPropertyName + .of(Profiles.INCLUDE_PROFILES_PROPERTY_NAME); + + private static final Bindable> STRING_LIST = Bindable.listOf(String.class); + private final DeferredLogFactory logFactory; private final Log logger; @@ -212,7 +224,9 @@ class ConfigDataEnvironment { this.logger.trace("Deducing profiles from current config data environment contributors"); Binder binder = contributors.getBinder(activationContext, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE); try { - Profiles profiles = new Profiles(this.environment, binder, this.additionalProfiles); + Set additionalProfiles = new LinkedHashSet<>(this.additionalProfiles); + additionalProfiles.addAll(getIncludedProfiles(contributors, activationContext)); + Profiles profiles = new Profiles(this.environment, binder, additionalProfiles); return activationContext.withProfiles(profiles); } catch (BindException ex) { @@ -223,6 +237,27 @@ class ConfigDataEnvironment { } } + private Collection getIncludedProfiles(ConfigDataEnvironmentContributors contributors, + ConfigDataActivationContext activationContext) { + PlaceholdersResolver placeholdersResolver = new ConfigDataEnvironmentContributorPlaceholdersResolver( + contributors, activationContext, true); + Set result = new LinkedHashSet<>(); + for (ConfigDataEnvironmentContributor contributor : contributors) { + ConfigurationPropertySource source = contributor.getConfigurationPropertySource(); + if (source == null) { + continue; + } + Binder binder = new Binder(Collections.singleton(source), placeholdersResolver); + binder.bind(INCLUDE_PROFILES, STRING_LIST).ifBound((includes) -> { + if (!contributor.isActive(activationContext)) { + InactiveConfigDataAccessException.throwIfPropertyFound(contributor, INCLUDE_PROFILES); + } + result.addAll(includes); + }); + } + return result; + } + private ConfigDataEnvironmentContributors processWithProfiles(ConfigDataEnvironmentContributors contributors, ConfigDataImporter importer, ConfigDataActivationContext activationContext) { this.logger.trace("Processing config data environment contributors with profile activation context"); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/InvalidConfigDataPropertyException.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/InvalidConfigDataPropertyException.java index 21f4622b7c7..b44775de0a2 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/InvalidConfigDataPropertyException.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/InvalidConfigDataPropertyException.java @@ -35,18 +35,12 @@ import org.springframework.boot.context.properties.source.ConfigurationPropertyS */ public class InvalidConfigDataPropertyException extends ConfigDataException { - private static final Map ERROR; - private static final Map WARNING; static { Map warning = new LinkedHashMap<>(); warning.put(ConfigurationPropertyName.of("spring.profiles"), ConfigurationPropertyName.of("spring.config.activate.on-profile")); WARNING = Collections.unmodifiableMap(warning); - Map error = new LinkedHashMap<>(); - error.put(ConfigurationPropertyName.of("spring.profiles.include"), - ConfigurationPropertyName.of("spring.profiles.group")); - ERROR = Collections.unmodifiableMap(error); } private final ConfigurationProperty property; @@ -90,20 +84,16 @@ public class InvalidConfigDataPropertyException extends ConfigDataException { } /** - * Throw a {@link InvalidConfigDataPropertyException} if the given - * {@link ConfigDataEnvironmentContributor} contains any invalid property. + * Throw a {@link InvalidConfigDataPropertyException} or log a warning if the given + * {@link ConfigDataEnvironmentContributor} contains any invalid property. A warning + * is logged if the property is still supported, but not recommended. An error is + * thrown if the property is completely unsupported. * @param logger the logger to use for warnings * @param contributor the contributor to check */ static void throwOrWarn(Log logger, ConfigDataEnvironmentContributor contributor) { ConfigurationPropertySource propertySource = contributor.getConfigurationPropertySource(); if (propertySource != null) { - ERROR.forEach((invalid, replacement) -> { - ConfigurationProperty property = propertySource.getConfigurationProperty(invalid); - if (property != null) { - throw new InvalidConfigDataPropertyException(property, replacement, contributor.getLocation()); - } - }); WARNING.forEach((invalid, replacement) -> { ConfigurationProperty property = propertySource.getConfigurationProperty(invalid); if (property != null) { 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 8230854e703..2e8ec6e760d 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 @@ -49,6 +49,11 @@ import org.springframework.util.StringUtils; */ public class Profiles implements Iterable { + /** + * Name of property to set to specify additionally included active profiles. + */ + public static final String INCLUDE_PROFILES_PROPERTY_NAME = "spring.profiles.include"; + private static final Bindable> STRING_STRINGS_MAP = Bindable .of(ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, String.class)); @@ -67,7 +72,7 @@ public class Profiles implements Iterable { * {@link Binder}. * @param environment the source environment * @param binder the binder for profile properties - * @param additionalProfiles and additional active profiles + * @param additionalProfiles any additional active profiles */ Profiles(Environment environment, Binder binder, Collection additionalProfiles) { this.groups = binder.bind("spring.profiles.group", STRING_STRINGS_MAP).orElseGet(LinkedMultiValueMap::new); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java index 30e4660b348..53e2b06cb57 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java @@ -549,6 +549,28 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests { () -> this.application.run("--spring.config.location=classpath:missing-appplication.properties")); } + @Test + void runWhenHasIncludedProfilesActivatesProfiles() { + ConfigurableApplicationContext context = this.application + .run("--spring.config.location=classpath:application-include-profiles.properties"); + assertThat(context.getEnvironment().getActiveProfiles()).containsExactlyInAnyOrder("p1", "p2", "p3", "p4", + "p5"); + } + + @Test + void runWhenHasIncludedProfilesWithPlaceholderActivatesProfiles() { + ConfigurableApplicationContext context = this.application + .run("--spring.config.location=classpath:application-include-profiles-with-placeholder.properties"); + assertThat(context.getEnvironment().getActiveProfiles()).containsExactlyInAnyOrder("p1", "p2", "p3", "p4", + "p5"); + } + + @Test + void runWhenHasIncludedProfilesWithProfileSpecificDocumentThrowsException() { + assertThatExceptionOfType(InactiveConfigDataAccessException.class).isThrownBy(() -> this.application + .run("--spring.config.location=classpath:application-include-profiles-in-profile-specific.properties")); + } + private Condition matchingPropertySource(final String sourceName) { return new Condition("environment containing property source " + sourceName) { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/InvalidConfigDataPropertyExceptionTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/InvalidConfigDataPropertyExceptionTests.java index a0ed9b7d9ee..c1375a58da7 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/InvalidConfigDataPropertyExceptionTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/InvalidConfigDataPropertyExceptionTests.java @@ -123,17 +123,6 @@ class InvalidConfigDataPropertyExceptionTests { + "'spring.config.activate.on-profile' [origin: \"spring.profiles\" from property source \"mockProperties\"]"); } - @Test - void throwOrWarnWhenHasErrorPropertyThrowsException() { - MockPropertySource propertySource = new MockPropertySource(); - propertySource.setProperty("spring.profiles.include", "a"); - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofExisting(propertySource); - assertThatExceptionOfType(InvalidConfigDataPropertyException.class) - .isThrownBy(() -> InvalidConfigDataPropertyException.throwOrWarn(this.logger, contributor)) - .withMessage("Property 'spring.profiles.include' is invalid and should be replaced with " - + "'spring.profiles.group' [origin: \"spring.profiles.include\" from property source \"mockProperties\"]"); - } - private static class TestConfigDataLocation extends ConfigDataLocation { @Override diff --git a/spring-boot-project/spring-boot/src/test/resources/application-include-profiles-in-profile-specific.properties b/spring-boot-project/spring-boot/src/test/resources/application-include-profiles-in-profile-specific.properties new file mode 100644 index 00000000000..646513a9ce6 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/application-include-profiles-in-profile-specific.properties @@ -0,0 +1,6 @@ +spring.profiles.active=p1 +spring.profiles.include=p2 +#--- +spring.config.activate.on-profile=p2 +spring.profiles.include=p3 + diff --git a/spring-boot-project/spring-boot/src/test/resources/application-include-profiles-with-placeholder.properties b/spring-boot-project/spring-boot/src/test/resources/application-include-profiles-with-placeholder.properties new file mode 100644 index 00000000000..e1baa5d408a --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/application-include-profiles-with-placeholder.properties @@ -0,0 +1,9 @@ +spring.profiles.active=p1 +spring.profiles.include=p2 +#--- +myprofile=p4 +spring.profiles.include=p3,${myprofile} +#--- +myotherprofile=p5 +spring.profiles.include=${myotherprofile} + diff --git a/spring-boot-project/spring-boot/src/test/resources/application-include-profiles.properties b/spring-boot-project/spring-boot/src/test/resources/application-include-profiles.properties new file mode 100644 index 00000000000..c20ed9ad301 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/application-include-profiles.properties @@ -0,0 +1,7 @@ +spring.profiles.active=p1 +spring.profiles.include=p2 +#--- +spring.profiles.include=p3,p4 +#--- +spring.profiles.include=p5 +