diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ConfigDataApplicationContextInitializer.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ConfigDataApplicationContextInitializer.java index 65091484576..9b39ad11903 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ConfigDataApplicationContextInitializer.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ConfigDataApplicationContextInitializer.java @@ -18,6 +18,7 @@ package org.springframework.boot.test.context; import org.springframework.boot.context.config.ConfigData; import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor; +import org.springframework.boot.env.DefaultBootstrapRegisty; import org.springframework.boot.env.DefaultPropertiesPropertySource; import org.springframework.boot.env.RandomValuePropertySource; import org.springframework.context.ApplicationContextInitializer; @@ -41,7 +42,9 @@ public class ConfigDataApplicationContextInitializer public void initialize(ConfigurableApplicationContext applicationContext) { ConfigurableEnvironment environment = applicationContext.getEnvironment(); RandomValuePropertySource.addToEnvironment(environment); - ConfigDataEnvironmentPostProcessor.applyTo(environment, applicationContext); + DefaultBootstrapRegisty bootstrapRegistry = new DefaultBootstrapRegisty(); + ConfigDataEnvironmentPostProcessor.applyTo(environment, applicationContext, bootstrapRegistry); + bootstrapRegistry.applicationContextPrepared(applicationContext); DefaultPropertiesPropertySource.moveToEnd(environment); } 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 73af332ba83..f826441a4cc 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 @@ -25,6 +25,7 @@ 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.Binder; +import org.springframework.boot.env.BootstrapRegistry; import org.springframework.boot.env.DefaultPropertiesPropertySource; import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.core.env.ConfigurableEnvironment; @@ -77,6 +78,8 @@ class ConfigDataEnvironment { private final Log logger; + private final BootstrapRegistry bootstrapRegistry; + private final ConfigurableEnvironment environment; private final ConfigDataLocationResolvers resolvers; @@ -90,16 +93,18 @@ class ConfigDataEnvironment { /** * Create a new {@link ConfigDataEnvironment} instance. * @param logFactory the deferred log factory + * @param bootstrapRegistry the bootstrap registry * @param environment the Spring {@link Environment}. * @param resourceLoader {@link ResourceLoader} to load resource locations * @param additionalProfiles any additional profiles to activate */ - ConfigDataEnvironment(DeferredLogFactory logFactory, ConfigurableEnvironment environment, - ResourceLoader resourceLoader, Collection additionalProfiles) { + ConfigDataEnvironment(DeferredLogFactory logFactory, BootstrapRegistry bootstrapRegistry, + ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection additionalProfiles) { Binder binder = Binder.get(environment); UseLegacyConfigProcessingException.throwIfRequested(binder); this.logFactory = logFactory; this.logger = logFactory.getLog(getClass()); + this.bootstrapRegistry = bootstrapRegistry; this.environment = environment; this.resolvers = createConfigDataLocationResolvers(logFactory, binder, resourceLoader); this.additionalProfiles = additionalProfiles; @@ -132,7 +137,7 @@ class ConfigDataEnvironment { this.logger.trace("Creating wrapped config data contributor for default property source"); contributors.add(ConfigDataEnvironmentContributor.ofExisting(defaultPropertySource)); } - return new ConfigDataEnvironmentContributors(this.logFactory, contributors); + return new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapRegistry, contributors); } ConfigDataEnvironmentContributors getContributors() { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributors.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributors.java index 5b26997b1d5..b659bf32728 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributors.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributors.java @@ -36,6 +36,7 @@ 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.logging.DeferredLogFactory; import org.springframework.core.log.LogMessage; import org.springframework.util.ObjectUtils; @@ -53,19 +54,25 @@ class ConfigDataEnvironmentContributors implements Iterable contributors) { this.logger = logFactory.getLog(getClass()); + this.bootstrapRegistry = bootstrapRegistry; this.root = ConfigDataEnvironmentContributor.of(contributors); } - private ConfigDataEnvironmentContributors(Log logger, ConfigDataEnvironmentContributor root) { + private ConfigDataEnvironmentContributors(Log logger, BootstrapRegistry bootstrapRegistry, + ConfigDataEnvironmentContributor root) { this.logger = logger; + this.bootstrapRegistry = bootstrapRegistry; this.root = root; } @@ -91,22 +98,27 @@ class ConfigDataEnvironmentContributors implements Iterable imports = unprocessed.getImports(); this.logger.trace(LogMessage.format("Processing imports %s", imports)); Map imported = importer.resolveAndLoad(activationContext, - locationResolverContext, imports); + locationResolverContext, loaderContext, imports); this.logger.trace(LogMessage.of(() -> imported.isEmpty() ? "Nothing imported" : "Imported " + imported.size() + " location " + ((imported.size() != 1) ? "s" : "") + imported.keySet())); ConfigDataEnvironmentContributor processed = unprocessed.withChildren(importPhase, asContributors(activationContext, imported)); - result = new ConfigDataEnvironmentContributors(this.logger, + result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapRegistry, result.getRoot().withReplacement(unprocessed, processed)); processedCount++; } } + protected final BootstrapRegistry getBootstrapRegistry() { + return this.bootstrapRegistry; + } + private ConfigDataEnvironmentContributor getFirstUnprocessed(ConfigDataEnvironmentContributors contributors, ConfigDataActivationContext activationContext, ImportPhase importPhase) { for (ConfigDataEnvironmentContributor contributor : contributors.getRoot()) { @@ -177,10 +189,27 @@ class ConfigDataEnvironmentContributors implements Iterable additionalProfiles) { - return new ConfigDataEnvironment(this.logFactory, environment, resourceLoader, additionalProfiles); + return new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry, environment, resourceLoader, + additionalProfiles); } private void postProcessUsingLegacyApplicationListener(ConfigurableEnvironment environment, @@ -103,7 +109,7 @@ public class ConfigDataEnvironmentPostProcessor implements EnvironmentPostProces * @param environment the environment to apply {@link ConfigData} to */ public static void applyTo(ConfigurableEnvironment environment) { - applyTo(environment, null, Collections.emptyList()); + applyTo(environment, null, null, Collections.emptyList()); } /** @@ -112,11 +118,13 @@ public class ConfigDataEnvironmentPostProcessor implements EnvironmentPostProces * directly and not necessarily as part of a {@link SpringApplication}. * @param environment the environment to apply {@link ConfigData} to * @param resourceLoader the resource loader to use + * @param bootstrapRegistry the bootstrap registry to use or {@code null} to use a + * throw-away registry * @param additionalProfiles any additional profiles that should be applied */ public static void applyTo(ConfigurableEnvironment environment, ResourceLoader resourceLoader, - String... additionalProfiles) { - applyTo(environment, resourceLoader, Arrays.asList(additionalProfiles)); + BootstrapRegistry bootstrapRegistry, String... additionalProfiles) { + applyTo(environment, resourceLoader, bootstrapRegistry, Arrays.asList(additionalProfiles)); } /** @@ -125,13 +133,17 @@ public class ConfigDataEnvironmentPostProcessor implements EnvironmentPostProces * directly and not necessarily as part of a {@link SpringApplication}. * @param environment the environment to apply {@link ConfigData} to * @param resourceLoader the resource loader to use + * @param bootstrapRegistry the bootstrap registry to use or {@code null} to use a + * throw-away registry * @param additionalProfiles any additional profiles that should be applied */ public static void applyTo(ConfigurableEnvironment environment, ResourceLoader resourceLoader, - Collection additionalProfiles) { - new ConfigDataEnvironmentPostProcessor(Supplier::get).postProcessEnvironment(environment, resourceLoader, - additionalProfiles); - + BootstrapRegistry bootstrapRegistry, Collection additionalProfiles) { + DeferredLogFactory logFactory = Supplier::get; + bootstrapRegistry = (bootstrapRegistry != null) ? bootstrapRegistry : new DefaultBootstrapRegisty(); + ConfigDataEnvironmentPostProcessor postProcessor = new ConfigDataEnvironmentPostProcessor(logFactory, + bootstrapRegistry); + postProcessor.postProcessEnvironment(environment, resourceLoader, additionalProfiles); } @SuppressWarnings("deprecation") diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataImporter.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataImporter.java index 73e46a21593..59dcb824878 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataImporter.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataImporter.java @@ -55,26 +55,29 @@ class ConfigDataImporter { * previously loaded. * @param activationContext the activation context * @param locationResolverContext the location resolver context + * @param loaderContext the loader context * @param locations the locations to resolve * @return a map of the loaded locations and data */ Map resolveAndLoad(ConfigDataActivationContext activationContext, - ConfigDataLocationResolverContext locationResolverContext, List locations) { + ConfigDataLocationResolverContext locationResolverContext, ConfigDataLoaderContext loaderContext, + List locations) { try { Profiles profiles = (activationContext != null) ? activationContext.getProfiles() : null; - return load(this.resolvers.resolveAll(locationResolverContext, locations, profiles)); + return load(loaderContext, this.resolvers.resolveAll(locationResolverContext, locations, profiles)); } catch (IOException ex) { throw new IllegalStateException("IO error on loading imports from " + locations, ex); } } - private Map load(List locations) throws IOException { + private Map load(ConfigDataLoaderContext loaderContext, + List locations) throws IOException { Map result = new LinkedHashMap<>(); for (int i = locations.size() - 1; i >= 0; i--) { ConfigDataLocation location = locations.get(i); if (this.loadedLocations.add(location)) { - result.put(location, this.loaders.load(location)); + result.put(location, this.loaders.load(loaderContext, location)); } } return Collections.unmodifiableMap(result); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoader.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoader.java index 7014d05aa87..75082703f7d 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoader.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoader.java @@ -40,19 +40,21 @@ public interface ConfigDataLoader { /** * Returns if the specified location can be loaded by this instance. + * @param context the loader context * @param location the location to check. * @return if the location is supported by this loader */ - default boolean isLoadable(L location) { + default boolean isLoadable(ConfigDataLoaderContext context, L location) { return true; } /** * Load {@link ConfigData} for the given location. + * @param context the loader context * @param location the location to load * @return the loaded config data or {@code null} if the location should be skipped * @throws IOException on IO error */ - ConfigData load(L location) throws IOException; + ConfigData load(ConfigDataLoaderContext context, L location) throws IOException; } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoaderContext.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoaderContext.java new file mode 100644 index 00000000000..e46211a6f32 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoaderContext.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.context.config; + +import org.springframework.boot.env.BootstrapRegistry; +import org.springframework.boot.env.EnvironmentPostProcessor; + +/** + * Context provided to {@link ConfigDataLoader} methods. + * + * @author Phillip Webb + * @since 2.4.0 + */ +public interface ConfigDataLoaderContext { + + /** + * Provides access to the {@link BootstrapRegistry} shared across all + * {@link EnvironmentPostProcessor EnvironmentPostProcessors}. + * @return the bootstrap registry + */ + BootstrapRegistry getBootstrapRegistry(); + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoaders.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoaders.java index 3967083bcab..43c3af693b9 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoaders.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoaders.java @@ -80,24 +80,25 @@ class ConfigDataLoaders { /** * Load {@link ConfigData} using the first appropriate {@link ConfigDataLoader}. * @param the config data location type + * @param context the loader context * @param location the location to load * @return the loaded {@link ConfigData} * @throws IOException on IO error */ - ConfigData load(L location) throws IOException { - ConfigDataLoader loader = getLoader(location); + ConfigData load(ConfigDataLoaderContext context, L location) throws IOException { + ConfigDataLoader loader = getLoader(context, location); this.logger.trace(LogMessage.of(() -> "Loading " + location + " using loader " + loader.getClass().getName())); - return loader.load(location); + return loader.load(context, location); } @SuppressWarnings("unchecked") - private ConfigDataLoader getLoader(L location) { + private ConfigDataLoader getLoader(ConfigDataLoaderContext context, L location) { ConfigDataLoader result = null; for (int i = 0; i < this.loaders.size(); i++) { ConfigDataLoader candidate = this.loaders.get(i); if (this.locationTypes.get(i).isInstance(location)) { ConfigDataLoader loader = (ConfigDataLoader) candidate; - if (loader.isLoadable(location)) { + if (loader.isLoadable(context, location)) { if (result != null) { throw new IllegalStateException("Multiple loaders found for location " + location + " [" + candidate.getClass().getName() + "," + result.getClass().getName() + "]"); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolverContext.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolverContext.java index 763e1d6888f..a792a808f7d 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolverContext.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolverContext.java @@ -17,6 +17,8 @@ package org.springframework.boot.context.config; import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.boot.env.BootstrapRegistry; +import org.springframework.boot.env.EnvironmentPostProcessor; /** * Context provided to {@link ConfigDataLocationResolver} methods. @@ -41,4 +43,11 @@ public interface ConfigDataLocationResolverContext { */ ConfigDataLocation getParent(); + /** + * Provides access to the {@link BootstrapRegistry} shared across all + * {@link EnvironmentPostProcessor EnvironmentPostProcessors}. + * @return the bootstrap registry + */ + BootstrapRegistry getBootstrapRegistry(); + } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigTreeConfigDataLoader.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigTreeConfigDataLoader.java index 86ae3306831..7a4bfc1adff 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigTreeConfigDataLoader.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigTreeConfigDataLoader.java @@ -31,7 +31,7 @@ import org.springframework.boot.env.ConfigTreePropertySource; class ConfigTreeConfigDataLoader implements ConfigDataLoader { @Override - public ConfigData load(ConfigTreeConfigDataLocation location) throws IOException { + public ConfigData load(ConfigDataLoaderContext context, ConfigTreeConfigDataLocation location) throws IOException { Path path = location.getPath(); String name = "Config tree '" + path + "'"; ConfigTreePropertySource source = new ConfigTreePropertySource(name, path); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ResourceConfigDataLoader.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ResourceConfigDataLoader.java index cc2414290bb..f293e6cd70a 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ResourceConfigDataLoader.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ResourceConfigDataLoader.java @@ -29,7 +29,7 @@ import org.springframework.core.io.Resource; class ResourceConfigDataLoader implements ConfigDataLoader { @Override - public ConfigData load(ResourceConfigDataLocation location) throws IOException { + public ConfigData load(ConfigDataLoaderContext context, ResourceConfigDataLocation location) throws IOException { return new ConfigData(location.load()); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/DefaultBootstrapRegisty.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/DefaultBootstrapRegisty.java index a64c39cafb3..b1c214cdf57 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/DefaultBootstrapRegisty.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/DefaultBootstrapRegisty.java @@ -80,6 +80,13 @@ public class DefaultBootstrapRegisty implements BootstrapRegistry { .forEach((registration) -> registration.applicationContextPrepared(applicationContext)); } + /** + * Clear the registry to reclaim memory. + */ + public void clear() { + this.registrations.clear(); + } + /** * Default implementation of {@link Registration}. */ diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListener.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListener.java index b387cb67cc2..2e17928033f 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListener.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/env/EnvironmentPostProcessorApplicationListener.java @@ -104,11 +104,16 @@ public class EnvironmentPostProcessorApplicationListener implements SmartApplica } private void onApplicationPreparedEvent(ApplicationPreparedEvent event) { - this.deferredLogs.switchOverAll(); this.bootstrapRegistry.applicationContextPrepared(event.getApplicationContext()); + finish(); } private void onApplicationFailedEvent(ApplicationFailedEvent event) { + finish(); + } + + private void finish() { + this.bootstrapRegistry.clear(); this.deferredLogs.switchOverAll(); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorsTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorsTests.java index 24db7cd2d66..5242572bf15 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorsTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorsTests.java @@ -35,6 +35,8 @@ import org.springframework.boot.context.config.ConfigDataEnvironmentContributor. import org.springframework.boot.context.config.ConfigDataEnvironmentContributors.BinderOption; import org.springframework.boot.context.properties.bind.BindException; import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.boot.env.BootstrapRegistry; +import org.springframework.boot.env.DefaultBootstrapRegisty; import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.mock.env.MockEnvironment; import org.springframework.mock.env.MockPropertySource; @@ -58,6 +60,8 @@ class ConfigDataEnvironmentContributorsTests { private DeferredLogFactory logFactory = Supplier::get; + private BootstrapRegistry bootstrapRegistry = new DefaultBootstrapRegisty(); + private MockEnvironment environment; private Binder binder; @@ -69,6 +73,9 @@ class ConfigDataEnvironmentContributorsTests { @Captor private ArgumentCaptor locationResolverContext; + @Captor + private ArgumentCaptor loaderContext; + @BeforeEach void setup() { this.environment = new MockEnvironment(); @@ -83,7 +90,7 @@ class ConfigDataEnvironmentContributorsTests { void createCreatesWithInitialContributors() { ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("test"); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, - Arrays.asList(contributor)); + this.bootstrapRegistry, Arrays.asList(contributor)); Iterator iterator = contributors.iterator(); assertThat(iterator.next()).isSameAs(contributor); assertThat(iterator.next().getKind()).isEqualTo(Kind.ROOT); @@ -94,7 +101,7 @@ class ConfigDataEnvironmentContributorsTests { ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor .ofExisting(new MockPropertySource()); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, - Arrays.asList(contributor)); + this.bootstrapRegistry, Arrays.asList(contributor)); ConfigDataEnvironmentContributors withProcessedImports = contributors.withProcessedImports(this.importer, this.activationContext); assertThat(withProcessedImports).isSameAs(contributors); @@ -107,10 +114,11 @@ class ConfigDataEnvironmentContributorsTests { MockPropertySource propertySource = new MockPropertySource(); Map imported = new LinkedHashMap<>(); imported.put(new TestConfigDataLocation("a"), new ConfigData(Arrays.asList(propertySource))); - given(this.importer.resolveAndLoad(eq(this.activationContext), any(), eq(locations))).willReturn(imported); + given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations))) + .willReturn(imported); ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("testimport"); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, - Arrays.asList(contributor)); + this.bootstrapRegistry, Arrays.asList(contributor)); ConfigDataEnvironmentContributors withProcessedImports = contributors.withProcessedImports(this.importer, this.activationContext); Iterator iterator = withProcessedImports.iterator(); @@ -128,18 +136,18 @@ class ConfigDataEnvironmentContributorsTests { initialPropertySource.setProperty("spring.config.import", "secondimport"); Map initialImported = new LinkedHashMap<>(); initialImported.put(new TestConfigDataLocation("a"), new ConfigData(Arrays.asList(initialPropertySource))); - given(this.importer.resolveAndLoad(eq(this.activationContext), any(), eq(initialLocations))) + given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(initialLocations))) .willReturn(initialImported); List secondLocations = Arrays.asList("secondimport"); MockPropertySource secondPropertySource = new MockPropertySource(); Map secondImported = new LinkedHashMap<>(); secondImported.put(new TestConfigDataLocation("b"), new ConfigData(Arrays.asList(secondPropertySource))); - given(this.importer.resolveAndLoad(eq(this.activationContext), any(), eq(secondLocations))) + given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(secondLocations))) .willReturn(secondImported); ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor .ofInitialImport("initialimport"); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, - Arrays.asList(contributor)); + this.bootstrapRegistry, Arrays.asList(contributor)); ConfigDataEnvironmentContributors withProcessedImports = contributors.withProcessedImports(this.importer, this.activationContext); Iterator iterator = withProcessedImports.iterator(); @@ -161,12 +169,13 @@ class ConfigDataEnvironmentContributorsTests { MockPropertySource propertySource = new MockPropertySource(); Map imported = new LinkedHashMap<>(); imported.put(new TestConfigDataLocation("a'"), new ConfigData(Arrays.asList(propertySource))); - given(this.importer.resolveAndLoad(eq(this.activationContext), any(), eq(locations))).willReturn(imported); + given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations))) + .willReturn(imported); ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("testimport"); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, - Arrays.asList(existingContributor, contributor)); + this.bootstrapRegistry, Arrays.asList(existingContributor, contributor)); contributors.withProcessedImports(this.importer, this.activationContext); - verify(this.importer).resolveAndLoad(any(), this.locationResolverContext.capture(), any()); + verify(this.importer).resolveAndLoad(any(), this.locationResolverContext.capture(), any(), any()); ConfigDataLocationResolverContext context = this.locationResolverContext.getValue(); assertThat(context.getBinder().bind("test", String.class).get()).isEqualTo("springboot"); } @@ -179,31 +188,75 @@ class ConfigDataEnvironmentContributorsTests { initialPropertySource.setProperty("spring.config.import", "secondimport"); Map initialImported = new LinkedHashMap<>(); initialImported.put(new TestConfigDataLocation("a"), new ConfigData(Arrays.asList(initialPropertySource))); - given(this.importer.resolveAndLoad(eq(this.activationContext), any(), eq(initialLocations))) + given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(initialLocations))) .willReturn(initialImported); List secondLocations = Arrays.asList("secondimport"); MockPropertySource secondPropertySource = new MockPropertySource(); Map secondImported = new LinkedHashMap<>(); secondImported.put(new TestConfigDataLocation("b"), new ConfigData(Arrays.asList(secondPropertySource))); - given(this.importer.resolveAndLoad(eq(this.activationContext), any(), eq(secondLocations))) + given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(secondLocations))) .willReturn(secondImported); ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor .ofInitialImport("initialimport"); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, - Arrays.asList(contributor)); + this.bootstrapRegistry, Arrays.asList(contributor)); contributors.withProcessedImports(this.importer, this.activationContext); - verify(this.importer).resolveAndLoad(any(), this.locationResolverContext.capture(), eq(secondLocations)); + verify(this.importer).resolveAndLoad(any(), this.locationResolverContext.capture(), any(), eq(secondLocations)); ConfigDataLocationResolverContext context = this.locationResolverContext.getValue(); assertThat(context.getParent()).hasToString("a"); } + @Test + void withProcessedImportsProvidesLocationResolverContextWithAccessToBootstrapRegistry() { + MockPropertySource existingPropertySource = new MockPropertySource(); + existingPropertySource.setProperty("test", "springboot"); + ConfigDataEnvironmentContributor existingContributor = ConfigDataEnvironmentContributor + .ofExisting(existingPropertySource); + this.importer = mock(ConfigDataImporter.class); + List locations = Arrays.asList("testimport"); + MockPropertySource propertySource = new MockPropertySource(); + Map imported = new LinkedHashMap<>(); + imported.put(new TestConfigDataLocation("a'"), new ConfigData(Arrays.asList(propertySource))); + given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations))) + .willReturn(imported); + ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("testimport"); + ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, + this.bootstrapRegistry, Arrays.asList(existingContributor, contributor)); + contributors.withProcessedImports(this.importer, this.activationContext); + verify(this.importer).resolveAndLoad(any(), this.locationResolverContext.capture(), any(), any()); + ConfigDataLocationResolverContext context = this.locationResolverContext.getValue(); + assertThat(context.getBootstrapRegistry()).isSameAs(this.bootstrapRegistry); + } + + @Test + void withProcessedImportsProvidesLoaderContextWithAccessToBootstrapRegistry() { + MockPropertySource existingPropertySource = new MockPropertySource(); + existingPropertySource.setProperty("test", "springboot"); + ConfigDataEnvironmentContributor existingContributor = ConfigDataEnvironmentContributor + .ofExisting(existingPropertySource); + this.importer = mock(ConfigDataImporter.class); + List locations = Arrays.asList("testimport"); + MockPropertySource propertySource = new MockPropertySource(); + Map imported = new LinkedHashMap<>(); + imported.put(new TestConfigDataLocation("a'"), new ConfigData(Arrays.asList(propertySource))); + given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations))) + .willReturn(imported); + ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("testimport"); + ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, + this.bootstrapRegistry, Arrays.asList(existingContributor, contributor)); + contributors.withProcessedImports(this.importer, this.activationContext); + verify(this.importer).resolveAndLoad(any(), any(), this.loaderContext.capture(), any()); + ConfigDataLoaderContext context = this.loaderContext.getValue(); + assertThat(context.getBootstrapRegistry()).isSameAs(this.bootstrapRegistry); + } + @Test void getBinderProvidesBinder() { MockPropertySource propertySource = new MockPropertySource(); propertySource.setProperty("test", "springboot"); ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofExisting(propertySource); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, - Arrays.asList(contributor)); + this.bootstrapRegistry, Arrays.asList(contributor)); Binder binder = contributors.getBinder(this.activationContext); assertThat(binder.bind("test", String.class).get()).isEqualTo("springboot"); } @@ -220,7 +273,7 @@ class ConfigDataEnvironmentContributorsTests { ConfigDataEnvironmentContributor secondContributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 1, this.activationContext); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, - Arrays.asList(firstContributor, secondContributor)); + this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor)); Binder binder = contributors.getBinder(this.activationContext); assertThat(binder.bind("test", String.class).get()).isEqualTo("one"); } @@ -238,7 +291,7 @@ class ConfigDataEnvironmentContributorsTests { ConfigDataEnvironmentContributor secondContributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 1, this.activationContext); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, - Arrays.asList(firstContributor, secondContributor)); + this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor)); Binder binder = contributors.getBinder(this.activationContext); assertThat(binder.bind("test", String.class).get()).isEqualTo("two"); } @@ -250,7 +303,7 @@ class ConfigDataEnvironmentContributorsTests { propertySource.setProperty("other", "springboot"); ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofExisting(propertySource); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, - Arrays.asList(contributor)); + this.bootstrapRegistry, Arrays.asList(contributor)); Binder binder = contributors.getBinder(this.activationContext); assertThat(binder.bind("test", String.class).get()).isEqualTo("springboot"); } @@ -269,7 +322,7 @@ class ConfigDataEnvironmentContributorsTests { ConfigDataEnvironmentContributor secondContributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 1, this.activationContext); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, - Arrays.asList(firstContributor, secondContributor)); + this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor)); Binder binder = contributors.getBinder(this.activationContext); assertThat(binder.bind("test", String.class).get()).isEqualTo("two"); } @@ -287,7 +340,7 @@ class ConfigDataEnvironmentContributorsTests { ConfigDataEnvironmentContributor secondContributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 1, this.activationContext); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, - Arrays.asList(firstContributor, secondContributor)); + this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor)); Binder binder = contributors.getBinder(this.activationContext, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE); assertThatExceptionOfType(BindException.class).isThrownBy(() -> binder.bind("test", String.class)) .satisfies((ex) -> assertThat(ex.getCause()).isInstanceOf(InactiveConfigDataAccessException.class)); @@ -306,7 +359,7 @@ class ConfigDataEnvironmentContributorsTests { ConfigDataEnvironmentContributor secondContributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 1, this.activationContext); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, - Arrays.asList(firstContributor, secondContributor)); + this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor)); Binder binder = contributors.getBinder(this.activationContext, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE); assertThatExceptionOfType(BindException.class).isThrownBy(() -> binder.bind("test", String.class)) .satisfies((ex) -> assertThat(ex.getCause()).isInstanceOf(InactiveConfigDataAccessException.class)); @@ -326,7 +379,7 @@ class ConfigDataEnvironmentContributorsTests { ConfigDataEnvironmentContributor secondContributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 1, this.activationContext); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, - Arrays.asList(firstContributor, secondContributor)); + this.bootstrapRegistry, Arrays.asList(firstContributor, secondContributor)); Binder binder = contributors.getBinder(this.activationContext, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE); assertThatExceptionOfType(BindException.class).isThrownBy(() -> binder.bind("test", String.class)) .satisfies((ex) -> assertThat(ex.getCause()).isInstanceOf(InactiveConfigDataAccessException.class)); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorBootstrapRegistryIntegrationTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorBootstrapRegistryIntegrationTests.java new file mode 100644 index 00000000000..9c0b375a963 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorBootstrapRegistryIntegrationTests.java @@ -0,0 +1,62 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.context.config; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.context.config.TestConfigDataBootstrap.LoaderHelper; +import org.springframework.boot.env.BootstrapRegistry; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link ConfigDataEnvironmentPostProcessor} when used with a + * {@link BootstrapRegistry}. + * + * @author Phillip Webb + */ +class ConfigDataEnvironmentPostProcessorBootstrapRegistryIntegrationTests { + + private SpringApplication application; + + @BeforeEach + void setup() { + this.application = new SpringApplication(Config.class); + this.application.setWebApplicationType(WebApplicationType.NONE); + } + + @Test + void bootstrapsApplicationContext() { + try (ConfigurableApplicationContext context = this.application + .run("--spring.config.import=classpath:application-bootstrap-registry-integration-tests.properties")) { + LoaderHelper bean = context.getBean(TestConfigDataBootstrap.LoaderHelper.class); + assertThat(bean).isNotNull(); + assertThat(bean.getLocation().getResolverHelper().getLocation()).isEqualTo("testbootstrap:test"); + } + } + + @Configuration + static class Config { + + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorTests.java index 7ce9857b944..01d0278059e 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorTests.java @@ -28,6 +28,7 @@ import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.boot.SpringApplication; +import org.springframework.boot.env.DefaultBootstrapRegisty; import org.springframework.core.env.StandardEnvironment; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.ResourceLoader; @@ -58,7 +59,8 @@ class ConfigDataEnvironmentPostProcessorTests { private ConfigDataEnvironment configDataEnvironment; @Spy - private ConfigDataEnvironmentPostProcessor postProcessor = new ConfigDataEnvironmentPostProcessor(Supplier::get); + private ConfigDataEnvironmentPostProcessor postProcessor = new ConfigDataEnvironmentPostProcessor(Supplier::get, + new DefaultBootstrapRegisty()); @Captor private ArgumentCaptor> additionalProfilesCaptor; @@ -117,7 +119,7 @@ class ConfigDataEnvironmentPostProcessorTests { @Test void applyToAppliesPostProcessing() { int before = this.environment.getPropertySources().size(); - ConfigDataEnvironmentPostProcessor.applyTo(this.environment, null, "dev"); + ConfigDataEnvironmentPostProcessor.applyTo(this.environment, null, null, "dev"); assertThat(this.environment.getPropertySources().size()).isGreaterThan(before); assertThat(this.environment.getActiveProfiles()).containsExactly("dev"); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentTests.java index a92fde5ed31..c70ab5431cf 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentTests.java @@ -29,6 +29,8 @@ import org.junit.jupiter.api.TestInfo; import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.ImportPhase; import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.Kind; import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.boot.env.BootstrapRegistry; +import org.springframework.boot.env.DefaultBootstrapRegisty; import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.PropertySource; @@ -50,6 +52,8 @@ class ConfigDataEnvironmentTests { private DeferredLogFactory logFactory = Supplier::get; + private BootstrapRegistry bootstrapRegistry = new DefaultBootstrapRegisty(); + private MockEnvironment environment = new MockEnvironment(); private ResourceLoader resourceLoader = new DefaultResourceLoader(); @@ -60,15 +64,15 @@ class ConfigDataEnvironmentTests { void createWhenUseLegacyPropertyInEnvironmentThrowsException() { this.environment.setProperty("spring.config.use-legacy-processing", "true"); assertThatExceptionOfType(UseLegacyConfigProcessingException.class) - .isThrownBy(() -> new ConfigDataEnvironment(this.logFactory, this.environment, this.resourceLoader, - this.additionalProfiles)); + .isThrownBy(() -> new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry, this.environment, + this.resourceLoader, this.additionalProfiles)); } @Test void createExposesEnvironmentBinderToConfigDataLocationResolvers() { this.environment.setProperty("spring", "boot"); TestConfigDataEnvironment configDataEnvironment = new TestConfigDataEnvironment(this.logFactory, - this.environment, this.resourceLoader, this.additionalProfiles); + this.bootstrapRegistry, this.environment, this.resourceLoader, this.additionalProfiles); assertThat(configDataEnvironment.getConfigDataLocationResolversBinder().bind("spring", String.class).get()) .isEqualTo("boot"); } @@ -81,8 +85,8 @@ class ConfigDataEnvironmentTests { this.environment.getPropertySources().addLast(propertySource1); this.environment.getPropertySources().addLast(propertySource2); this.environment.getPropertySources().addLast(propertySource3); - ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.environment, - this.resourceLoader, this.additionalProfiles); + ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry, + this.environment, this.resourceLoader, this.additionalProfiles); List children = configDataEnvironment.getContributors().getRoot() .getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION); Object[] wrapped = children.stream().filter((child) -> child.getKind() == Kind.EXISTING) @@ -100,8 +104,8 @@ class ConfigDataEnvironmentTests { this.environment.getPropertySources().addLast(defaultPropertySource); this.environment.getPropertySources().addLast(propertySource1); this.environment.getPropertySources().addLast(propertySource2); - ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.environment, - this.resourceLoader, this.additionalProfiles); + ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry, + this.environment, this.resourceLoader, this.additionalProfiles); List children = configDataEnvironment.getContributors().getRoot() .getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION); Object[] wrapped = children.stream().filter((child) -> child.getKind() == Kind.EXISTING) @@ -116,8 +120,8 @@ class ConfigDataEnvironmentTests { this.environment.setProperty("spring.config.location", "l1,l2"); this.environment.setProperty("spring.config.additional-location", "a1,a2"); this.environment.setProperty("spring.config.import", "i1,i2"); - ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.environment, - this.resourceLoader, this.additionalProfiles); + ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry, + this.environment, this.resourceLoader, this.additionalProfiles); List children = configDataEnvironment.getContributors().getRoot() .getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION); Object[] imports = children.stream().filter((child) -> child.getKind() == Kind.INITIAL_IMPORT) @@ -128,8 +132,8 @@ class ConfigDataEnvironmentTests { @Test void processAndApplyAddsImportedSourceToEnvironment(TestInfo info) { this.environment.setProperty("spring.config.location", getConfigLocation(info)); - ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.environment, - this.resourceLoader, this.additionalProfiles); + ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry, + this.environment, this.resourceLoader, this.additionalProfiles); configDataEnvironment.processAndApply(); assertThat(this.environment.getProperty("spring")).isEqualTo("boot"); } @@ -137,8 +141,8 @@ class ConfigDataEnvironmentTests { @Test void processAndApplyOnlyAddsActiveContributors(TestInfo info) { this.environment.setProperty("spring.config.location", getConfigLocation(info)); - ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.environment, - this.resourceLoader, this.additionalProfiles); + ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry, + this.environment, this.resourceLoader, this.additionalProfiles); configDataEnvironment.processAndApply(); assertThat(this.environment.getProperty("spring")).isEqualTo("boot"); assertThat(this.environment.getProperty("other")).isNull(); @@ -149,8 +153,8 @@ class ConfigDataEnvironmentTests { MockPropertySource defaultPropertySource = new MockPropertySource("defaultProperties"); this.environment.getPropertySources().addFirst(defaultPropertySource); this.environment.setProperty("spring.config.location", getConfigLocation(info)); - ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.environment, - this.resourceLoader, this.additionalProfiles); + ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry, + this.environment, this.resourceLoader, this.additionalProfiles); configDataEnvironment.processAndApply(); List> sources = this.environment.getPropertySources().stream().collect(Collectors.toList()); assertThat(sources.get(sources.size() - 1)).isSameAs(defaultPropertySource); @@ -159,8 +163,8 @@ class ConfigDataEnvironmentTests { @Test void processAndApplySetsDefaultProfiles(TestInfo info) { this.environment.setProperty("spring.config.location", getConfigLocation(info)); - ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.environment, - this.resourceLoader, this.additionalProfiles); + ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry, + this.environment, this.resourceLoader, this.additionalProfiles); configDataEnvironment.processAndApply(); assertThat(this.environment.getDefaultProfiles()).containsExactly("one", "two", "three"); } @@ -168,8 +172,8 @@ class ConfigDataEnvironmentTests { @Test void processAndApplySetsActiveProfiles(TestInfo info) { this.environment.setProperty("spring.config.location", getConfigLocation(info)); - ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.environment, - this.resourceLoader, this.additionalProfiles); + ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry, + this.environment, this.resourceLoader, this.additionalProfiles); configDataEnvironment.processAndApply(); assertThat(this.environment.getActiveProfiles()).containsExactly("one", "two", "three"); } @@ -177,8 +181,8 @@ class ConfigDataEnvironmentTests { @Test void processAndApplySetsActiveProfilesAndProfileGroups(TestInfo info) { this.environment.setProperty("spring.config.location", getConfigLocation(info)); - ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.environment, - this.resourceLoader, this.additionalProfiles); + ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry, + this.environment, this.resourceLoader, this.additionalProfiles); configDataEnvironment.processAndApply(); assertThat(this.environment.getActiveProfiles()).containsExactly("one", "four", "five", "two", "three"); } @@ -187,8 +191,8 @@ class ConfigDataEnvironmentTests { @Disabled("Disabled until spring.profiles suppport is dropped") void processAndApplyWhenHasInvalidPropertyThrowsException() { this.environment.setProperty("spring.profile", "a"); - ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.environment, - this.resourceLoader, this.additionalProfiles); + ConfigDataEnvironment configDataEnvironment = new ConfigDataEnvironment(this.logFactory, this.bootstrapRegistry, + this.environment, this.resourceLoader, this.additionalProfiles); assertThatExceptionOfType(InvalidConfigDataPropertyException.class) .isThrownBy(() -> configDataEnvironment.processAndApply()); } @@ -202,9 +206,10 @@ class ConfigDataEnvironmentTests { private Binder configDataLocationResolversBinder; - TestConfigDataEnvironment(DeferredLogFactory logFactory, ConfigurableEnvironment environment, - ResourceLoader resourceLoader, Collection additionalProfiles) { - super(logFactory, environment, resourceLoader, additionalProfiles); + TestConfigDataEnvironment(DeferredLogFactory logFactory, BootstrapRegistry bootstrapRegistry, + ConfigurableEnvironment environment, ResourceLoader resourceLoader, + Collection additionalProfiles) { + super(logFactory, bootstrapRegistry, environment, resourceLoader, additionalProfiles); } @Override diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataImporterTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataImporterTests.java index 9bf8b1d7bfa..a86fe987b81 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataImporterTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataImporterTests.java @@ -54,6 +54,9 @@ class ConfigDataImporterTests { @Mock private ConfigDataLocationResolverContext locationResolverContext; + @Mock + private ConfigDataLoaderContext loaderContext; + @Mock private ConfigDataActivationContext activationContext; @@ -75,11 +78,12 @@ class ConfigDataImporterTests { ConfigData configData2 = new ConfigData(Collections.singleton(new MockPropertySource())); given(this.resolvers.resolveAll(this.locationResolverContext, locations, this.profiles)) .willReturn(resolvedLocations); - given(this.loaders.load(resolvedLocation1)).willReturn(configData1); - given(this.loaders.load(resolvedLocation2)).willReturn(configData2); + given(this.loaders.load(this.loaderContext, resolvedLocation1)).willReturn(configData1); + given(this.loaders.load(this.loaderContext, resolvedLocation2)).willReturn(configData2); ConfigDataImporter importer = new ConfigDataImporter(this.resolvers, this.loaders); Collection loaded = importer - .resolveAndLoad(this.activationContext, this.locationResolverContext, locations).values(); + .resolveAndLoad(this.activationContext, this.locationResolverContext, this.loaderContext, locations) + .values(); assertThat(loaded).containsExactly(configData2, configData1); } @@ -99,14 +103,14 @@ class ConfigDataImporterTests { .willReturn(resolvedLocations1and2); given(this.resolvers.resolveAll(this.locationResolverContext, locations2and3, this.profiles)) .willReturn(resolvedLocations2and3); - given(this.loaders.load(resolvedLocation1)).willReturn(configData1); - given(this.loaders.load(resolvedLocation2)).willReturn(configData2); - given(this.loaders.load(resolvedLocation3)).willReturn(configData3); + given(this.loaders.load(this.loaderContext, resolvedLocation1)).willReturn(configData1); + given(this.loaders.load(this.loaderContext, resolvedLocation2)).willReturn(configData2); + given(this.loaders.load(this.loaderContext, resolvedLocation3)).willReturn(configData3); ConfigDataImporter importer = new ConfigDataImporter(this.resolvers, this.loaders); - Collection loaded1and2 = importer - .resolveAndLoad(this.activationContext, this.locationResolverContext, locations1and2).values(); - Collection loaded2and3 = importer - .resolveAndLoad(this.activationContext, this.locationResolverContext, locations2and3).values(); + Collection loaded1and2 = importer.resolveAndLoad(this.activationContext, + this.locationResolverContext, this.loaderContext, locations1and2).values(); + Collection loaded2and3 = importer.resolveAndLoad(this.activationContext, + this.locationResolverContext, this.loaderContext, locations2and3).values(); assertThat(loaded1and2).containsExactly(configData2, configData1); assertThat(loaded2and3).containsExactly(configData3); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLoaderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLoaderTests.java index b41793831e9..87aae67504e 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLoaderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLoaderTests.java @@ -21,6 +21,7 @@ import java.io.IOException; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; /** * Tests for {@link ConfigDataLoader}. @@ -32,15 +33,17 @@ class ConfigDataLoaderTests { private TestConfigDataLoader loader = new TestConfigDataLoader(); + private ConfigDataLoaderContext context = mock(ConfigDataLoaderContext.class); + @Test void isLoadableAlwaysReturnsTrue() { - assertThat(this.loader.isLoadable(new TestConfigDataLocation())).isTrue(); + assertThat(this.loader.isLoadable(this.context, new TestConfigDataLocation())).isTrue(); } static class TestConfigDataLoader implements ConfigDataLoader { @Override - public ConfigData load(TestConfigDataLocation location) throws IOException { + public ConfigData load(ConfigDataLoaderContext context, TestConfigDataLocation location) throws IOException { return null; } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLoadersTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLoadersTests.java index 4de530d9856..d8311df8a38 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLoadersTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLoadersTests.java @@ -30,6 +30,7 @@ import org.springframework.mock.env.MockPropertySource; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.mockito.Mockito.mock; /** * Tests for {@link ConfigDataLoaders}. @@ -41,6 +42,8 @@ class ConfigDataLoadersTests { private DeferredLogFactory logFactory = Supplier::get; + private ConfigDataLoaderContext context = mock(ConfigDataLoaderContext.class); + @Test void createWhenLoaderHasLogParameterInjectsLog() { new ConfigDataLoaders(this.logFactory, Arrays.asList(LoggingConfigDataLoader.class.getName())); @@ -51,7 +54,7 @@ class ConfigDataLoadersTests { TestConfigDataLocation location = new TestConfigDataLocation("test"); ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, Arrays.asList(TestConfigDataLoader.class.getName())); - ConfigData loaded = loaders.load(location); + ConfigData loaded = loaders.load(this.context, location); assertThat(getLoader(loaded)).isInstanceOf(TestConfigDataLoader.class); } @@ -60,7 +63,7 @@ class ConfigDataLoadersTests { TestConfigDataLocation location = new TestConfigDataLocation("test"); ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, Arrays.asList(LoggingConfigDataLoader.class.getName(), TestConfigDataLoader.class.getName())); - assertThatIllegalStateException().isThrownBy(() -> loaders.load(location)) + assertThatIllegalStateException().isThrownBy(() -> loaders.load(this.context, location)) .withMessageContaining("Multiple loaders found for location test"); } @@ -69,7 +72,7 @@ class ConfigDataLoadersTests { TestConfigDataLocation location = new TestConfigDataLocation("test"); ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, Arrays.asList(NonLoadableConfigDataLoader.class.getName())); - assertThatIllegalStateException().isThrownBy(() -> loaders.load(location)) + assertThatIllegalStateException().isThrownBy(() -> loaders.load(this.context, location)) .withMessage("No loader found for location 'test'"); } @@ -78,7 +81,7 @@ class ConfigDataLoadersTests { TestConfigDataLocation location = new TestConfigDataLocation("test"); ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, Arrays.asList(OtherConfigDataLoader.class.getName(), SpecificConfigDataLoader.class.getName())); - ConfigData loaded = loaders.load(location); + ConfigData loaded = loaders.load(this.context, location); assertThat(getLoader(loaded)).isInstanceOf(SpecificConfigDataLoader.class); } @@ -121,7 +124,7 @@ class ConfigDataLoadersTests { } @Override - public ConfigData load(ConfigDataLocation location) throws IOException { + public ConfigData load(ConfigDataLoaderContext context, ConfigDataLocation location) throws IOException { throw new AssertionError("Unexpected call"); } @@ -130,7 +133,7 @@ class ConfigDataLoadersTests { static class TestConfigDataLoader implements ConfigDataLoader { @Override - public ConfigData load(ConfigDataLocation location) throws IOException { + public ConfigData load(ConfigDataLoaderContext context, ConfigDataLocation location) throws IOException { return createConfigData(this, location); } @@ -139,7 +142,7 @@ class ConfigDataLoadersTests { static class NonLoadableConfigDataLoader extends TestConfigDataLoader { @Override - public boolean isLoadable(ConfigDataLocation location) { + public boolean isLoadable(ConfigDataLoaderContext context, ConfigDataLocation location) { return false; } @@ -148,7 +151,7 @@ class ConfigDataLoadersTests { static class SpecificConfigDataLoader implements ConfigDataLoader { @Override - public ConfigData load(TestConfigDataLocation location) throws IOException { + public ConfigData load(ConfigDataLoaderContext context, TestConfigDataLocation location) throws IOException { return createConfigData(this, location); } @@ -157,7 +160,7 @@ class ConfigDataLoadersTests { static class OtherConfigDataLoader implements ConfigDataLoader { @Override - public ConfigData load(OtherConfigDataLocation location) throws IOException { + public ConfigData load(ConfigDataLoaderContext context, OtherConfigDataLocation location) throws IOException { return createConfigData(this, location); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationResolverTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationResolverTests.java index 535cb46f525..d675960d73c 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationResolverTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationResolverTests.java @@ -31,9 +31,9 @@ import static org.mockito.Mockito.mock; */ class ConfigDataLocationResolverTests { - ConfigDataLocationResolver resolver = new TestConfigDataLocationResolver(); + private ConfigDataLocationResolver resolver = new TestConfigDataLocationResolver(); - ConfigDataLocationResolverContext context = mock(ConfigDataLocationResolverContext.class); + private ConfigDataLocationResolverContext context = mock(ConfigDataLocationResolverContext.class); @Test void resolveProfileSpecificReturnsEmptyList() { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigTreeConfigDataLoaderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigTreeConfigDataLoaderTests.java index 45fb1286b1d..1999795a278 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigTreeConfigDataLoaderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigTreeConfigDataLoaderTests.java @@ -28,6 +28,7 @@ import org.springframework.core.env.PropertySource; import org.springframework.util.FileCopyUtils; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; /** * Tests for {@link ConfigTreeConfigDataLoader}. @@ -39,6 +40,8 @@ public class ConfigTreeConfigDataLoaderTests { private ConfigTreeConfigDataLoader loader = new ConfigTreeConfigDataLoader(); + private ConfigDataLoaderContext loaderContext = mock(ConfigDataLoaderContext.class); + @TempDir Path directory; @@ -48,7 +51,7 @@ public class ConfigTreeConfigDataLoaderTests { file.getParentFile().mkdirs(); FileCopyUtils.copy("world".getBytes(StandardCharsets.UTF_8), file); ConfigTreeConfigDataLocation location = new ConfigTreeConfigDataLocation(this.directory.toString()); - ConfigData configData = this.loader.load(location); + ConfigData configData = this.loader.load(this.loaderContext, location); assertThat(configData.getPropertySources().size()).isEqualTo(1); PropertySource source = configData.getPropertySources().get(0); assertThat(source.getName()).isEqualTo("Config tree '" + this.directory.toString() + "'"); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ResourceConfigDataLoaderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ResourceConfigDataLoaderTests.java index 01d4d2cf870..6d254e446b3 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ResourceConfigDataLoaderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ResourceConfigDataLoaderTests.java @@ -26,6 +26,7 @@ import org.springframework.core.env.PropertySource; import org.springframework.core.io.ClassPathResource; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; /** * Tests for {@link ResourceConfigDataLoader}. @@ -37,11 +38,13 @@ public class ResourceConfigDataLoaderTests { private ResourceConfigDataLoader loader = new ResourceConfigDataLoader(); + private ConfigDataLoaderContext loaderContext = mock(ConfigDataLoaderContext.class); + @Test void loadWhenLocationResultsInMultiplePropertySourcesAddsAllToConfigData() throws IOException { ResourceConfigDataLocation location = new ResourceConfigDataLocation("application.yml", new ClassPathResource("configdata/yaml/application.yml"), new YamlPropertySourceLoader()); - ConfigData configData = this.loader.load(location); + ConfigData configData = this.loader.load(this.loaderContext, location); assertThat(configData.getPropertySources().size()).isEqualTo(2); PropertySource source1 = configData.getPropertySources().get(0); PropertySource source2 = configData.getPropertySources().get(1); @@ -56,7 +59,7 @@ public class ResourceConfigDataLoaderTests { ResourceConfigDataLocation location = new ResourceConfigDataLocation("testproperties.properties", new ClassPathResource("config/0-empty/testproperties.properties"), new PropertiesPropertySourceLoader()); - ConfigData configData = this.loader.load(location); + ConfigData configData = this.loader.load(this.loaderContext, location); assertThat(configData.getPropertySources().size()).isEqualTo(0); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/TestConfigDataBootstrap.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/TestConfigDataBootstrap.java new file mode 100644 index 00000000000..726ffb5b055 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/TestConfigDataBootstrap.java @@ -0,0 +1,115 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.context.config; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.MapPropertySource; + +/** + * Test classes used with + * {@link ConfigDataEnvironmentPostProcessorBootstrapRegistryIntegrationTests} to show how + * a bootstrap registry can be used. This example will create helper instances during + * result and load. It also shows how the helper can ultimately be registered as a bean. + * + * @author Phillip Webb + */ +class TestConfigDataBootstrap { + + static class LocationResolver implements ConfigDataLocationResolver { + + @Override + public boolean isResolvable(ConfigDataLocationResolverContext context, String location) { + return location.startsWith("testbootstrap:"); + } + + @Override + public List resolve(ConfigDataLocationResolverContext context, String location) { + ResolverHelper helper = context.getBootstrapRegistry().get(ResolverHelper.class, + () -> new ResolverHelper(location)); + return Collections.singletonList(new Location(helper)); + } + + } + + static class Loader implements ConfigDataLoader { + + @Override + public ConfigData load(ConfigDataLoaderContext context, Location location) throws IOException { + context.getBootstrapRegistry().get(LoaderHelper.class, () -> new LoaderHelper(location), + LoaderHelper::addToContext); + return new ConfigData( + Collections.singleton(new MapPropertySource("loaded", Collections.singletonMap("test", "test")))); + } + + } + + static class Location extends ConfigDataLocation { + + private final ResolverHelper resolverHelper; + + Location(ResolverHelper resolverHelper) { + this.resolverHelper = resolverHelper; + } + + @Override + public String toString() { + return "test"; + } + + ResolverHelper getResolverHelper() { + return this.resolverHelper; + } + + } + + static class ResolverHelper { + + private final String location; + + ResolverHelper(String location) { + this.location = location; + } + + String getLocation() { + return this.location; + } + + } + + static class LoaderHelper { + + private final Location location; + + LoaderHelper(Location location) { + this.location = location; + } + + Location getLocation() { + return this.location; + } + + static void addToContext(ConfigurableApplicationContext context, LoaderHelper loaderHelper) { + context.getBeanFactory().registerSingleton("loaderHelper", loaderHelper); + } + + } + +} diff --git a/spring-boot-project/spring-boot/src/test/resources/META-INF/spring.factories b/spring-boot-project/spring-boot/src/test/resources/META-INF/spring.factories index 7be3145156b..35e21e5ae5a 100644 --- a/spring-boot-project/spring-boot/src/test/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot/src/test/resources/META-INF/spring.factories @@ -1,3 +1,9 @@ org.springframework.boot.env.PropertySourceLoader=\ org.springframework.boot.context.config.TestPropertySourceLoader1,\ org.springframework.boot.context.config.TestPropertySourceLoader2 + +org.springframework.boot.context.config.ConfigDataLocationResolver=\ +org.springframework.boot.context.config.TestConfigDataBootstrap.LocationResolver + +org.springframework.boot.context.config.ConfigDataLoader=\ +org.springframework.boot.context.config.TestConfigDataBootstrap.Loader \ No newline at end of file diff --git a/spring-boot-project/spring-boot/src/test/resources/application-bootstrap-registry-integration-tests.properties b/spring-boot-project/spring-boot/src/test/resources/application-bootstrap-registry-integration-tests.properties new file mode 100644 index 00000000000..5a1fdceb704 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/resources/application-bootstrap-registry-integration-tests.properties @@ -0,0 +1 @@ +spring.config.import=testbootstrap:test \ No newline at end of file