Provide ConfigFileApplicationListener replacement

Deprecate `ConfigFileApplicationListener` and provide a replacement
mechanism that supports arbitrary config data imports.

This commit updates the following areas:

- Extract `EnvironmentPostProcessor` invocation logic from the
  `ConfigFileApplicationListener` to new dedicated listener. Also
  providing support for `Log` injection.

- Extract `RandomPropertySource` adding logic from the
  `ConfigFileApplicationListener` to a dedicated class.

- Migrate to the recently introduced `DefaultPropertiesPropertySource`
  class when moving the defaultProperties `PropertySource`

- Replace processing logic with a phased approach to ensure that
  profile enablement happens in a distinct phase and that profiles
  can no longer be activated on an ad-hoc basis.

- Provide a more predictable and logical import order for processing
  `application.properties` and `application.yml` files.

- Add support for a `spring.config.import` property which can be used
  to import additional config data. Also provide a pluggable API
  allowing third-parties to resolve and load locations themselves.

- Add `spring.config.activate.on-profile` support which replaces the
  existing `spring.profiles` property.

- Add `spring.config.activate.on-cloud-platform` support which allows
  a config data document to be active only on a given cloud platform.

- Support a `spring.config.use-legacy-processing` property allowing the
  previous processing logic to be used.

Closes gh-22497

Co-authored-by: Madhura Bhave <mbhave@vmware.com>
This commit is contained in:
Phillip Webb 2020-07-13 21:19:10 -07:00
parent 44f18362d3
commit 3352024b1c
96 changed files with 8130 additions and 168 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* 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.
@ -25,13 +25,14 @@ import org.springframework.boot.ResourceBanner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.context.config.AnsiOutputApplicationListener;
import org.springframework.boot.context.config.ConfigFileApplicationListener;
import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor;
import org.springframework.boot.context.logging.ClasspathLoggingApplicationListener;
import org.springframework.boot.context.logging.LoggingApplicationListener;
import org.springframework.boot.devtools.remote.client.RemoteClientConfiguration;
import org.springframework.boot.devtools.restart.RestartInitializer;
import org.springframework.boot.devtools.restart.RestartScopeInitializer;
import org.springframework.boot.devtools.restart.Restarter;
import org.springframework.boot.env.EnvironmentPostProcessorApplicationListener;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ApplicationListener;
import org.springframework.core.io.ClassPathResource;
@ -71,7 +72,7 @@ public final class RemoteSpringApplication {
private Collection<ApplicationListener<?>> getListeners() {
List<ApplicationListener<?>> listeners = new ArrayList<>();
listeners.add(new AnsiOutputApplicationListener());
listeners.add(new ConfigFileApplicationListener());
listeners.add(new EnvironmentPostProcessorApplicationListener(ConfigDataEnvironmentPostProcessor.class));
listeners.add(new ClasspathLoggingApplicationListener());
listeners.add(new LoggingApplicationListener());
listeners.add(new RemoteUrlPropertyExtractor());

View File

@ -0,0 +1,63 @@
/*
* 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.test.context;
import java.util.function.Supplier;
import org.springframework.boot.context.config.ConfigData;
import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor;
import org.springframework.boot.env.DefaultPropertiesPropertySource;
import org.springframework.boot.env.RandomValuePropertySource;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.test.context.ContextConfiguration;
/**
* {@link ApplicationContextInitializer} that can be used with the
* {@link ContextConfiguration#initializers()} to trigger loading of {@link ConfigData}
* such as {@literal application.properties}.
*
* @author Phillip Webb
* @since 2.4.0
* @see ConfigDataEnvironmentPostProcessor
*/
public class ConfigDataApplicationContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment environment = applicationContext.getEnvironment();
RandomValuePropertySource.addToEnvironment(environment);
new ConfigDataProcessor().addPropertySources(environment, applicationContext);
DefaultPropertiesPropertySource.moveToEnd(environment);
}
private static class ConfigDataProcessor extends ConfigDataEnvironmentPostProcessor {
ConfigDataProcessor() {
super(Supplier::get);
}
void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
addPropertySources(environment, resourceLoader, null);
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* 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.
@ -16,7 +16,6 @@
package org.springframework.boot.test.context;
import org.springframework.boot.context.config.ConfigFileApplicationListener;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.ContextConfiguration;
@ -28,14 +27,16 @@ import org.springframework.test.context.ContextConfiguration;
*
* @author Phillip Webb
* @since 1.4.0
* @see ConfigFileApplicationListener
* @see org.springframework.boot.context.config.ConfigFileApplicationListener
* @deprecated since 2.4.0 in favor of {@link ConfigDataApplicationContextInitializer}
*/
@Deprecated
public class ConfigFileApplicationContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
new ConfigFileApplicationListener() {
new org.springframework.boot.context.config.ConfigFileApplicationListener() {
public void apply() {
addPropertySources(applicationContext.getEnvironment(), applicationContext);
addPostProcessors(applicationContext);

View File

@ -0,0 +1,55 @@
/*
* 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.test.context;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ConfigDataApplicationContextInitializer}.
*
* @author Phillip Webb
*/
@ExtendWith(SpringExtension.class)
@DirtiesContext
@ContextConfiguration(classes = ConfigDataApplicationContextInitializerTests.Config.class,
initializers = ConfigDataApplicationContextInitializer.class)
class ConfigDataApplicationContextInitializerTests {
@Autowired
private Environment environment;
@Test
void initializerPopulatesEnvironment() {
assertThat(this.environment.getProperty("foo")).isEqualTo("bucket");
}
@Configuration(proxyBeanMethods = false)
static class Config {
}
}

View File

@ -0,0 +1,57 @@
/*
* 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.test.context;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ConfigDataApplicationContextInitializer}.
*
* @author Phillip Webb
*/
@ExtendWith(SpringExtension.class)
@DirtiesContext
@TestPropertySource(properties = "spring.config.use-legacy-processing=true")
@ContextConfiguration(classes = ConfigDataApplicationContextInitializerWithLegacySwitchTests.Config.class,
initializers = ConfigDataApplicationContextInitializer.class)
class ConfigDataApplicationContextInitializerWithLegacySwitchTests {
@Autowired
private Environment environment;
@Test
void initializerPopulatesEnvironment() {
assertThat(this.environment.getProperty("foo")).isEqualTo("bucket");
}
@Configuration(proxyBeanMethods = false)
static class Config {
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* 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.
@ -33,6 +33,8 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Phillip Webb
*/
@Deprecated
@SuppressWarnings("deprecation")
@ExtendWith(SpringExtension.class)
@DirtiesContext
@ContextConfiguration(classes = ConfigFileApplicationContextInitializerTests.Config.class,

View File

@ -23,7 +23,6 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@ -237,7 +236,7 @@ public class SpringApplication {
private Map<String, Object> defaultProperties;
private Set<String> additionalProfiles = new HashSet<>();
private Set<String> additionalProfiles = Collections.emptySet();
private boolean allowBeanDefinitionOverriding;
@ -352,6 +351,7 @@ public class SpringApplication {
ConfigurationPropertySources.attach(environment);
listeners.environmentPrepared(environment);
DefaultPropertiesPropertySource.moveToEnd(environment);
configureAdditionalProfiles(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
@ -527,9 +527,16 @@ public class SpringApplication {
* @see org.springframework.boot.context.config.ConfigFileApplicationListener
*/
protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
environment.setActiveProfiles(StringUtils.toStringArray(profiles));
}
private void configureAdditionalProfiles(ConfigurableEnvironment environment) {
if (!CollectionUtils.isEmpty(this.additionalProfiles)) {
Set<String> profiles = new LinkedHashSet<>(Arrays.asList(environment.getActiveProfiles()));
if (!profiles.containsAll(this.additionalProfiles)) {
profiles.addAll(this.additionalProfiles);
environment.setActiveProfiles(StringUtils.toStringArray(profiles));
}
}
}
private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
@ -1043,7 +1050,15 @@ public class SpringApplication {
* @param profiles the additional profiles to set
*/
public void setAdditionalProfiles(String... profiles) {
this.additionalProfiles = new LinkedHashSet<>(Arrays.asList(profiles));
this.additionalProfiles = Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(profiles)));
}
/**
* Return an immutable set of any additional profiles in use.
* @return the additional profiles
*/
public Set<String> getAdditionalProfiles() {
return this.additionalProfiles;
}
/**

View File

@ -22,8 +22,10 @@ import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.config.ConfigFileApplicationListener;
import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.json.JsonParser;
@ -92,14 +94,36 @@ import org.springframework.util.StringUtils;
public class CloudFoundryVcapEnvironmentPostProcessor
implements EnvironmentPostProcessor, Ordered, ApplicationListener<ApplicationPreparedEvent> {
private static final DeferredLog logger = new DeferredLog();
private static final String VCAP_APPLICATION = "VCAP_APPLICATION";
private static final String VCAP_SERVICES = "VCAP_SERVICES";
private final Log logger;
private final boolean switchableLogger;
// Before ConfigFileApplicationListener so values there can use these ones
private int order = ConfigFileApplicationListener.DEFAULT_ORDER - 1;
private int order = ConfigDataEnvironmentPostProcessor.ORDER - 1;
/**
* Create a new {@link CloudFoundryVcapEnvironmentPostProcessor} instance.
* @deprecated since 2.4.0 in favor of
* {@link #CloudFoundryVcapEnvironmentPostProcessor(Log)}
*/
@Deprecated
public CloudFoundryVcapEnvironmentPostProcessor() {
this.logger = new DeferredLog();
this.switchableLogger = true;
}
/**
* Create a new {@link CloudFoundryVcapEnvironmentPostProcessor} instance.
* @param logger the logger to use
*/
public CloudFoundryVcapEnvironmentPostProcessor(Log logger) {
this.logger = logger;
this.switchableLogger = false;
}
public void setOrder(int order) {
this.order = order;
@ -128,9 +152,17 @@ public class CloudFoundryVcapEnvironmentPostProcessor
}
}
/**
* Event listener used to switch logging.
* @deprecated since 2.4.0 in favor of only using {@link EnvironmentPostProcessor}
* callbacks
*/
@Deprecated
@Override
public void onApplicationEvent(ApplicationPreparedEvent event) {
logger.switchTo(CloudFoundryVcapEnvironmentPostProcessor.class);
if (this.switchableLogger) {
((DeferredLog) this.logger).switchTo(CloudFoundryVcapEnvironmentPostProcessor.class);
}
}
private void addWithPrefix(Properties properties, Properties other, String prefix) {
@ -148,7 +180,7 @@ public class CloudFoundryVcapEnvironmentPostProcessor
extractPropertiesFromApplication(properties, map);
}
catch (Exception ex) {
logger.error("Could not parse VCAP_APPLICATION", ex);
this.logger.error("Could not parse VCAP_APPLICATION", ex);
}
return properties;
}
@ -161,7 +193,7 @@ public class CloudFoundryVcapEnvironmentPostProcessor
extractPropertiesFromServices(properties, map);
}
catch (Exception ex) {
logger.error("Could not parse VCAP_SERVICES", ex);
this.logger.error("Could not parse VCAP_SERVICES", ex);
}
return properties;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* 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.
@ -20,6 +20,7 @@ import org.springframework.boot.ansi.AnsiOutput;
import org.springframework.boot.ansi.AnsiOutput.Enabled;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.env.EnvironmentPostProcessorApplicationListener;
import org.springframework.context.ApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
@ -46,8 +47,8 @@ public class AnsiOutputApplicationListener
@Override
public int getOrder() {
// Apply after ConfigFileApplicationListener has called EnvironmentPostProcessors
return ConfigFileApplicationListener.DEFAULT_ORDER + 1;
// Apply after EnvironmentPostProcessorApplicationListener
return EnvironmentPostProcessorApplicationListener.DEFAULT_ORDER + 1;
}
}

View File

@ -0,0 +1,92 @@
/*
* 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.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
import org.springframework.util.Assert;
/**
* Configuration data that has been loaded from an external {@link ConfigDataLocation
* location} and may ultimately contribute {@link PropertySource property sources} to
* Spring's {@link Environment}.
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.4.0
* @see ConfigDataLocationResolver
* @see ConfigDataLoader
*/
public final class ConfigData {
private final List<PropertySource<?>> propertySources;
private final Set<Option> options;
/**
* Create a new {@link ConfigData} instance.
* @param propertySources the config data property sources in ascending priority
* order.
* @param options the config data options
*/
public ConfigData(Collection<? extends PropertySource<?>> propertySources, Option... options) {
Assert.notNull(propertySources, "PropertySources must not be null");
Assert.notNull(options, "Options must not be null");
this.propertySources = Collections.unmodifiableList(new ArrayList<>(propertySources));
this.options = Collections.unmodifiableSet(
(options.length != 0) ? EnumSet.copyOf(Arrays.asList(options)) : EnumSet.noneOf(Option.class));
}
/**
* Return the configuration data property sources in ascending priority order. If the
* same key is contained in more than one of the sources, then the later source will
* win.
* @return the config data property sources
*/
public List<PropertySource<?>> getPropertySources() {
return this.propertySources;
}
/**
* Return a set of {@link Option config data options} for this source.
* @return the config data options
*/
public Set<Option> getOptions() {
return this.options;
}
/**
* Option flags that can be applied config data.
*/
public enum Option {
/**
* Ignore all imports properties from the sources.
*/
IGNORE_IMPORTS;
}
}

View File

@ -0,0 +1,100 @@
/*
* 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.cloud.CloudPlatform;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.core.env.Environment;
import org.springframework.core.style.ToStringCreator;
/**
* Context information used when determining when to activate
* {@link ConfigDataEnvironmentContributor contributed} {@link ConfigData}.
*
* @author Phillip Webb
*/
class ConfigDataActivationContext {
private final CloudPlatform cloudPlatform;
private final Profiles profiles;
/**
* Create a new {@link ConfigDataActivationContext} instance before any profiles have
* been activated.
* @param environment the source environment
* @param binder a binder providing access to relevant config data contributions
*/
ConfigDataActivationContext(Environment environment, Binder binder) {
this.cloudPlatform = deduceCloudPlatform(environment, binder);
this.profiles = null;
}
/**
* Create a new {@link ConfigDataActivationContext} instance with the given
* {@link CloudPlatform} and {@link Profiles}.
* @param cloudPlatform the cloud platform
* @param profiles the profiles
*/
ConfigDataActivationContext(CloudPlatform cloudPlatform, Profiles profiles) {
this.cloudPlatform = cloudPlatform;
this.profiles = profiles;
}
private CloudPlatform deduceCloudPlatform(Environment environment, Binder binder) {
for (CloudPlatform candidate : CloudPlatform.values()) {
if (candidate.isEnforced(binder)) {
return candidate;
}
}
return CloudPlatform.getActive(environment);
}
/**
* Return a new {@link ConfigDataActivationContext} with specific profiles.
* @param profiles the profiles
* @return a new {@link ConfigDataActivationContext} with specific profiles
*/
ConfigDataActivationContext withProfiles(Profiles profiles) {
return new ConfigDataActivationContext(this.cloudPlatform, profiles);
}
/**
* Return the active {@link CloudPlatform} or {@code null}.
* @return the active cloud platform
*/
CloudPlatform getCloudPlatform() {
return this.cloudPlatform;
}
/**
* Return profile information if it is available.
* @return profile information or {@code null}
*/
Profiles getProfiles() {
return this.profiles;
}
@Override
public String toString() {
ToStringCreator creator = new ToStringCreator(this);
creator.append("cloudPlatform", this.cloudPlatform);
creator.append("profiles", this.profiles);
return creator.toString();
}
}

View File

@ -0,0 +1,260 @@
/*
* 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.util.ArrayList;
import java.util.Collection;
import java.util.List;
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.DefaultPropertiesPropertySource;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.log.LogMessage;
import org.springframework.util.StringUtils;
/**
* Wrapper around a {@link ConfigurableEnvironment} that can be used to import and apply
* {@link ConfigData}. Configures the initial set of
* {@link ConfigDataEnvironmentContributors} by wrapping property sources from the Spring
* {@link Environment} and adding the initial set of imports.
* <p>
* The initial imports can be influenced via the {@link #LOCATION_PROPERTY},
* {@value #ADDITIONAL_LOCATION_PROPERTY} and {@value #SPRING_CONFIG_IMPORT} properties.
* If not explicit properties are set, the {@link #DEFAULT_SEARCH_LOCATIONS} will be used.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class ConfigDataEnvironment {
/**
* Property used override the imported locations.
*/
static final String LOCATION_PROPERTY = "spring.config.location";
/**
* Property used to provide additional locations to import.
*/
static final String ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location";
/**
* Property used to provide additional locations to import.
*/
static final String SPRING_CONFIG_IMPORT = "spring.config.import";
/**
* Default search locations used if not {@link #LOCATION_PROPERTY} is found.
*/
static final String[] DEFAULT_SEARCH_LOCATIONS = { "classpath:/", "classpath:/config/", "file:./",
"file:./config/*/", "file:./config/" };
private static final String[] EMPTY_LOCATIONS = new String[0];
private final DeferredLogFactory logFactory;
private final Log logger;
private final ConfigurableEnvironment environment;
private final ConfigDataLocationResolvers resolvers;
private final Collection<String> additionalProfiles;
private final ConfigDataLoaders loaders;
private final ConfigDataEnvironmentContributors contributors;
/**
* Create a new {@link ConfigDataEnvironment} instance.
* @param logFactory the deferred log factory
* @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<String> additionalProfiles) {
Binder binder = Binder.get(environment);
UseLegacyConfigProcessingException.throwIfRequested(binder);
this.logFactory = logFactory;
this.logger = logFactory.getLog(getClass());
this.environment = environment;
this.resolvers = createConfigDataLocationResolvers(logFactory, binder, resourceLoader);
this.additionalProfiles = additionalProfiles;
this.loaders = new ConfigDataLoaders(logFactory);
this.contributors = createContributors(binder);
}
protected ConfigDataLocationResolvers createConfigDataLocationResolvers(DeferredLogFactory logFactory,
Binder binder, ResourceLoader resourceLoader) {
return new ConfigDataLocationResolvers(logFactory, binder, resourceLoader);
}
private ConfigDataEnvironmentContributors createContributors(Binder binder) {
this.logger.trace("Building config data environment contributors");
MutablePropertySources propertySources = this.environment.getPropertySources();
List<ConfigDataEnvironmentContributor> contributors = new ArrayList<>(propertySources.size() + 10);
PropertySource<?> defaultPropertySource = null;
for (PropertySource<?> propertySource : propertySources) {
if (DefaultPropertiesPropertySource.hasMatchingName(propertySource)) {
defaultPropertySource = propertySource;
}
else {
this.logger.trace(LogMessage.format("Creating wrapped config data contributor for '%s'",
propertySource.getName()));
contributors.add(ConfigDataEnvironmentContributor.ofExisting(propertySource));
}
}
contributors.addAll(getInitialImportContributors(binder));
if (defaultPropertySource != null) {
this.logger.trace("Creating wrapped config data contributor for default property source");
contributors.add(ConfigDataEnvironmentContributor.ofExisting(defaultPropertySource));
}
return new ConfigDataEnvironmentContributors(this.logFactory, contributors);
}
ConfigDataEnvironmentContributors getContributors() {
return this.contributors;
}
private List<ConfigDataEnvironmentContributor> getInitialImportContributors(Binder binder) {
List<ConfigDataEnvironmentContributor> initialContributors = new ArrayList<>();
addInitialImportContributors(initialContributors,
binder.bind(SPRING_CONFIG_IMPORT, String[].class).orElse(EMPTY_LOCATIONS));
addInitialImportContributors(initialContributors,
binder.bind(ADDITIONAL_LOCATION_PROPERTY, String[].class).orElse(EMPTY_LOCATIONS));
addInitialImportContributors(initialContributors,
binder.bind(LOCATION_PROPERTY, String[].class).orElse(DEFAULT_SEARCH_LOCATIONS));
return initialContributors;
}
private void addInitialImportContributors(List<ConfigDataEnvironmentContributor> initialContributors,
String[] locations) {
for (int i = locations.length - 1; i >= 0; i--) {
initialContributors.add(createInitialImportContributor(locations[i]));
}
}
private ConfigDataEnvironmentContributor createInitialImportContributor(String location) {
this.logger.trace(LogMessage.format("Adding initial config data import from location '%s'", location));
return ConfigDataEnvironmentContributor.ofInitialImport(location);
}
/**
* Process all contributions and apply any newly imported property sources to the
* {@link Environment}.
*/
void processAndApply() {
ConfigDataImporter importer = new ConfigDataImporter(this.resolvers, this.loaders);
ConfigDataEnvironmentContributors contributors = processInitial(this.contributors, importer);
ConfigDataActivationContext activationContext = createActivationContext(contributors);
contributors = processWithoutProfiles(contributors, importer, activationContext);
activationContext = withProfiles(contributors, activationContext);
contributors = processWithProfiles(contributors, importer, activationContext);
applyToEnvironment(contributors, activationContext);
}
private ConfigDataEnvironmentContributors processInitial(ConfigDataEnvironmentContributors contributors,
ConfigDataImporter importer) {
this.logger.trace("Processing initial config data environment contributors without activation context");
return contributors.withProcessedImports(importer, null);
}
private ConfigDataActivationContext createActivationContext(ConfigDataEnvironmentContributors contributors) {
this.logger.trace("Creating config data activation context from initial contributions");
Binder binder = contributors.getBinder(null, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE);
try {
return new ConfigDataActivationContext(this.environment, binder);
}
catch (BindException ex) {
if (ex.getCause() instanceof InactiveConfigDataAccessException) {
throw (InactiveConfigDataAccessException) ex.getCause();
}
throw ex;
}
}
private ConfigDataEnvironmentContributors processWithoutProfiles(ConfigDataEnvironmentContributors contributors,
ConfigDataImporter importer, ConfigDataActivationContext activationContext) {
this.logger.trace("Processing config data environment contributors with intial activation context");
return contributors.withProcessedImports(importer, activationContext);
}
private ConfigDataActivationContext withProfiles(ConfigDataEnvironmentContributors contributors,
ConfigDataActivationContext activationContext) {
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);
return activationContext.withProfiles(profiles);
}
catch (BindException ex) {
if (ex.getCause() instanceof InactiveConfigDataAccessException) {
throw (InactiveConfigDataAccessException) ex.getCause();
}
throw ex;
}
}
private ConfigDataEnvironmentContributors processWithProfiles(ConfigDataEnvironmentContributors contributors,
ConfigDataImporter importer, ConfigDataActivationContext activationContext) {
this.logger.trace("Processing config data environment contributors with profile activation context");
return contributors.withProcessedImports(importer, activationContext);
}
private void applyToEnvironment(ConfigDataEnvironmentContributors contributors,
ConfigDataActivationContext activationContext) {
checkForInvalidProperties(contributors);
MutablePropertySources propertySources = this.environment.getPropertySources();
this.logger.trace("Applying config data environment contributions");
for (ConfigDataEnvironmentContributor contributor : contributors) {
if (contributor.getKind() == ConfigDataEnvironmentContributor.Kind.IMPORTED
&& contributor.getPropertySource() != null) {
if (!contributor.isActive(activationContext)) {
this.logger.trace(LogMessage.format("Skipping inactive property source '%s'",
contributor.getPropertySource().getName()));
}
else {
this.logger.trace(LogMessage.format("Adding imported property source '%s'",
contributor.getPropertySource().getName()));
propertySources.addLast(contributor.getPropertySource());
}
}
}
DefaultPropertiesPropertySource.moveToEnd(propertySources);
Profiles profiles = activationContext.getProfiles();
this.logger.trace(LogMessage.format("Setting default profies: %s", profiles.getDefault()));
this.environment.setDefaultProfiles(StringUtils.toStringArray(profiles.getDefault()));
this.logger.trace(LogMessage.format("Setting active profiles: %s", profiles.getActive()));
this.environment.setActiveProfiles(StringUtils.toStringArray(profiles.getActive()));
}
private void checkForInvalidProperties(ConfigDataEnvironmentContributors contributors) {
for (ConfigDataEnvironmentContributor contributor : contributors) {
InvalidConfigDataPropertyException.throwOrWarn(this.logger, contributor);
}
}
}

View File

@ -0,0 +1,395 @@
/*
* 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.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
/**
* A single element that may directly or indirectly contribute configuration data to the
* {@link Environment}. There are several different {@link Kind kinds} of contributor, all
* are immutable and will be replaced with new versions as imports are processed.
* <p>
* Contributors may provide a set of imports that should be processed and ultimately
* turned into children. There are two distinct import phases:
* <ul>
* <li>{@link ImportPhase#BEFORE_PROFILE_ACTIVATION Before} profiles have been
* activated.</li>
* <li>{@link ImportPhase#AFTER_PROFILE_ACTIVATION After} profiles have been
* activated.</li>
* </ul>
* In each phase <em>all</em> imports will be resolved before they are loaded.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironmentContributor> {
private final ConfigDataLocation location;
private final PropertySource<?> propertySource;
private final ConfigurationPropertySource configurationPropertySource;
private final ConfigDataProperties properties;
private final Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children;
private final Kind kind;
/**
* Create a new {@link ConfigDataEnvironmentContributor} instance.
* @param kind the contributor kind
* @param location the location that contributed the data or {@code null}
* @param propertySource the property source for the data or {@code null}
* @param configurationPropertySource the configuration property source for the data
* or {@code null}
* @param properties the config data properties or {@code null}
* @param children the children of this contributor at each {@link ImportPhase}
*/
ConfigDataEnvironmentContributor(Kind kind, ConfigDataLocation location, PropertySource<?> propertySource,
ConfigurationPropertySource configurationPropertySource, ConfigDataProperties properties,
Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children) {
this.kind = kind;
this.location = location;
this.properties = properties;
this.propertySource = propertySource;
this.configurationPropertySource = configurationPropertySource;
this.children = (children != null) ? children : Collections.emptyMap();
}
/**
* Return the contributor kind.
* @return the kind of contributor
*/
Kind getKind() {
return this.kind;
}
/**
* Return if this contributor is currently active.
* @param activationContext the activation context
* @return if the contributor is active
*/
boolean isActive(ConfigDataActivationContext activationContext) {
return this.properties == null || this.properties.isActive(activationContext);
}
/**
* Return the location that contributed this instance.
* @return the location or {@code null}
*/
ConfigDataLocation getLocation() {
return this.location;
}
/**
* Return the property source for this contributor.
* @return the property source or {@code null}
*/
PropertySource<?> getPropertySource() {
return this.propertySource;
}
/**
* Return the configuration property source for this contributor.
* @return the configuration property source or {@code null}
*/
ConfigurationPropertySource getConfigurationPropertySource() {
return this.configurationPropertySource;
}
/**
* Return any imports requested by this contributor.
* @return the imports
*/
List<String> getImports() {
return (this.properties != null) ? this.properties.getImports() : Collections.emptyList();
}
/**
* Return true if this contributor has imports that have not yet been processed in the
* given phase.
* @param importPhase the import phase
* @return if there are unprocessed imports
*/
boolean hasUnprocessedImports(ImportPhase importPhase) {
if (getImports().isEmpty()) {
return false;
}
return !this.children.containsKey(importPhase);
}
/**
* Return children of this contributor for the given phase.
* @param importPhase the import phase
* @return a list of children
*/
List<ConfigDataEnvironmentContributor> getChildren(ImportPhase importPhase) {
return this.children.getOrDefault(importPhase, Collections.emptyList());
}
/**
* Returns a {@link Stream} that traverses this contributor and all its children in
* priority order.
* @return the stream
*/
Stream<ConfigDataEnvironmentContributor> stream() {
return StreamSupport.stream(spliterator(), false);
}
/**
* Returns an {@link Iterator} that traverses this contributor and all its children in
* priority order.
* @return the iterator
* @see java.lang.Iterable#iterator()
*/
@Override
public Iterator<ConfigDataEnvironmentContributor> iterator() {
return new ContributorIterator();
}
/**
* Create a new {@link ConfigDataEnvironmentContributor} instance with a new set of
* children for the given phase.
* @param importPhase the import phase
* @param children the new children
* @return a new contributor instance
*/
ConfigDataEnvironmentContributor withChildren(ImportPhase importPhase,
List<ConfigDataEnvironmentContributor> children) {
Map<ImportPhase, List<ConfigDataEnvironmentContributor>> updatedChildren = new LinkedHashMap<>(this.children);
updatedChildren.put(importPhase, children);
return new ConfigDataEnvironmentContributor(this.kind, this.location, this.propertySource,
this.configurationPropertySource, this.properties, updatedChildren);
}
/**
* Create a new {@link ConfigDataEnvironmentContributor} instance where a existing
* child is replaced.
* @param existing the existing node that should be replaced
* @param replacement the replacement node that should be used instead
* @return a new {@link ConfigDataEnvironmentContributor} instance
*/
ConfigDataEnvironmentContributor withReplacement(ConfigDataEnvironmentContributor existing,
ConfigDataEnvironmentContributor replacement) {
if (this == existing) {
return replacement;
}
Map<ImportPhase, List<ConfigDataEnvironmentContributor>> updatedChildren = new LinkedHashMap<>(
this.children.size());
this.children.forEach((importPhase, contributors) -> {
List<ConfigDataEnvironmentContributor> updatedContributors = new ArrayList<>(contributors.size());
for (ConfigDataEnvironmentContributor contributor : contributors) {
updatedContributors.add(contributor.withReplacement(existing, replacement));
}
updatedChildren.put(importPhase, Collections.unmodifiableList(updatedContributors));
});
return new ConfigDataEnvironmentContributor(this.kind, this.location, this.propertySource,
this.configurationPropertySource, this.properties, updatedChildren);
}
/**
* Factory method to create a {@link Kind#ROOT root} contributor.
* @param contributors the immediate children of the root
* @return a new {@link ConfigDataEnvironmentContributor} instance
*/
static ConfigDataEnvironmentContributor of(List<ConfigDataEnvironmentContributor> contributors) {
Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children = new LinkedHashMap<>();
children.put(ImportPhase.BEFORE_PROFILE_ACTIVATION, Collections.unmodifiableList(contributors));
return new ConfigDataEnvironmentContributor(Kind.ROOT, null, null, null, null, children);
}
/**
* Factory method to create a {@link Kind#INITIAL_IMPORT initial import} contributor.
* This contributor is used to trigger initial imports of additional contributors. It
* does not contribute any properties itself.
* @param importLocation the initial import location
* @return a new {@link ConfigDataEnvironmentContributor} instance
*/
static ConfigDataEnvironmentContributor ofInitialImport(String importLocation) {
List<String> imports = Collections.singletonList(importLocation);
ConfigDataProperties properties = new ConfigDataProperties(imports, null);
return new ConfigDataEnvironmentContributor(Kind.INITIAL_IMPORT, null, null, null, properties, null);
}
/**
* Factory method to create a contributor that wraps an {@link Kind#EXISTING existing}
* property source. The contributor provides access to existing properties, but
* doesn't actively import any additional contributors.
* @param propertySource the property source to wrap
* @return a new {@link ConfigDataEnvironmentContributor} instance
*/
static ConfigDataEnvironmentContributor ofExisting(PropertySource<?> propertySource) {
return new ConfigDataEnvironmentContributor(Kind.EXISTING, null, propertySource,
ConfigurationPropertySource.from(propertySource), null, null);
}
/**
* Factory method to create a {@link Kind#IMPORTED imported} contributor. This
* contributor has been actively imported from another contributor and may itself
* import further contributors later.
* @param location the location of imported config data
* @param configData the config data
* @param propertySourceIndex the index of the property source that should be used
* @param activationContext the current activation context
* @return a new {@link ConfigDataEnvironmentContributor} instance
*/
static ConfigDataEnvironmentContributor ofImported(ConfigDataLocation location, ConfigData configData,
int propertySourceIndex, ConfigDataActivationContext activationContext) {
PropertySource<?> propertySource = configData.getPropertySources().get(propertySourceIndex);
ConfigurationPropertySource configurationPropertySource = ConfigurationPropertySource.from(propertySource);
Binder binder = new Binder(configurationPropertySource);
UseLegacyConfigProcessingException.throwIfRequested(binder);
ConfigDataProperties properties = ConfigDataProperties.get(binder);
if (configData.getOptions().contains(ConfigData.Option.IGNORE_IMPORTS)) {
properties = properties.withoutImports();
}
return new ConfigDataEnvironmentContributor(Kind.IMPORTED, location, propertySource,
configurationPropertySource, properties, null);
}
/**
* The various kinds of contributor.
*/
enum Kind {
/**
* A root contributor used contain the initial set of children.
*/
ROOT,
/**
* An initial import that needs to be processed.
*/
INITIAL_IMPORT,
/**
* An existing property source that contributes properties but no imports.
*/
EXISTING,
/**
* A contributor with {@link ConfigData} imported from another contributor.
*/
IMPORTED;
}
/**
* Import phases that can be used when obtaining imports.
*/
enum ImportPhase {
/**
* The phase before profiles have been activated.
*/
BEFORE_PROFILE_ACTIVATION,
/**
* The phase after profiles have been activated.
*/
AFTER_PROFILE_ACTIVATION;
/**
* Return the {@link ImportPhase} based on the given activation context.
* @param activationContext the activation context
* @return the import phase
*/
static ImportPhase get(ConfigDataActivationContext activationContext) {
if (activationContext != null && activationContext.getProfiles() != null) {
return AFTER_PROFILE_ACTIVATION;
}
return BEFORE_PROFILE_ACTIVATION;
}
}
/**
* Iterator that traverses the contributor tree.
*/
private final class ContributorIterator implements Iterator<ConfigDataEnvironmentContributor> {
private ImportPhase phase;
private Iterator<ConfigDataEnvironmentContributor> children;
private Iterator<ConfigDataEnvironmentContributor> current;
private ConfigDataEnvironmentContributor next;
private ContributorIterator() {
this.phase = ImportPhase.AFTER_PROFILE_ACTIVATION;
this.children = getChildren(this.phase).iterator();
this.current = Collections.emptyIterator();
}
@Override
public boolean hasNext() {
return fetchIfNecessary() != null;
}
@Override
public ConfigDataEnvironmentContributor next() {
ConfigDataEnvironmentContributor next = fetchIfNecessary();
if (next == null) {
throw new NoSuchElementException();
}
this.next = null;
return next;
}
private ConfigDataEnvironmentContributor fetchIfNecessary() {
if (this.next != null) {
return this.next;
}
if (this.current.hasNext()) {
this.next = this.current.next();
return this.next;
}
if (this.children.hasNext()) {
this.current = this.children.next().iterator();
return fetchIfNecessary();
}
if (this.phase == ImportPhase.AFTER_PROFILE_ACTIVATION) {
this.phase = ImportPhase.BEFORE_PROFILE_ACTIVATION;
this.children = getChildren(this.phase).iterator();
return fetchIfNecessary();
}
if (this.phase == ImportPhase.BEFORE_PROFILE_ACTIVATION) {
this.phase = null;
this.next = ConfigDataEnvironmentContributor.this;
return this.next;
}
return null;
}
}
}

View File

@ -0,0 +1,78 @@
/*
* 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.context.properties.bind.PlaceholdersResolver;
import org.springframework.boot.origin.Origin;
import org.springframework.boot.origin.OriginLookup;
import org.springframework.core.env.PropertySource;
import org.springframework.util.PropertyPlaceholderHelper;
import org.springframework.util.SystemPropertyUtils;
/**
* {@link PlaceholdersResolver} backed by one or more
* {@link ConfigDataEnvironmentContributor} instances.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class ConfigDataEnvironmentContributorPlaceholdersResolver implements PlaceholdersResolver {
private final Iterable<ConfigDataEnvironmentContributor> contributors;
private final ConfigDataActivationContext activationContext;
private final boolean failOnResolveFromInactiveContributor;
private final PropertyPlaceholderHelper helper;
ConfigDataEnvironmentContributorPlaceholdersResolver(Iterable<ConfigDataEnvironmentContributor> contributors,
ConfigDataActivationContext activationContext, boolean failOnResolveFromInactiveContributor) {
this.contributors = contributors;
this.activationContext = activationContext;
this.failOnResolveFromInactiveContributor = failOnResolveFromInactiveContributor;
this.helper = new PropertyPlaceholderHelper(SystemPropertyUtils.PLACEHOLDER_PREFIX,
SystemPropertyUtils.PLACEHOLDER_SUFFIX, SystemPropertyUtils.VALUE_SEPARATOR, true);
}
@Override
public Object resolvePlaceholders(Object value) {
if (value instanceof String) {
return this.helper.replacePlaceholders((String) value, this::resolvePlaceholder);
}
return value;
}
private String resolvePlaceholder(String placeholder) {
Object result = null;
for (ConfigDataEnvironmentContributor contributor : this.contributors) {
PropertySource<?> propertySource = contributor.getPropertySource();
Object value = (propertySource != null) ? propertySource.getProperty(placeholder) : null;
if (value != null && !contributor.isActive(this.activationContext)) {
if (this.failOnResolveFromInactiveContributor) {
Origin origin = OriginLookup.getOrigin(propertySource, placeholder);
throw new InactiveConfigDataAccessException(propertySource, contributor.getLocation(), placeholder,
origin);
}
value = null;
}
result = (result != null) ? result : value;
}
return (result != null) ? String.valueOf(result) : null;
}
}

View File

@ -0,0 +1,251 @@
/*
* 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.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import org.apache.commons.logging.Log;
import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.ImportPhase;
import org.springframework.boot.context.properties.bind.BindContext;
import org.springframework.boot.context.properties.bind.BindHandler;
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.logging.DeferredLogFactory;
import org.springframework.core.log.LogMessage;
import org.springframework.util.ObjectUtils;
/**
* An immutable tree structure of {@link ConfigDataEnvironmentContributors} used to
* process imports.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class ConfigDataEnvironmentContributors implements Iterable<ConfigDataEnvironmentContributor> {
private final Log logger;
private final ConfigDataEnvironmentContributor root;
/**
* Create a new {@link ConfigDataEnvironmentContributors} instance.
* @param logFactory the log factory
* @param contributors the initial set of contributors
*/
ConfigDataEnvironmentContributors(DeferredLogFactory logFactory,
List<ConfigDataEnvironmentContributor> contributors) {
this.logger = logFactory.getLog(getClass());
this.root = ConfigDataEnvironmentContributor.of(contributors);
}
private ConfigDataEnvironmentContributors(Log logger, ConfigDataEnvironmentContributor root) {
this.logger = logger;
this.root = root;
}
/**
* Processes imports from all active contributors and return a new
* {@link ConfigDataEnvironmentContributors} instance.
* @param importer the importer used to import {@link ConfigData}
* @param activationContext the current activation context or {@code null} if the
* context has not get been created
* @return a {@link ConfigDataEnvironmentContributors} instance with all relevant
* imports have been processed
*/
ConfigDataEnvironmentContributors withProcessedImports(ConfigDataImporter importer,
ConfigDataActivationContext activationContext) {
ImportPhase importPhase = ImportPhase.get(activationContext);
this.logger.trace(LogMessage.format("Processing imports for phase %s. %s", importPhase,
(activationContext != null) ? activationContext : "no activation context"));
ConfigDataEnvironmentContributors result = this;
int processedCount = 0;
while (true) {
ConfigDataEnvironmentContributor unprocessed = getFirstUnprocessed(result, activationContext, importPhase);
if (unprocessed == null) {
this.logger.trace(LogMessage.format("Processed imports for of %d contributors", processedCount));
return result;
}
ConfigDataLocationResolverContext locationResolverContext = new ContributorLocationResolverContext(result,
unprocessed, activationContext);
List<String> imports = unprocessed.getImports();
this.logger.trace(LogMessage.format("Processing imports %s", imports));
Map<ConfigDataLocation, ConfigData> imported = importer.resolveAndLoad(activationContext,
locationResolverContext, 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.getRoot().withReplacement(unprocessed, processed));
processedCount++;
}
}
private ConfigDataEnvironmentContributor getFirstUnprocessed(ConfigDataEnvironmentContributors contributors,
ConfigDataActivationContext activationContext, ImportPhase importPhase) {
for (ConfigDataEnvironmentContributor contributor : contributors.getRoot()) {
if (contributor.isActive(activationContext) && contributor.hasUnprocessedImports(importPhase)) {
return contributor;
}
}
return null;
}
private List<ConfigDataEnvironmentContributor> asContributors(ConfigDataActivationContext activationContext,
Map<ConfigDataLocation, ConfigData> imported) {
List<ConfigDataEnvironmentContributor> contributors = new ArrayList<>(imported.size() * 5);
imported.forEach((location, data) -> {
for (int i = data.getPropertySources().size() - 1; i >= 0; i--) {
contributors.add(ConfigDataEnvironmentContributor.ofImported(location, data, i, activationContext));
}
});
return Collections.unmodifiableList(contributors);
}
/**
* Returns the root contributor.
* @return the root contributor.
*/
ConfigDataEnvironmentContributor getRoot() {
return this.root;
}
/**
* Return a {@link Binder} that works against all active contributors.
* @param activationContext the activation context
* @param options binder options to apply
* @return a binder instance
*/
Binder getBinder(ConfigDataActivationContext activationContext, BinderOption... options) {
return getBinder(activationContext, ObjectUtils.isEmpty(options) ? EnumSet.noneOf(BinderOption.class)
: EnumSet.copyOf(Arrays.asList(options)));
}
private Binder getBinder(ConfigDataActivationContext activationContext, Set<BinderOption> options) {
boolean failOnInactiveSource = options.contains(BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE);
Iterable<ConfigurationPropertySource> sources = () -> getBinderSources(activationContext,
!failOnInactiveSource);
PlaceholdersResolver placeholdersResolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(this.root,
activationContext, failOnInactiveSource);
BindHandler bindHandler = !failOnInactiveSource ? null : new InactiveSourceChecker(activationContext);
return new Binder(sources, placeholdersResolver, null, null, bindHandler);
}
private Iterator<ConfigurationPropertySource> getBinderSources(ConfigDataActivationContext activationContext,
boolean filterInactive) {
Stream<ConfigDataEnvironmentContributor> sources = this.root.stream()
.filter(this::hasConfigurationPropertySource);
if (filterInactive) {
sources = sources.filter((contributor) -> contributor.isActive(activationContext));
}
return sources.map(ConfigDataEnvironmentContributor::getConfigurationPropertySource).iterator();
}
private boolean hasConfigurationPropertySource(ConfigDataEnvironmentContributor contributor) {
return contributor.getConfigurationPropertySource() != null;
}
@Override
public Iterator<ConfigDataEnvironmentContributor> iterator() {
return this.root.iterator();
}
/**
* {@link ConfigDataLocationResolverContext} backed by a
* {@link ConfigDataEnvironmentContributor}.
*/
private static class ContributorLocationResolverContext implements ConfigDataLocationResolverContext {
private final ConfigDataEnvironmentContributors contributors;
private final ConfigDataEnvironmentContributor contributor;
private final ConfigDataActivationContext activationContext;
private volatile Binder binder;
ContributorLocationResolverContext(ConfigDataEnvironmentContributors contributors,
ConfigDataEnvironmentContributor contributor, ConfigDataActivationContext activationContext) {
this.contributors = contributors;
this.contributor = contributor;
this.activationContext = activationContext;
}
@Override
public Binder getBinder() {
Binder binder = this.binder;
if (binder == null) {
binder = this.contributors.getBinder(this.activationContext);
this.binder = binder;
}
return binder;
}
@Override
public ConfigDataLocation getParent() {
return this.contributor.getLocation();
}
}
private class InactiveSourceChecker implements BindHandler {
private final ConfigDataActivationContext activationContext;
InactiveSourceChecker(ConfigDataActivationContext activationContext) {
this.activationContext = activationContext;
}
@Override
public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target, BindContext context,
Object result) {
for (ConfigDataEnvironmentContributor contributor : ConfigDataEnvironmentContributors.this) {
if (!contributor.isActive(this.activationContext)) {
InactiveConfigDataAccessException.throwIfPropertyFound(contributor, name);
}
}
return result;
}
}
/**
* Binder options that can be used with
* {@link ConfigDataEnvironmentContributors#getBinder(ConfigDataActivationContext, BinderOption...)}.
*/
enum BinderOption {
/**
* Throw an exception if an inactive contributor contains a bound value.
*/
FAIL_ON_BIND_TO_INACTIVE_SOURCE;
}
}

View File

@ -0,0 +1,110 @@
/*
* 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.util.Collection;
import org.apache.commons.logging.Log;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.log.LogMessage;
/**
* {@link EnvironmentPostProcessor} that loads and apply {@link ConfigData} to Spring's
* {@link Environment}.
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.4.0
*/
public class ConfigDataEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
/**
* The default order for the processor.
*/
public static final int ORDER = Ordered.HIGHEST_PRECEDENCE + 10;
private final DeferredLogFactory logFactory;
private final Log logger;
public ConfigDataEnvironmentPostProcessor(DeferredLogFactory logFactory) {
this.logFactory = logFactory;
this.logger = logFactory.getLog(getClass());
}
@Override
public int getOrder() {
return ORDER;
}
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
addPropertySources(environment, application.getResourceLoader(), application.getAdditionalProfiles());
}
protected final void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader,
Collection<String> additionalProfiles) {
try {
this.logger.trace("Post-processing environment to add config data");
resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader();
getConfigDataEnvironment(environment, resourceLoader, additionalProfiles).processAndApply();
}
catch (UseLegacyConfigProcessingException ex) {
this.logger.debug(LogMessage.format("Switching to legacy config file processing [%s]",
ex.getConfigurationProperty()));
postProcessUsingLegacyApplicationListener(environment, resourceLoader);
}
}
ConfigDataEnvironment getConfigDataEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader,
Collection<String> additionalProfiles) {
return new ConfigDataEnvironment(this.logFactory, environment, resourceLoader, additionalProfiles);
}
private void postProcessUsingLegacyApplicationListener(ConfigurableEnvironment environment,
ResourceLoader resourceLoader) {
getLegacyListener().addPropertySources(environment, resourceLoader);
}
@SuppressWarnings("deprecation")
LegacyConfigFileApplicationListener getLegacyListener() {
return new LegacyConfigFileApplicationListener(this.logFactory.getLog(ConfigFileApplicationListener.class));
}
@SuppressWarnings("deprecation")
static class LegacyConfigFileApplicationListener extends ConfigFileApplicationListener {
LegacyConfigFileApplicationListener(Log logger) {
super(logger);
}
@Override
public void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
super.addPropertySources(environment, resourceLoader);
}
}
}

View File

@ -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;
/**
* Abstract base class for configuration data exceptions.
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.4.0
*/
public abstract class ConfigDataException extends RuntimeException {
/**
* Create a new {@link ConfigDataException} instance.
* @param message the exception message
* @param cause the exception cause
*/
protected ConfigDataException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,83 @@
/*
* 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.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Imports {@link ConfigData} by {@link ConfigDataLocationResolver resolving} and
* {@link ConfigDataLoader loading} imports. {@link ConfigDataLocation locations} are
* tracked to ensure that they are not imported multiple times.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class ConfigDataImporter {
private final ConfigDataLocationResolvers resolvers;
private final ConfigDataLoaders loaders;
private final Set<ConfigDataLocation> loadedLocations = new HashSet<>();
/**
* Create a new {@link ConfigDataImporter} instance.
* @param resolvers the config data location resolvers
* @param loaders the condif data loaders
*/
ConfigDataImporter(ConfigDataLocationResolvers resolvers, ConfigDataLoaders loaders) {
this.resolvers = resolvers;
this.loaders = loaders;
}
/**
* Resolve and load the given list of locations, filtering any that have been
* previously loaded.
* @param activationContext the activation context
* @param locationResolverContext the location resolver context
* @param locations the locations to resolve
* @return a map of the loaded locations and data
*/
Map<ConfigDataLocation, ConfigData> resolveAndLoad(ConfigDataActivationContext activationContext,
ConfigDataLocationResolverContext locationResolverContext, List<String> locations) {
try {
Profiles profiles = (activationContext != null) ? activationContext.getProfiles() : null;
return load(this.resolvers.resolveAll(locationResolverContext, locations, profiles));
}
catch (IOException ex) {
throw new IllegalStateException("IO errorload imports from " + locations, ex);
}
}
private Map<ConfigDataLocation, ConfigData> load(List<ConfigDataLocation> locations) throws IOException {
Map<ConfigDataLocation, ConfigData> 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));
}
}
return Collections.unmodifiableMap(result);
}
}

View File

@ -0,0 +1,58 @@
/*
* 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 org.apache.commons.logging.Log;
/**
* Strategy class that can be used used to load {@link ConfigData} instances from a
* {@link ConfigDataLocation location}. Implementations should be added as a
* {@code spring.factories} entries. The following constructor parameter types are
* supported:
* <ul>
* <li>{@link Log} - if the resolver needs deferred logging</li>
* </ul>
* <p>
* Multiple loaders cannot claim the same location.
*
* @param <L> the location type
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.4.0
*/
public interface ConfigDataLoader<L extends ConfigDataLocation> {
/**
* Returns if the specified location can be loaded by this instance.
* @param location the location to check.
* @return if the location is supported by this loader
*/
default boolean isLoadable(L location) {
return true;
}
/**
* Load {@link ConfigData} for the given location.
* @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;
}

View File

@ -0,0 +1,112 @@
/*
* 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.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.commons.logging.Log;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.ResolvableType;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.log.LogMessage;
import org.springframework.util.Assert;
/**
* A collection of {@link ConfigDataLoader} instances loaded via {@code spring.factories}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class ConfigDataLoaders {
private final Log logger;
private final List<ConfigDataLoader<?>> loaders;
private final List<Class<?>> locationTypes;
/**
* Create a new {@link ConfigDataLoaders} instance.
* @param logFactory the deferred log factory
*/
ConfigDataLoaders(DeferredLogFactory logFactory) {
this(logFactory, SpringFactoriesLoader.loadFactoryNames(ConfigDataLoader.class, null));
}
/**
* Create a new {@link ConfigDataLoaders} instance.
* @param logFactory the deferred log factory
* @param names the {@link ConfigDataLoader} class names instantiate
*/
ConfigDataLoaders(DeferredLogFactory logFactory, List<String> names) {
this.logger = logFactory.getLog(getClass());
Instantiator<ConfigDataLoader<?>> instantiator = new Instantiator<>(ConfigDataLoader.class,
(availableParameters) -> availableParameters.add(Log.class, logFactory::getLog));
this.loaders = instantiator.instantiate(names);
this.locationTypes = getLocationTypes(this.loaders);
}
private List<Class<?>> getLocationTypes(List<ConfigDataLoader<?>> loaders) {
List<Class<?>> locationTypes = new ArrayList<>(loaders.size());
for (ConfigDataLoader<?> loader : loaders) {
locationTypes.add(getLocationType(loader));
}
return Collections.unmodifiableList(locationTypes);
}
private Class<?> getLocationType(ConfigDataLoader<?> loader) {
return ResolvableType.forClass(loader.getClass()).as(ConfigDataLoader.class).resolveGeneric();
}
/**
* Load {@link ConfigData} using the first appropriate {@link ConfigDataLoader}.
* @param <L> the condig data location type
* @param location the location to load
* @return the loaded {@link ConfigData}
* @throws IOException on IO error
*/
<L extends ConfigDataLocation> ConfigData load(L location) throws IOException {
ConfigDataLoader<L> loader = getLoader(location);
this.logger.trace(LogMessage.of(() -> "Loading " + location + " using loader " + loader.getClass().getName()));
return loader.load(location);
}
@SuppressWarnings("unchecked")
private <L extends ConfigDataLocation> ConfigDataLoader<L> getLoader(L location) {
ConfigDataLoader<L> 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<L> loader = (ConfigDataLoader<L>) candidate;
if (loader.isLoadable(location)) {
if (result != null) {
throw new IllegalStateException("Multiple loaders found for location " + location + " ["
+ candidate.getClass().getName() + "," + result.getClass().getName() + "]");
}
result = loader;
}
}
}
Assert.state(result != null, "No loader found for location '" + location + "'");
return result;
}
}

View File

@ -0,0 +1,30 @@
/*
* 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;
/**
* A location from which {@link ConfigData} can be loaded. Implementations must implement
* a valid {@link #equals(Object) equals}, {@link #hashCode() hashCode} and
* {@link #toString() toString} methods.
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.4.0
*/
public abstract class ConfigDataLocation {
}

View File

@ -0,0 +1,84 @@
/*
* 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.util.Collections;
import java.util.List;
import org.apache.commons.logging.Log;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
/**
* Strategy interface used to resolve {@link ConfigDataLocation locations} from a String
* based location address. Implementations should be added as a {@code spring.factories}
* entries. The following constructor parameter types are supported:
* <ul>
* <li>{@link Log} - if the resolver needs deferred logging</li>
* <li>{@link Binder} - if the resolver needs to obtain values from the initial
* {@link Environment}</li>
* <li>{@link ResourceLoader} - if the resolver needs a resource loader</li>
* </ul>
* <p>
* Resolvers may implement {@link Ordered} or use the {@link Order @Order} annotation. The
* first resolver that supports the given location will be used.
*
* @param <L> the location type
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.4.0
*/
public interface ConfigDataLocationResolver<L extends ConfigDataLocation> {
/**
* Returns if the specified location address can be resolved by this resolver.
* @param context the location resolver context
* @param location the location to check.
* @return if the location is supported by this resolver
*/
boolean isResolvable(ConfigDataLocationResolverContext context, String location);
/**
* Resolve a location string into one or more {@link ConfigDataLocation} instances.
* @param context the location resolver context
* @param location the location that should be resolved
* @return a list of resolved locations in ascending priority order. If the same key
* is contained in more than one of the location, then the later source will win.
*
*/
List<L> resolve(ConfigDataLocationResolverContext context, String location);
/**
* Resolve a location string into one or more {@link ConfigDataLocation} instances
* based on available profiles. This method is called once profiles have been deduced
* from the contributed values. By default this method returns an empty list.
* @param context the location resolver context
* @param location the location that should be resolved
* @param profiles profile information
* @return a list of resolved locations in ascending priority order.If the same key is
* contained in more than one of the location, then the later source will win.
*/
default List<L> resolveProfileSpecific(ConfigDataLocationResolverContext context, String location,
Profiles profiles) {
return Collections.emptyList();
}
}

View File

@ -0,0 +1,44 @@
/*
* 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.context.properties.bind.Binder;
/**
* Context provided to {@link ConfigDataLocationResolver} methods.
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.4.0
*/
public interface ConfigDataLocationResolverContext {
/**
* Provides access to a binder that can be used to obtain previously contributed
* values.
* @return a binder instance
*/
Binder getBinder();
/**
* Provides access to the parent location that triggered the resolve or {@code null}
* if there is no available parent.
* @return the parent location
*/
ConfigDataLocation getParent();
}

View File

@ -0,0 +1,150 @@
/*
* 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.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.commons.logging.Log;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.StringUtils;
/**
* A collection of {@link ConfigDataLocationResolver} instances loaded via
* {@code spring.factories}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class ConfigDataLocationResolvers {
private final List<ConfigDataLocationResolver<?>> resolvers;
/**
* Create a new {@link ConfigDataLocationResolvers} instance.
* @param logFactory a {@link DeferredLogFactory} used to inject {@link Log} instances
* @param binder a binder providing values from the initial {@link Environment}
* @param resourceLoader {@link ResourceLoader} to load resource locations
*/
ConfigDataLocationResolvers(DeferredLogFactory logFactory, Binder binder, ResourceLoader resourceLoader) {
this(logFactory, binder, resourceLoader,
SpringFactoriesLoader.loadFactoryNames(ConfigDataLocationResolver.class, null));
}
/**
* Create a new {@link ConfigDataLocationResolvers} instance.
* @param logFactory a {@link DeferredLogFactory} used to inject {@link Log} instances
* @param binder {@link Binder} providing values from the initial {@link Environment}
* @param resourceLoader {@link ResourceLoader} to load resource locations
* @param names the {@link ConfigDataLocationResolver} class names
*/
ConfigDataLocationResolvers(DeferredLogFactory logFactory, Binder binder, ResourceLoader resourceLoader,
List<String> names) {
Instantiator<ConfigDataLocationResolver<?>> instantiator = new Instantiator<>(ConfigDataLocationResolver.class,
(availableParameters) -> {
availableParameters.add(Log.class, logFactory::getLog);
availableParameters.add(Binder.class, binder);
availableParameters.add(ResourceLoader.class, resourceLoader);
});
this.resolvers = reorder(instantiator.instantiate(names));
}
private List<ConfigDataLocationResolver<?>> reorder(List<ConfigDataLocationResolver<?>> resolvers) {
List<ConfigDataLocationResolver<?>> reordered = new ArrayList<>(resolvers.size());
ResourceConfigDataLocationResolver resourceResolver = null;
for (ConfigDataLocationResolver<?> resolver : resolvers) {
if (resolver instanceof ResourceConfigDataLocationResolver) {
resourceResolver = (ResourceConfigDataLocationResolver) resolver;
}
else {
reordered.add(resolver);
}
}
if (resourceResolver != null) {
reordered.add(resourceResolver);
}
return Collections.unmodifiableList(reordered);
}
/**
* Resolve all location strings using the most appropriate
* {@link ConfigDataLocationResolver}.
* @param context the location resolver context
* @param locations the locations to resolve
* @param profiles the current profiles or {@code null}
* @return the resolved locations
*/
List<ConfigDataLocation> resolveAll(ConfigDataLocationResolverContext context, List<String> locations,
Profiles profiles) {
List<ConfigDataLocation> resolved = new ArrayList<>(locations.size());
for (String location : locations) {
resolved.addAll(resolveAll(context, location, profiles));
}
return resolved;
}
private List<ConfigDataLocation> resolveAll(ConfigDataLocationResolverContext context, String location,
Profiles profiles) {
if (!StringUtils.hasText(location)) {
return Collections.emptyList();
}
for (ConfigDataLocationResolver<?> resolver : getResolvers()) {
if (resolver.isResolvable(context, location)) {
return resolve(resolver, context, location, profiles);
}
}
throw new UnsupportedConfigDataLocationException(location);
}
private List<ConfigDataLocation> resolve(ConfigDataLocationResolver<?> resolver,
ConfigDataLocationResolverContext context, String location, Profiles profiles) {
List<ConfigDataLocation> resolved = nonNullList(resolver.resolve(context, location));
if (profiles == null) {
return resolved;
}
List<ConfigDataLocation> profileSpecific = nonNullList(
resolver.resolveProfileSpecific(context, location, profiles));
return merge(resolved, profileSpecific);
}
@SuppressWarnings("unchecked")
private <T> List<T> nonNullList(List<? extends T> list) {
return (list != null) ? (List<T>) list : Collections.emptyList();
}
private <T> List<T> merge(List<T> list1, List<T> list2) {
List<T> merged = new ArrayList<>(list1.size() + list2.size());
merged.addAll(list1);
merged.addAll(list2);
return merged;
}
/**
* Return the resolvers managed by this object.
* @return the resolvers
*/
List<ConfigDataLocationResolver<?>> getResolvers() {
return this.resolvers;
}
}

View File

@ -0,0 +1,186 @@
/*
* 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.util.Collections;
import java.util.List;
import java.util.function.Predicate;
import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.boot.context.properties.bind.BindContext;
import org.springframework.boot.context.properties.bind.BindHandler;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.bind.Name;
import org.springframework.boot.context.properties.source.ConfigurationProperty;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.util.ObjectUtils;
/**
* Bound properties used when working with {@link ConfigData}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class ConfigDataProperties {
private static final ConfigurationPropertyName NAME = ConfigurationPropertyName.of("spring.config");
private static final ConfigurationPropertyName LEGACY_PROFILES_NAME = ConfigurationPropertyName
.of("spring.profiles");
private static final Bindable<ConfigDataProperties> BINDABLE_PROPERTIES = Bindable.of(ConfigDataProperties.class);
private static final Bindable<String[]> BINDABLE_STRING_ARRAY = Bindable.of(String[].class);
private final List<String> imports;
private final Activate activate;
/**
* Create a new {@link ConfigDataProperties} instance.
* @param imports the imports requested
* @param activate the activate properties
*/
ConfigDataProperties(@Name("import") List<String> imports, Activate activate) {
this.imports = (imports != null) ? imports : Collections.emptyList();
this.activate = activate;
}
/**
* Return any additional imports requested.
* @return the requested imports
*/
List<String> getImports() {
return this.imports;
}
/**
* Return {@code true} if the properties indicate that the config data property source
* is active for the given activation context.
* @param activationContext the activation context
* @return {@code true} if the config data property source is active
*/
boolean isActive(ConfigDataActivationContext activationContext) {
return this.activate == null || this.activate.isActive(activationContext);
}
/**
* Return a new variant of these properties without any imports.
* @return a new {@link ConfigDataProperties} instance
*/
ConfigDataProperties withoutImports() {
return new ConfigDataProperties(null, this.activate);
}
ConfigDataProperties withLegacyProfiles(String[] legacyProfiles, ConfigurationProperty property) {
if (this.activate != null && !ObjectUtils.isEmpty(this.activate.onProfile)) {
throw new InvalidConfigDataPropertyException(property, NAME.append("activate.on-profile"), null);
}
return new ConfigDataProperties(this.imports, new Activate(this.activate.onCloudPlatform, legacyProfiles));
}
/**
* Factory method used to create {@link ConfigDataProperties} from the given
* {@link Binder}.
* @param binder the binder used to bind the properties
* @return a {@link ConfigDataProperties} instance or {@code null}
*/
static ConfigDataProperties get(Binder binder) {
LegacyProfilesBindHandler legacyProfilesBindHandler = new LegacyProfilesBindHandler();
String[] legacyProfiles = binder.bind(LEGACY_PROFILES_NAME, BINDABLE_STRING_ARRAY, legacyProfilesBindHandler)
.orElse(null);
ConfigDataProperties properties = binder.bind(NAME, BINDABLE_PROPERTIES).orElse(null);
if (!ObjectUtils.isEmpty(legacyProfiles)) {
properties = (properties != null)
? properties.withLegacyProfiles(legacyProfiles, legacyProfilesBindHandler.getProperty())
: new ConfigDataProperties(null, new Activate(null, legacyProfiles));
}
return properties;
}
/**
* {@link BindHandler} used to check for legacy processing properties.
*/
private static class LegacyProfilesBindHandler implements BindHandler {
private ConfigurationProperty property;
@Override
public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target, BindContext context,
Object result) {
this.property = context.getConfigurationProperty();
return result;
}
ConfigurationProperty getProperty() {
return this.property;
}
}
/**
* Activate properties used to determine when a config data property source is active.
*/
static class Activate {
private final CloudPlatform onCloudPlatform;
private final String[] onProfile;
/**
* Create a new {@link Activate} instance.
* @param onCloudPlatform the cloud platform required for activation
* @param onProfile the profile expression required for activation
*/
Activate(CloudPlatform onCloudPlatform, String[] onProfile) {
this.onProfile = onProfile;
this.onCloudPlatform = onCloudPlatform;
}
/**
* Return {@code true} if the properties indicate that the config data property
* source is active for the given activation context.
* @param activationContext the activation context
* @return {@code true} if the config data property source is active
*/
boolean isActive(ConfigDataActivationContext activationContext) {
if (activationContext == null) {
return false;
}
boolean activate = true;
activate = activate && isActive(activationContext.getCloudPlatform());
activate = activate && isActive(activationContext.getProfiles());
return activate;
}
private boolean isActive(CloudPlatform cloudPlatform) {
return this.onCloudPlatform == null || this.onCloudPlatform == cloudPlatform;
}
private boolean isActive(Profiles profiles) {
return ObjectUtils.isEmpty(this.onProfile)
|| (profiles != null && matchesActiveProfiles(profiles::isAccepted));
}
private boolean matchesActiveProfiles(Predicate<String> activeProfiles) {
return org.springframework.core.env.Profiles.of(this.onProfile).matches(activeProfiles);
}
}
}

View File

@ -49,6 +49,7 @@ import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.bind.PropertySourcesPlaceholdersResolver;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.boot.env.DefaultPropertiesPropertySource;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.boot.env.RandomValuePropertySource;
@ -58,7 +59,6 @@ import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.event.SmartApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
@ -109,11 +109,11 @@ import org.springframework.util.StringUtils;
* @author Eddú Meléndez
* @author Madhura Bhave
* @since 1.0.0
* @deprecated since 2.4.0 in favor of {@link ConfigDataEnvironmentPostProcessor}
*/
@Deprecated
public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
private static final String DEFAULT_PROPERTIES = "defaultProperties";
// Note the order is from least to most specific (last one wins)
private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/*/,file:./config/";
@ -164,7 +164,7 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
*/
public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 10;
private final DeferredLog logger = new DeferredLog();
private final Log logger;
private static final Resource[] EMPTY_RESOURCES = {};
@ -176,6 +176,14 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
private int order = DEFAULT_ORDER;
public ConfigFileApplicationListener() {
this(new DeferredLog());
}
ConfigFileApplicationListener(Log logger) {
this.logger = logger;
}
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return ApplicationEnvironmentPreparedEvent.class.isAssignableFrom(eventType)
@ -184,25 +192,8 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent(event);
}
}
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors);
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}
}
List<EnvironmentPostProcessor> loadPostProcessors() {
return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, getClass().getClassLoader());
throw new IllegalStateException(
"ConfigFileApplicationListener is deprected and can only be used as an EnvironmentPostProcessor");
}
@Override
@ -210,11 +201,6 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
addPropertySources(environment, application.getResourceLoader());
}
private void onApplicationPreparedEvent(ApplicationEvent event) {
this.logger.switchTo(ConfigFileApplicationListener.class);
addPostProcessors(((ApplicationPreparedEvent) event).getApplicationContext());
}
/**
* Add config file property sources to the specified environment.
* @param environment the environment to add source to
@ -290,10 +276,7 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
}
private void reorderSources(ConfigurableEnvironment environment) {
PropertySource<?> defaultProperties = environment.getPropertySources().remove(DEFAULT_PROPERTIES);
if (defaultProperties != null) {
environment.getPropertySources().addLast(defaultProperties);
}
DefaultPropertiesPropertySource.moveToEnd(environment);
}
}
@ -332,26 +315,27 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
}
void load() {
FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY,
(defaultProperties) -> {
this.profiles = new LinkedList<>();
this.processedProfiles = new LinkedList<>();
this.activatedProfiles = false;
this.loaded = new LinkedHashMap<>();
initializeProfiles();
while (!this.profiles.isEmpty()) {
Profile profile = this.profiles.poll();
if (isDefaultProfile(profile)) {
addProfileToEnvironment(profile.getName());
}
load(profile, this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
addLoadedPropertySources();
applyActiveProfiles(defaultProperties);
});
FilteredPropertySource.apply(this.environment, DefaultPropertiesPropertySource.NAME, LOAD_FILTERED_PROPERTY,
this::loadWithFilteredProperties);
}
private void loadWithFilteredProperties(PropertySource<?> defaultProperties) {
this.profiles = new LinkedList<>();
this.processedProfiles = new LinkedList<>();
this.activatedProfiles = false;
this.loaded = new LinkedHashMap<>();
initializeProfiles();
while (!this.profiles.isEmpty()) {
Profile profile = this.profiles.poll();
if (isDefaultProfile(profile)) {
addProfileToEnvironment(profile.getName());
}
load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
addLoadedPropertySources();
applyActiveProfiles(defaultProperties);
}
/**
@ -748,8 +732,8 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
private void addLoadedPropertySource(MutablePropertySources destination, String lastAdded,
PropertySource<?> source) {
if (lastAdded == null) {
if (destination.contains(DEFAULT_PROPERTIES)) {
destination.addBefore(DEFAULT_PROPERTIES, source);
if (destination.contains(DefaultPropertiesPropertySource.NAME)) {
destination.addBefore(DefaultPropertiesPropertySource.NAME, source);
}
else {
destination.addLast(source);

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* 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.
@ -28,7 +28,9 @@ import org.springframework.core.env.PropertySource;
* {@link ConfigFileApplicationListener} to filter out properties for specific operations.
*
* @author Phillip Webb
* @deprecated since 2.4.0 along with {@link ConfigFileApplicationListener}
*/
@Deprecated
class FilteredPropertySource extends PropertySource<PropertySource<?>> {
private final Set<String> filteredProperties;

View File

@ -0,0 +1,130 @@
/*
* 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.context.properties.source.ConfigurationProperty;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.origin.Origin;
import org.springframework.core.env.PropertySource;
/**
* Exception thrown when an attempt is made to resolve a property against an inactive
* {@link ConfigData} property source. Used to ensure that a user doesn't accidentally
* attempt to specify a properties that can never be resolved.
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.4.0
*/
public class InactiveConfigDataAccessException extends ConfigDataException {
private final PropertySource<?> propertySource;
private final ConfigDataLocation location;
private final String propertyName;
private final Origin origin;
/**
* Create a new {@link InactiveConfigDataAccessException} instance.
* @param propertySource the inactive property source
* @param location the {@link ConfigDataLocation} of the property source or
* {@code null} if the source was not loaded from {@link ConfigData}.
* @param propertyName the name of the property
* @param origin the origin or the property or {@code null}
*/
InactiveConfigDataAccessException(PropertySource<?> propertySource, ConfigDataLocation location,
String propertyName, Origin origin) {
super(getMessage(propertySource, location, propertyName, origin), null);
this.propertySource = propertySource;
this.location = location;
this.propertyName = propertyName;
this.origin = origin;
}
private static String getMessage(PropertySource<?> propertySource, ConfigDataLocation location, String propertyName,
Origin origin) {
StringBuilder message = new StringBuilder("Inactive property source '");
message.append(propertySource.getName());
if (location != null) {
message.append("' imported from location '");
message.append(location);
}
message.append("' cannot contain property '");
message.append(propertyName);
message.append("'");
if (origin != null) {
message.append(" [origin: ");
message.append(origin);
message.append("]");
}
return message.toString();
}
/**
* Return the inactive property source that contained the property.
* @return the property source
*/
public PropertySource<?> getPropertySource() {
return this.propertySource;
}
/**
* Return the {@link ConfigDataLocation} of the property source or {@code null} if the
* source was not loaded from {@link ConfigData}.
* @return the config data location or {@code null}
*/
public ConfigDataLocation getLocation() {
return this.location;
}
/**
* Return the name of the property.
* @return the property name
*/
public String getPropertyName() {
return this.propertyName;
}
/**
* Return the origin or the property or {@code null}.
* @return the property origin
*/
public Origin getOrigin() {
return this.origin;
}
/**
* Throw a {@link InactiveConfigDataAccessException} if the given
* {@link ConfigDataEnvironmentContributor} contains the property.
* @param contributor the contributor to check
* @param name the name to check
*/
static void throwIfPropertyFound(ConfigDataEnvironmentContributor contributor, ConfigurationPropertyName name) {
ConfigurationPropertySource source = contributor.getConfigurationPropertySource();
ConfigurationProperty property = (source != null) ? source.getConfigurationProperty(name) : null;
if (property != null) {
PropertySource<?> propertySource = contributor.getPropertySource();
ConfigDataLocation location = contributor.getLocation();
throw new InactiveConfigDataAccessException(propertySource, location, name.toString(),
property.getOrigin());
}
}
}

View File

@ -0,0 +1,162 @@
/*
* 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.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/**
* Simple factory used to instantiate objects by injecting available parameters.
*
* @param <T> the type to instantiate
* @author Phillip Webb
*/
class Instantiator<T> {
private static final Comparator<Constructor<?>> CONSTRUCTOR_COMPARATOR = Comparator
.<Constructor<?>>comparingInt(Constructor::getParameterCount).reversed();
private final Class<?> type;
private final Map<Class<?>, Function<Class<?>, Object>> availableParameters;
/**
* Create a new {@link Instantiator} instance for the given type.
* @param type the type to instantiate
* @param availableParameters consumer used to register avaiable parameters
*/
Instantiator(Class<?> type, Consumer<AvailableParameters> availableParameters) {
this.type = type;
this.availableParameters = getAvailableParameters(availableParameters);
}
private Map<Class<?>, Function<Class<?>, Object>> getAvailableParameters(
Consumer<AvailableParameters> availableParameters) {
Map<Class<?>, Function<Class<?>, Object>> result = new LinkedHashMap<>();
availableParameters.accept(new AvailableParameters() {
@Override
public void add(Class<?> type, Object instance) {
result.put(type, (factoryType) -> instance);
}
@Override
public void add(Class<?> type, Function<Class<?>, Object> factory) {
result.put(type, factory);
}
});
return Collections.unmodifiableMap(result);
}
/**
* Instantiate the given set of class name, injecting constructor arguments as
* necessary.
* @param names the class names to instantiate
* @return a list of instantiated instances
*/
List<T> instantiate(Collection<String> names) {
List<T> instances = new ArrayList<>(names.size());
for (String name : names) {
instances.add(instantiate(name));
}
AnnotationAwareOrderComparator.sort(instances);
return Collections.unmodifiableList(instances);
}
private T instantiate(String name) {
try {
Class<?> type = ClassUtils.forName(name, null);
Assert.isAssignable(this.type, type);
return instantiate(type);
}
catch (Throwable ex) {
throw new IllegalArgumentException("Unable to instantiate " + this.type.getName() + " [" + name + "]", ex);
}
}
@SuppressWarnings("unchecked")
private T instantiate(Class<?> type) throws Exception {
Constructor<?>[] constructors = type.getDeclaredConstructors();
Arrays.sort(constructors, CONSTRUCTOR_COMPARATOR);
for (Constructor<?> constructor : constructors) {
Object[] args = getArgs(constructor.getParameterTypes());
if (args != null) {
ReflectionUtils.makeAccessible(constructor);
return (T) constructor.newInstance(args);
}
}
throw new IllegalAccessException("Unable to find suitable constructor");
}
private Object[] getArgs(Class<?>[] parameterTypes) {
Object[] args = new Object[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
Function<Class<?>, Object> parameter = getAvailableParameter(parameterTypes[i]);
if (parameter == null) {
return null;
}
args[i] = parameter.apply(this.type);
}
return args;
}
private Function<Class<?>, Object> getAvailableParameter(Class<?> parameterType) {
for (Map.Entry<Class<?>, Function<Class<?>, Object>> entry : this.availableParameters.entrySet()) {
if (entry.getKey().isAssignableFrom(parameterType)) {
return entry.getValue();
}
}
return null;
}
/**
* Callback used to register available parameters.
*/
interface AvailableParameters {
/**
* Add a parameter with an instance value.
* @param type the parameter type
* @param instance the instance that should be injected
*/
void add(Class<?> type, Object instance);
/**
* Add a parameter with an instance factory.
* @param type the parameter type
* @param factory the factory used to create the instance that should be injected
*/
void add(Class<?> type, Function<Class<?>, Object> factory);
}
}

View File

@ -0,0 +1,134 @@
/*
* 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.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.springframework.boot.context.properties.source.ConfigurationProperty;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
/**
* Exception thrown if an invalid property is found when processing config data.
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.4.0
*/
public class InvalidConfigDataPropertyException extends ConfigDataException {
private static final Map<ConfigurationPropertyName, ConfigurationPropertyName> ERROR = Collections.emptyMap();
private static final Map<ConfigurationPropertyName, ConfigurationPropertyName> WARNING;
static {
Map<ConfigurationPropertyName, ConfigurationPropertyName> warning = new LinkedHashMap<>();
warning.put(ConfigurationPropertyName.of("spring.profiles"),
ConfigurationPropertyName.of("spring.config.activate.on-profile"));
WARNING = Collections.unmodifiableMap(warning);
}
private final ConfigurationProperty property;
private final ConfigurationPropertyName replacement;
private final ConfigDataLocation location;
InvalidConfigDataPropertyException(ConfigurationProperty property, ConfigurationPropertyName replacement,
ConfigDataLocation location) {
super(getMessage(property, replacement, location), null);
this.property = property;
this.replacement = replacement;
this.location = location;
}
/**
* Return source property that caused the exception.
* @return the invalid property
*/
public ConfigurationProperty getProperty() {
return this.property;
}
/**
* Return the {@link ConfigDataLocation} of the invalid property or {@code null} if
* the source was not loaded from {@link ConfigData}.
* @return the config data location or {@code null}
*/
public ConfigDataLocation getLocation() {
return this.location;
}
/**
* Return the replacement property that should be used instead or {@code null} if not
* replacement is available.
* @return the replacement property name
*/
public ConfigurationPropertyName getReplacement() {
return this.replacement;
}
/**
* Throw a {@link InvalidConfigDataPropertyException} if the given
* {@link ConfigDataEnvironmentContributor} contains any invalid property.
* @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) {
logger.warn(getMessage(property, replacement, contributor.getLocation()));
}
});
}
}
private static String getMessage(ConfigurationProperty property, ConfigurationPropertyName replacement,
ConfigDataLocation location) {
StringBuilder message = new StringBuilder("Property '");
message.append(property.getName());
if (location != null) {
message.append("' imported from location '");
message.append(location);
}
message.append("' is invalid");
if (replacement != null) {
message.append(" and should be replaced with '");
message.append(replacement);
message.append("'");
}
if (property.getOrigin() != null) {
message.append(" [origin: ");
message.append(property.getOrigin());
message.append("]");
}
return message.toString();
}
}

View File

@ -0,0 +1,173 @@
/*
* 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.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
* Provides access to environment profiles that have either been set directly on the
* {@link Environment} or will be set based on configuration data property values.
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.4.0
*/
public class Profiles implements Iterable<String> {
private static final Set<String> UNSET_ACTIVE = Collections.emptySet();
private static final Set<String> UNSET_DEFAULT = Collections.singleton("default");
private final List<String> activeProfiles;
private final List<String> defaultProfiles;
private final List<String> acceptedProfiles;
/**
* Create a new {@link Profiles} instance based on the {@link Environment} and
* {@link Binder}.
* @param environment the source environment
* @param binder the binder for profile properties
* @param additionalProfiles and additional active profiles
*/
Profiles(Environment environment, Binder binder, Collection<String> additionalProfiles) {
this.activeProfiles = asUniqueItemList(get(environment, binder, environment::getActiveProfiles,
AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, UNSET_ACTIVE), additionalProfiles);
this.defaultProfiles = asUniqueItemList(get(environment, binder, environment::getDefaultProfiles,
AbstractEnvironment.DEFAULT_PROFILES_PROPERTY_NAME, UNSET_DEFAULT));
this.acceptedProfiles = expandAcceptedProfiles(this.activeProfiles, this.defaultProfiles);
}
private String[] get(Environment environment, Binder binder, Supplier<String[]> supplier, String propertyName,
Set<String> unset) {
String propertyValue = environment.getProperty(propertyName);
if (hasExplicit(supplier, propertyValue, unset)) {
return supplier.get();
}
return binder.bind(propertyName, String[].class).orElse(StringUtils.toStringArray(unset));
}
private boolean hasExplicit(Supplier<String[]> supplier, String propertyValue, Set<String> unset) {
Set<String> profiles = new LinkedHashSet<>(Arrays.asList(supplier.get()));
if (!StringUtils.hasLength(propertyValue)) {
return !unset.equals(profiles);
}
Set<String> propertyProfiles = StringUtils
.commaDelimitedListToSet(StringUtils.trimAllWhitespace(propertyValue));
return !propertyProfiles.equals(profiles);
}
private List<String> expandAcceptedProfiles(List<String> activeProfiles, List<String> defaultProfiles) {
Deque<String> stack = new ArrayDeque<>();
asReversedList((!activeProfiles.isEmpty()) ? activeProfiles : defaultProfiles).forEach(stack::push);
Set<String> acceptedProfiles = new LinkedHashSet<>();
while (!stack.isEmpty()) {
acceptedProfiles.add(stack.pop());
}
return asUniqueItemList(StringUtils.toStringArray(acceptedProfiles));
}
private List<String> asReversedList(List<String> list) {
if (list == null || list.isEmpty()) {
return Collections.emptyList();
}
List<String> reversed = new ArrayList<>(list);
Collections.reverse(reversed);
return Collections.unmodifiableList(reversed);
}
private List<String> asUniqueItemList(String[] array) {
return asUniqueItemList(array, null);
}
private List<String> asUniqueItemList(String[] array, Collection<String> additional) {
LinkedHashSet<String> uniqueItems = new LinkedHashSet<>(Arrays.asList(array));
if (!CollectionUtils.isEmpty(additional)) {
uniqueItems.addAll(additional);
}
return Collections.unmodifiableList(new ArrayList<>(uniqueItems));
}
/**
* Return an iterator for all {@link #getAccepted() accepted profiles}.
*/
@Override
public Iterator<String> iterator() {
return getAccepted().iterator();
}
/**
* Return the active profiles.
* @return the active profiles
*/
public List<String> getActive() {
return this.activeProfiles;
}
/**
* Return the default profiles.
* @return the active profiles
*/
public List<String> getDefault() {
return this.defaultProfiles;
}
/**
* Return the accepted profiles.
* @return the accepted profiles
*/
public List<String> getAccepted() {
return this.acceptedProfiles;
}
/**
* Return if the given profile is active.
* @param profile the profile to test
* @return if the profile is active
*/
public boolean isAccepted(String profile) {
return this.acceptedProfiles.contains(profile);
}
@Override
public String toString() {
ToStringCreator creator = new ToStringCreator(this);
creator.append("active", getActive().toString());
creator.append("default", getDefault().toString());
creator.append("accepted", getAccepted().toString());
return creator.toString();
}
}

View File

@ -0,0 +1,36 @@
/*
* 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 org.springframework.core.io.Resource;
/**
* {@link ConfigDataLoader} for {@link Resource} backed locations.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class ResourceConfigDataLoader implements ConfigDataLoader<ResourceConfigDataLocation> {
@Override
public ConfigData load(ResourceConfigDataLocation location) throws IOException {
return new ConfigData(location.load());
}
}

View File

@ -0,0 +1,95 @@
/*
* 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.List;
import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.FileUrlResource;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
/**
* {@link ConfigDataLocation} backed by a {@link Resource}.
*
* @author Madhura Bhave
* @author Phillip Webb
*/
class ResourceConfigDataLocation extends ConfigDataLocation {
private final String name;
private final Resource resource;
private final PropertySourceLoader propertySourceLoader;
/**
* Create a new {@link ResourceConfigDataLocation} instance.
* @param name the source location
* @param resource the underlying resource
* @param propertySourceLoader the loader that should be used to load the resource
*/
ResourceConfigDataLocation(String name, Resource resource, PropertySourceLoader propertySourceLoader) {
Assert.notNull(name, "Name must not be null");
Assert.notNull(resource, "Resource must not be null");
Assert.notNull(propertySourceLoader, "PropertySourceLoader must not be null");
this.name = name;
this.resource = resource;
this.propertySourceLoader = propertySourceLoader;
}
String getLocation() {
return this.name;
}
List<PropertySource<?>> load() throws IOException {
return this.propertySourceLoader.load(this.name, this.resource);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
ResourceConfigDataLocation other = (ResourceConfigDataLocation) obj;
return this.resource.equals(other.resource);
}
@Override
public int hashCode() {
return this.resource.hashCode();
}
@Override
public String toString() {
if (this.resource instanceof FileSystemResource || this.resource instanceof FileUrlResource) {
try {
return "file [" + this.resource.getFile().toString() + "]";
}
catch (IOException ex) {
}
}
return this.resource.toString();
}
}

View File

@ -0,0 +1,360 @@
/*
* 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.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.core.Ordered;
import org.springframework.core.env.Environment;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.log.LogMessage;
import org.springframework.util.Assert;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
/**
* {@link ConfigDataLocationResolver} for standard locations.
*
* @author Madhura Bhave
* @author Phillip Webb
*/
class ResourceConfigDataLocationResolver implements ConfigDataLocationResolver<ResourceConfigDataLocation>, Ordered {
private static final String PREFIX = "resource:";
static final String CONFIG_NAME_PROPERTY = "spring.config.name";
private static final String[] DEFAULT_CONFIG_NAMES = { "application" };
private static final Resource[] EMPTY_RESOURCES = {};
private static final Comparator<File> FILE_COMPARATOR = Comparator.comparing(File::getAbsolutePath);
private static final Pattern URL_PREFIX = Pattern.compile("^([a-zA-Z][a-zA-Z0-9*]*?:)(.*$)");
private static final String NO_PROFILE = null;
private final Log logger;
private final List<PropertySourceLoader> propertySourceLoaders;
private final String[] configNames;
private final ResourceLoader resourceLoader;
/**
* Create a new {@link ResourceConfigDataLocationResolver} instance.
* @param logger the logger to use
* @param binder a binder backed by the initial {@link Environment}
* @param resourceLoader a {@link ResourceLoader} used to load resources
*/
ResourceConfigDataLocationResolver(Log logger, Binder binder, ResourceLoader resourceLoader) {
this.logger = logger;
this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
getClass().getClassLoader());
this.configNames = getConfigNames(binder);
this.resourceLoader = resourceLoader;
}
private String[] getConfigNames(Binder binder) {
String[] configNames = binder.bind(CONFIG_NAME_PROPERTY, String[].class).orElse(DEFAULT_CONFIG_NAMES);
for (String configName : configNames) {
validateConfigName(configName);
}
return configNames;
}
private void validateConfigName(String name) {
Assert.state(!name.contains("*"), () -> "Config name '" + name + "' cannot contain '*'");
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
@Override
public boolean isResolvable(ConfigDataLocationResolverContext context, String location) {
return true;
}
@Override
public List<ResourceConfigDataLocation> resolve(ConfigDataLocationResolverContext context, String location) {
return resolve(location, getResolvables(context, location));
}
@Override
public List<ResourceConfigDataLocation> resolveProfileSpecific(ConfigDataLocationResolverContext context,
String location, Profiles profiles) {
return resolve(location, getProfileSpecificResolvables(context, location, profiles));
}
private Set<Resolvable> getResolvables(ConfigDataLocationResolverContext context, String location) {
String resourceLocation = getResourceLocation(context, location);
try {
if (isDirectoryLocation(resourceLocation)) {
return getResolvablesForDirectory(resourceLocation, NO_PROFILE);
}
return getResolvablesForFile(resourceLocation, NO_PROFILE);
}
catch (RuntimeException ex) {
throw new IllegalStateException("Unable to load config data from '" + location + "'", ex);
}
}
private Set<Resolvable> getProfileSpecificResolvables(ConfigDataLocationResolverContext context, String location,
Profiles profiles) {
Set<Resolvable> resolvables = new LinkedHashSet<>();
String resourceLocation = getResourceLocation(context, location);
for (String profile : profiles) {
resolvables.addAll(getResolvables(resourceLocation, profile));
}
return resolvables;
}
private String getResourceLocation(ConfigDataLocationResolverContext context, String location) {
String resourceLocation = (location.startsWith(PREFIX)) ? location.substring(PREFIX.length()) : location;
boolean isAbsolute = resourceLocation.startsWith("/") || URL_PREFIX.matcher(resourceLocation).matches();
if (isAbsolute) {
return resourceLocation;
}
ConfigDataLocation parent = context.getParent();
if (parent instanceof ResourceConfigDataLocation) {
String parentLocation = ((ResourceConfigDataLocation) parent).getLocation();
String parentDirectory = parentLocation.substring(0, parentLocation.lastIndexOf("/") + 1);
return parentDirectory + resourceLocation;
}
return resourceLocation;
}
private Set<Resolvable> getResolvables(String resourceLocation, String profile) {
if (isDirectoryLocation(resourceLocation)) {
return getResolvablesForDirectory(resourceLocation, profile);
}
return getResolvablesForFile(resourceLocation, profile);
}
private Set<Resolvable> getResolvablesForDirectory(String resourceLocation, String profile) {
Set<Resolvable> resolvables = new LinkedHashSet<>();
for (String name : this.configNames) {
for (PropertySourceLoader loader : this.propertySourceLoaders) {
for (String extension : loader.getFileExtensions()) {
resolvables.add(new Resolvable(resourceLocation + name, profile, extension, loader));
}
}
}
return resolvables;
}
private Set<Resolvable> getResolvablesForFile(String resourceLocation, String profile) {
for (PropertySourceLoader loader : this.propertySourceLoaders) {
String extension = getLoadableFileExtension(loader, resourceLocation);
if (extension != null) {
String root = resourceLocation.substring(0, resourceLocation.length() - extension.length() - 1);
return Collections.singleton(new Resolvable(root, profile, extension, loader));
}
}
throw new IllegalStateException("File extension is not known to any PropertySourceLoader. "
+ "If the location is meant to reference a directory, it must end in '/'");
}
private String getLoadableFileExtension(PropertySourceLoader loader, String resourceLocation) {
for (String fileExtension : loader.getFileExtensions()) {
if (StringUtils.endsWithIgnoreCase(resourceLocation, fileExtension)) {
return fileExtension;
}
}
return null;
}
private boolean isDirectoryLocation(String resourceLocation) {
return resourceLocation.endsWith("/");
}
private List<ResourceConfigDataLocation> resolve(String location, Set<Resolvable> resolvables) {
List<ResourceConfigDataLocation> resolved = new ArrayList<>();
for (Resolvable resolvable : resolvables) {
resolved.addAll(resolve(location, resolvable));
}
return resolved;
}
private List<ResourceConfigDataLocation> resolve(String location, Resolvable resolvable) {
if (!resolvable.isPatternLocation()) {
return resolveNonPattern(location, resolvable);
}
return resolvePattern(location, resolvable);
}
private List<ResourceConfigDataLocation> resolveNonPattern(String location, Resolvable resolvable) {
Resource resource = loadResource(resolvable.getResourceLocation());
if (resource.exists()) {
ResourceConfigDataLocation resolved = createConfigResourceLocation(location, resolvable, resource);
return Collections.singletonList(resolved);
}
logSkippingResource(resolvable);
return Collections.emptyList();
}
private List<ResourceConfigDataLocation> resolvePattern(String location, Resolvable resolvable) {
validatePatternLocation(resolvable.getResourceLocation());
List<ResourceConfigDataLocation> resolved = new ArrayList<>();
for (Resource resource : getResourcesFromResourceLocationPattern(resolvable.getResourceLocation())) {
if (resource.exists()) {
resolved.add(createConfigResourceLocation(location, resolvable, resource));
}
else {
logSkippingResource(resolvable);
}
}
return resolved;
}
private void logSkippingResource(Resolvable resolvable) {
this.logger.trace(LogMessage.format("Skipping missing resource location %s", resolvable.getResourceLocation()));
}
private ResourceConfigDataLocation createConfigResourceLocation(String location, Resolvable resolvable,
Resource resource) {
String name = String.format("Resource config '%s' imported via location \"%s\"",
resolvable.getResourceLocation(), location);
return new ResourceConfigDataLocation(name, resource, resolvable.getLoader());
}
private void validatePatternLocation(String resourceLocation) {
Assert.state(!resourceLocation.startsWith(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX),
"Classpath wildcard patterns cannot be used as a search location");
Assert.state(StringUtils.countOccurrencesOf(resourceLocation, "*") == 1,
() -> "Search location '" + resourceLocation + "' cannot contain multiple wildcards");
String directoryPath = resourceLocation.substring(0, resourceLocation.lastIndexOf("/") + 1);
Assert.state(directoryPath.endsWith("*/"),
() -> "Search location '" + resourceLocation + "' must end with '*/'");
}
private Resource[] getResourcesFromResourceLocationPattern(String resourceLocationPattern) {
String directoryPath = resourceLocationPattern.substring(0, resourceLocationPattern.indexOf("*/"));
String fileName = resourceLocationPattern.substring(resourceLocationPattern.lastIndexOf("/") + 1);
Resource directoryResource = loadResource(directoryPath);
if (!directoryResource.exists()) {
return EMPTY_RESOURCES;
}
File directory = getDirectory(resourceLocationPattern, directoryResource);
File[] subDirectories = directory.listFiles(File::isDirectory);
if (subDirectories == null) {
return EMPTY_RESOURCES;
}
Arrays.sort(subDirectories, FILE_COMPARATOR);
List<Resource> resources = new ArrayList<>();
FilenameFilter filter = (dir, name) -> name.equals(fileName);
for (File subDirectory : subDirectories) {
File[] files = subDirectory.listFiles(filter);
if (files != null) {
Arrays.stream(files).map(FileSystemResource::new).forEach(resources::add);
}
}
return resources.toArray(EMPTY_RESOURCES);
}
private Resource loadResource(String location) {
location = StringUtils.cleanPath(location);
if (!ResourceUtils.isUrl(location)) {
location = ResourceUtils.FILE_URL_PREFIX + location;
}
return this.resourceLoader.getResource(location);
}
private File getDirectory(String patternLocation, Resource resource) {
try {
File directory = resource.getFile();
Assert.state(directory.isDirectory(), () -> "'" + directory + "' is not a directory");
return directory;
}
catch (Exception ex) {
throw new IllegalStateException(
"Unable to load config data resource from pattern '" + patternLocation + "'", ex);
}
}
/**
* A resource location that could be resolved by this resolver.
*/
private static class Resolvable {
private final String resourceLocation;
private final PropertySourceLoader loader;
Resolvable(String rootLocation, String profile, String extension, PropertySourceLoader loader) {
String profileSuffix = (StringUtils.hasText(profile)) ? "-" + profile : "";
this.resourceLocation = rootLocation + profileSuffix + "." + extension;
this.loader = loader;
}
boolean isPatternLocation() {
return this.resourceLocation.contains("*");
}
String getResourceLocation() {
return this.resourceLocation;
}
PropertySourceLoader getLoader() {
return this.loader;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if ((obj == null) || (getClass() != obj.getClass())) {
return false;
}
Resolvable other = (Resolvable) obj;
return this.resourceLocation.equals(other.resourceLocation);
}
@Override
public int hashCode() {
return this.resourceLocation.hashCode();
}
@Override
public String toString() {
return this.resourceLocation;
}
}
}

View File

@ -0,0 +1,47 @@
/*
* 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;
/**
* Exception throw if a {@link ConfigDataLocation} is not supported.
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.4.0
*/
public class UnsupportedConfigDataLocationException extends ConfigDataException {
private final String location;
/**
* Create a new {@link UnsupportedConfigDataLocationException} instance.
* @param location the unsupported location
*/
UnsupportedConfigDataLocationException(String location) {
super("Unsupported config data location '" + location + "'", null);
this.location = location;
}
/**
* Return the unsupported location.
* @return the unsupported location
*/
public String getLocation() {
return this.location;
}
}

View File

@ -0,0 +1,94 @@
/*
* 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.context.properties.bind.BindContext;
import org.springframework.boot.context.properties.bind.BindException;
import org.springframework.boot.context.properties.bind.BindHandler;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationProperty;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
/**
* Exception thrown if legacy processing must be used.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
final class UseLegacyConfigProcessingException extends ConfigDataException {
/**
* The property name used to trigger legacy processing.
*/
static final ConfigurationPropertyName PROPERTY_NAME = ConfigurationPropertyName
.of("spring.config.use-legacy-processing");
private static final Bindable<Boolean> BOOLEAN = Bindable.of(Boolean.class);
private static final UseLegacyProcessingBindHandler BIND_HANDLER = new UseLegacyProcessingBindHandler();
private final ConfigurationProperty configurationProperty;
UseLegacyConfigProcessingException(ConfigurationProperty configurationProperty) {
super("Legacy processing requested from " + configurationProperty, null);
this.configurationProperty = configurationProperty;
}
/**
* Return the source configuration property that requested the use of legacy
* processing.
* @return the configurationProperty the configuration property
*/
ConfigurationProperty getConfigurationProperty() {
return this.configurationProperty;
}
/**
* Throw a new {@link UseLegacyConfigProcessingException} instance if
* {@link #PROPERTY_NAME} binds to {@code true}.
* @param binder the binder to use
*/
static void throwIfRequested(Binder binder) {
try {
binder.bind(PROPERTY_NAME, BOOLEAN, BIND_HANDLER);
}
catch (BindException ex) {
if (ex.getCause() instanceof UseLegacyConfigProcessingException) {
throw (UseLegacyConfigProcessingException) ex.getCause();
}
throw ex;
}
}
/**
* {@link BindHandler} used to check for legacy processing properties.
*/
private static class UseLegacyProcessingBindHandler implements BindHandler {
@Override
public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target, BindContext context,
Object result) {
if (Boolean.TRUE.equals(result)) {
throw new UseLegacyConfigProcessingException(context.getConfigurationProperty());
}
return result;
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* 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.
@ -18,6 +18,6 @@
* External configuration support allowing 'application.properties' to be loaded and used
* within a Spring Boot application.
*
* @see org.springframework.boot.context.config.ConfigFileApplicationListener
* @see org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor
*/
package org.springframework.boot.context.config;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* 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.
@ -16,7 +16,10 @@
package org.springframework.boot.env;
import org.apache.commons.logging.Log;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
@ -26,12 +29,14 @@ import org.springframework.core.env.Environment;
* <p>
* EnvironmentPostProcessor implementations have to be registered in
* {@code META-INF/spring.factories}, using the fully qualified name of this class as the
* key.
* key. Implementations may implement the {@link org.springframework.core.Ordered Ordered}
* interface or use an {@link org.springframework.core.annotation.Order @Order} annotation
* if they wish to be invoked in specific order.
* <p>
* {@code EnvironmentPostProcessor} processors are encouraged to detect whether Spring's
* {@link org.springframework.core.Ordered Ordered} interface has been implemented or if
* the {@link org.springframework.core.annotation.Order @Order} annotation is present and
* to sort instances accordingly if so prior to invocation.
* Since Spring Boot 2.4, {@code EnvironmentPostProcessor} implementations may optionally
* take a single {@link Log} or {@link DeferredLogFactory} instance as a constructor
* argument. The injected {@link Log} instance will defer output until the application has
* been full prepared to allow the environment itself to configure logging levels.
*
* @author Andy Wilkinson
* @author Stephane Nicoll

View File

@ -0,0 +1,181 @@
/*
* 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.env;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.context.event.ApplicationFailedEvent;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.boot.logging.DeferredLogs;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.SmartApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/**
* {@link SmartApplicationListener} used to trigger {@link EnvironmentPostProcessor
* EnvironmentPostProcessors} registered in the {@code spring.factories} file.
*
* @author Phillip Webb
* @since 2.4.0
*/
public class EnvironmentPostProcessorApplicationListener implements SmartApplicationListener, Ordered {
/**
* The default order for the processor.
*/
public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 10;
private final DeferredLogs deferredLogs = new DeferredLogs();
private int order = DEFAULT_ORDER;
private final List<String> postProcessorClassNames;
/**
* Create a new {@link EnvironmentPostProcessorApplicationListener} with
* {@link EnvironmentPostProcessor} classes loaded via {@code spring.factories}.
*/
public EnvironmentPostProcessorApplicationListener() {
this(SpringFactoriesLoader.loadFactoryNames(EnvironmentPostProcessor.class,
EnvironmentPostProcessorApplicationListener.class.getClassLoader()));
}
/**
* Create a new {@link EnvironmentPostProcessorApplicationListener} with the specified
* {@link EnvironmentPostProcessor} classes.
* @param postProcessorClasses the environment post processor classes
*/
public EnvironmentPostProcessorApplicationListener(Class<?>... postProcessorClasses) {
this(Arrays.stream(postProcessorClasses).map(Class::getName).collect(Collectors.toList()));
}
/**
* Create a new {@link EnvironmentPostProcessorApplicationListener} with the specified
* {@link EnvironmentPostProcessor} class names.
* @param postProcessorClassNames the environment post processor class names
*/
public EnvironmentPostProcessorApplicationListener(String... postProcessorClassNames) {
this(Arrays.asList(postProcessorClassNames));
}
/**
* Create a new {@link EnvironmentPostProcessorApplicationListener} with the specified
* {@link EnvironmentPostProcessor} class names.
* @param postProcessorClassNames the environment post processor class names
*/
public EnvironmentPostProcessorApplicationListener(List<String> postProcessorClassNames) {
this.postProcessorClassNames = postProcessorClassNames;
}
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return ApplicationEnvironmentPreparedEvent.class.isAssignableFrom(eventType)
|| ApplicationPreparedEvent.class.isAssignableFrom(eventType)
|| ApplicationFailedEvent.class.isAssignableFrom(eventType);
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent((ApplicationPreparedEvent) event);
}
}
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
SpringApplication application = event.getSpringApplication();
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors(event.getSpringApplication());
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(environment, application);
}
}
private List<EnvironmentPostProcessor> loadPostProcessors(SpringApplication application) {
return loadPostProcessors(application, this.postProcessorClassNames);
}
private List<EnvironmentPostProcessor> loadPostProcessors(SpringApplication application, List<String> names) {
List<EnvironmentPostProcessor> postProcessors = new ArrayList<>(names.size());
for (String name : names) {
try {
postProcessors.add(instantiatePostProcessor(application, name));
}
catch (Throwable ex) {
throw new IllegalArgumentException("Unable to instantiate factory class [" + name
+ "] for factory type [" + EnvironmentPostProcessor.class.getName() + "]", ex);
}
}
AnnotationAwareOrderComparator.sort(postProcessors);
return postProcessors;
}
private EnvironmentPostProcessor instantiatePostProcessor(SpringApplication application, String name)
throws Exception {
Class<?> type = ClassUtils.forName(name, getClass().getClassLoader());
Assert.isAssignable(EnvironmentPostProcessor.class, type);
Constructor<?>[] constructors = type.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
if (constructor.getParameterCount() == 1) {
Class<?> cls = constructor.getParameterTypes()[0];
if (DeferredLogFactory.class.isAssignableFrom(cls)) {
return newInstance(constructor, this.deferredLogs);
}
if (Log.class.isAssignableFrom(cls)) {
return newInstance(constructor, this.deferredLogs.getLog(type));
}
}
}
return (EnvironmentPostProcessor) ReflectionUtils.accessibleConstructor(type).newInstance();
}
private EnvironmentPostProcessor newInstance(Constructor<?> constructor, Object... initargs) throws Exception {
ReflectionUtils.makeAccessible(constructor);
return (EnvironmentPostProcessor) constructor.newInstance(initargs);
}
private void onApplicationPreparedEvent(ApplicationPreparedEvent event) {
this.deferredLogs.switchOverAll();
}
@Override
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
}

View File

@ -23,8 +23,10 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.log.LogMessage;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
@ -75,9 +77,7 @@ public class RandomValuePropertySource extends PropertySource<Random> {
if (!name.startsWith(PREFIX)) {
return null;
}
if (logger.isTraceEnabled()) {
logger.trace("Generating random property for '" + name + "'");
}
logger.trace(LogMessage.format("Generating random property for '%s'", name));
return getRandomValue(name.substring(PREFIX.length()));
}
@ -138,8 +138,23 @@ public class RandomValuePropertySource extends PropertySource<Random> {
}
public static void addToEnvironment(ConfigurableEnvironment environment) {
environment.getPropertySources().addAfter(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
new RandomValuePropertySource(RANDOM_PROPERTY_SOURCE_NAME));
addToEnvironment(environment, logger);
}
static void addToEnvironment(ConfigurableEnvironment environment, Log logger) {
MutablePropertySources sources = environment.getPropertySources();
PropertySource<?> existing = sources.get(RANDOM_PROPERTY_SOURCE_NAME);
if (existing != null) {
logger.trace("RandomValuePropertySource already present");
return;
}
RandomValuePropertySource randomSource = new RandomValuePropertySource(RANDOM_PROPERTY_SOURCE_NAME);
if (sources.get(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME) != null) {
sources.addAfter(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, randomSource);
}
else {
sources.addLast(randomSource);
}
logger.trace("RandomValuePropertySource add to Environment");
}

View File

@ -0,0 +1,58 @@
/*
* 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.env;
import org.apache.commons.logging.Log;
import org.springframework.boot.SpringApplication;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
/**
* {@link EnvironmentPostProcessor} to add the {@link RandomValuePropertySource}.
*
* @author Phillip Webb
* @since 2.4.0
*/
public class RandomValuePropertySourceEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
/**
* The default order of this post-processor.
*/
public static final int ORDER = Ordered.HIGHEST_PRECEDENCE + 1;
private final Log logger;
/**
* Create a new {@link RandomValuePropertySourceEnvironmentPostProcessor} instance.
* @param logger the logger to use
*/
public RandomValuePropertySourceEnvironmentPostProcessor(Log logger) {
this.logger = logger;
}
@Override
public int getOrder() {
return ORDER;
}
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
RandomValuePropertySource.addToEnvironment(environment, this.logger);
}
}

View File

@ -752,7 +752,11 @@
"name": "spring.profiles",
"type": "java.util.List<java.lang.String>",
"sourceType": "org.springframework.boot.context.config.ConfigFileApplicationListener",
"description": "Comma-separated list of profile expressions that at least one should match for the document to be included."
"description": "Comma-separated list of profile expressions that at least one should match for the document to be included.",
"deprecation": {
"replacement": "spring.config.import.on-profile",
"level": "warning"
}
},
{
"name": "spring.profiles.active",
@ -779,6 +783,24 @@
"description": "Enable trace logs.",
"sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener",
"defaultValue": false
},
{
"name": "spring.config.import",
"type": "java.util.List<java.lang.String>",
"description": "Import additional config data.",
"sourceType": "org.springframework.boot.context.config.ConfigDataProperties"
},
{
"name": "spring.config.activate.on-cloud-platform",
"type": "org.springframework.boot.cloud.CloudPlatform",
"description": "The cloud platform that required for the document to be included",
"sourceType": "org.springframework.boot.context.config.ConfigDataProperties"
},
{
"name": "spring.config.import.on-profile",
"type": "java.lang.String[]",
"description": "Profile expressions that should match for the document to be included",
"sourceType": "org.springframework.boot.context.config.ConfigDataProperties"
}
],
"hints": [
@ -861,6 +883,25 @@
"name": "spring-profile-name"
}
]
},
{
"name": "spring.config.import",
"values": [
{
"value": "file:"
},
{
"value": "classpath:"
},
{
"value": "volumemount:"
}
],
"providers": [
{
"name": "any"
}
]
}
]
}

View File

@ -3,6 +3,14 @@ org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
# ConfigData Location Resolvers
org.springframework.boot.context.config.ConfigDataLocationResolver=\
org.springframework.boot.context.config.ResourceConfigDataLocationResolver
# ConfigData Loaders
org.springframework.boot.context.config.ConfigDataLoader=\
org.springframework.boot.context.config.ResourceConfigDataLoader
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
@ -23,18 +31,19 @@ org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.env.EnvironmentPostProcessorApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor,\
org.springframework.boot.env.RandomValuePropertySourceEnvironmentPostProcessor
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor,\
org.springframework.boot.reactor.DebugAgentEnvironmentPostProcessor

View File

@ -587,8 +587,8 @@ class SpringApplicationTests {
ConfigurableEnvironment environment = new StandardEnvironment();
application.setEnvironment(environment);
this.context = application.run("--spring.profiles.active=bar,spam");
// Command line should always come last
assertThat(environment.getActiveProfiles()).containsExactly("foo", "bar", "spam");
// Since Boot 2.4 additional should always be last
assertThat(environment.getActiveProfiles()).containsExactly("bar", "spam", "foo");
}
@Test

View File

@ -16,6 +16,7 @@
package org.springframework.boot.cloud.cloudfoundry;
import org.apache.commons.logging.LogFactory;
import org.junit.jupiter.api.Test;
import org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor;
@ -33,7 +34,8 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
class CloudFoundryVcapEnvironmentPostProcessorTests {
private final CloudFoundryVcapEnvironmentPostProcessor initializer = new CloudFoundryVcapEnvironmentPostProcessor();
private final CloudFoundryVcapEnvironmentPostProcessor initializer = new CloudFoundryVcapEnvironmentPostProcessor(
LogFactory.getLog(getClass()));
private final ConfigurableApplicationContext context = new AnnotationConfigApplicationContext();

View File

@ -0,0 +1,99 @@
/*
* 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.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.mock.env.MockEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ConfigDataActivationContext}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class ConfigDataActivationContextTests {
@Test
void getCloudPlatformWhenCloudPropertyNotPresentDeducesCloudPlatform() {
Environment environment = new MockEnvironment();
Binder binder = Binder.get(environment);
ConfigDataActivationContext context = new ConfigDataActivationContext(environment, binder);
assertThat(context.getCloudPlatform()).isNull();
}
@Test
void getCloudPlatformWhenClouldPropertyInEnvironmentDeducesCloudPlatform() {
MockEnvironment environment = createKuberntesEnvironment();
Binder binder = Binder.get(environment);
ConfigDataActivationContext context = new ConfigDataActivationContext(environment, binder);
assertThat(context.getCloudPlatform()).isEqualTo(CloudPlatform.KUBERNETES);
}
@Test
void getCloudPlatformWhenCloudPropertyHasBeenContributedDuringInitialLoadDeducesCloudPlatform() {
Environment environment = createKuberntesEnvironment();
Binder binder = new Binder(
new MapConfigurationPropertySource(Collections.singletonMap("spring.main.cloud-platform", "HEROKU")));
ConfigDataActivationContext context = new ConfigDataActivationContext(environment, binder);
assertThat(context.getCloudPlatform()).isEqualTo(CloudPlatform.HEROKU);
}
@Test
void getProfilesWhenWithoutProfilesReturnsNull() {
Environment environment = new MockEnvironment();
Binder binder = Binder.get(environment);
ConfigDataActivationContext context = new ConfigDataActivationContext(environment, binder);
assertThat(context.getProfiles()).isNull();
}
@Test
void getProfilesWhenWithProfilesReturnsProfiles() {
MockEnvironment environment = new MockEnvironment();
environment.setActiveProfiles("a", "b", "c");
Binder binder = Binder.get(environment);
ConfigDataActivationContext context = new ConfigDataActivationContext(environment, binder);
Profiles profiles = new Profiles(environment, binder, null);
context = context.withProfiles(profiles);
assertThat(context.getProfiles()).isEqualTo(profiles);
}
private MockEnvironment createKuberntesEnvironment() {
MockEnvironment environment = new MockEnvironment();
Map<String, Object> map = new LinkedHashMap<>();
map.put("KUBERNETES_SERVICE_HOST", "host");
map.put("KUBERNETES_SERVICE_PORT", "port");
PropertySource<?> propertySource = new MapPropertySource(
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, map);
environment.getPropertySources().addLast(propertySource);
return environment;
}
}

View File

@ -0,0 +1,136 @@
/*
* 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.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.springframework.boot.origin.Origin;
import org.springframework.boot.origin.OriginLookup;
import org.springframework.boot.origin.PropertySourceOrigin;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for {@link ConfigDataEnvironmentContributorPlaceholdersResolver}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class ConfigDataEnvironmentContributorPlaceholdersResolverTests {
@Test
void resolvePlaceholdersWhenNotStringReturnsResolved() {
ConfigDataEnvironmentContributorPlaceholdersResolver resolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(
Collections.emptyList(), null, false);
assertThat(resolver.resolvePlaceholders(123)).isEqualTo(123);
}
@Test
void resolvePlaceholdersWhenNotFoundReturnsOriginal() {
ConfigDataEnvironmentContributorPlaceholdersResolver resolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(
Collections.emptyList(), null, false);
assertThat(resolver.resolvePlaceholders("${test}")).isEqualTo("${test}");
}
@Test
void resolvePlaceholdersWhenFoundReturnsFirstMatch() {
List<ConfigDataEnvironmentContributor> contributors = new ArrayList<>();
contributors.add(new TestConfigDataEnvironmentContributor(new TestPropertySource("s1", "nope", "t1"), true));
contributors.add(new TestConfigDataEnvironmentContributor(new TestPropertySource("s2", "test", "t2"), true));
contributors.add(new TestConfigDataEnvironmentContributor(new TestPropertySource("s3", "test", "t3"), true));
ConfigDataEnvironmentContributorPlaceholdersResolver resolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(
contributors, null, true);
assertThat(resolver.resolvePlaceholders("${test}")).isEqualTo("t2");
}
@Test
void resolvePlaceholdersWhenFoundInInactiveThrowsException() {
List<ConfigDataEnvironmentContributor> contributors = new ArrayList<>();
contributors.add(new TestConfigDataEnvironmentContributor(new TestPropertySource("s1", "nope", "t1"), true));
contributors.add(new TestConfigDataEnvironmentContributor(new TestPropertySource("s2", "test", "t2"), true));
contributors.add(new TestConfigDataEnvironmentContributor(new TestPropertySource("s3", "test", "t3"), false));
ConfigDataEnvironmentContributorPlaceholdersResolver resolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(
contributors, null, true);
assertThatExceptionOfType(InactiveConfigDataAccessException.class)
.isThrownBy(() -> resolver.resolvePlaceholders("${test}"))
.satisfies(propertyNameAndOriginOf("test", "s3"));
}
@Test
void resolvePlaceholderWhenFoundInInactiveAndIgnoringReturnsResolved() {
List<ConfigDataEnvironmentContributor> contributors = new ArrayList<>();
contributors.add(new TestConfigDataEnvironmentContributor(new TestPropertySource("s1", "nope", "t1"), true));
contributors.add(new TestConfigDataEnvironmentContributor(new TestPropertySource("s2", "test", "t2"), true));
contributors.add(new TestConfigDataEnvironmentContributor(new TestPropertySource("s3", "test", "t3"), false));
ConfigDataEnvironmentContributorPlaceholdersResolver resolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(
contributors, null, false);
assertThat(resolver.resolvePlaceholders("${test}")).isEqualTo("t2");
}
private Consumer<InactiveConfigDataAccessException> propertyNameAndOriginOf(String propertyName, String origin) {
return (ex) -> {
assertThat(ex.getPropertyName()).isEqualTo(propertyName);
assertThat(((PropertySourceOrigin) (ex.getOrigin())).getPropertySource().getName()).isEqualTo(origin);
};
}
static class TestPropertySource extends MapPropertySource implements OriginLookup<String> {
TestPropertySource(String name, String key, String value) {
this(name, Collections.singletonMap(key, value));
}
TestPropertySource(String name, Map<String, Object> source) {
super(name, source);
}
@Override
public Origin getOrigin(String key) {
if (getSource().containsKey(key)) {
return new PropertySourceOrigin(this, key);
}
return null;
}
}
static class TestConfigDataEnvironmentContributor extends ConfigDataEnvironmentContributor {
private final boolean active;
protected TestConfigDataEnvironmentContributor(PropertySource<?> propertySource, boolean active) {
super(Kind.ROOT, null, propertySource, null, null, null);
this.active = active;
}
@Override
boolean isActive(ConfigDataActivationContext activationContext) {
return this.active;
}
}
}

View File

@ -0,0 +1,359 @@
/*
* 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.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.ImportPhase;
import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.Kind;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.mock.env.MockPropertySource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link ConfigDataEnvironmentContributor}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class ConfigDataEnvironmentContributorTests {
private ConfigDataActivationContext activationContext = new ConfigDataActivationContext(CloudPlatform.KUBERNETES,
null);
@Test
void getKindReturnsKind() {
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("test");
assertThat(contributor.getKind()).isEqualTo(Kind.INITIAL_IMPORT);
}
@Test
void isActiveWhenPropertiesIsNullReturnsTrue() {
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("test");
assertThat(contributor.isActive(null)).isTrue();
}
@Test
void isActiveWhenPropertiesIsActiveReturnsTrue() {
MockPropertySource propertySource = new MockPropertySource();
propertySource.setProperty("spring.config.activate.on-cloud-platform", "kubernetes");
ConfigData configData = new ConfigData(Collections.singleton(propertySource));
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 0,
this.activationContext);
assertThat(contributor.isActive(this.activationContext)).isTrue();
}
@Test
void isActiveWhenPropertiesIsNotActiveReturnsFalse() {
MockPropertySource propertySource = new MockPropertySource();
propertySource.setProperty("spring.config.activate.on-cloud-platform", "heroku");
ConfigData configData = new ConfigData(Collections.singleton(propertySource));
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 0,
this.activationContext);
assertThat(contributor.isActive(this.activationContext)).isFalse();
}
@Test
void getLocationReturnsLocation() {
ConfigData configData = new ConfigData(Collections.singleton(new MockPropertySource()));
ConfigDataLocation location = mock(ConfigDataLocation.class);
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(location, configData,
0, this.activationContext);
assertThat(contributor.getLocation()).isSameAs(location);
}
@Test
void getPropertySourceReturnsPropertySource() {
MockPropertySource propertySource = new MockPropertySource();
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofExisting(propertySource);
assertThat(contributor.getPropertySource()).isSameAs(propertySource);
}
@Test
void getConfigurationPropertySourceReturnsAdaptedPropertySource() {
MockPropertySource propertySource = new MockPropertySource();
propertySource.setProperty("spring", "boot");
ConfigData configData = new ConfigData(Collections.singleton(propertySource));
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 0,
this.activationContext);
assertThat(contributor.getConfigurationPropertySource()
.getConfigurationProperty(ConfigurationPropertyName.of("spring")).getValue()).isEqualTo("boot");
}
@Test
void getImportsWhenPropertiesIsNullReturnsEmptyList() {
ConfigData configData = new ConfigData(Collections.singleton(new MockPropertySource()));
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 0,
this.activationContext);
assertThat(contributor.getImports()).isEmpty();
}
@Test
void getImportsReturnsImports() {
MockPropertySource propertySource = new MockPropertySource();
propertySource.setProperty("spring.config.import", "spring,boot");
ConfigData configData = new ConfigData(Collections.singleton(propertySource));
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 0,
this.activationContext);
assertThat(contributor.getImports()).containsExactly("spring", "boot");
}
@Test
void hasUnprocessedImportsWhenNoImportsReturnsFalse() {
ConfigData configData = new ConfigData(Collections.singleton(new MockPropertySource()));
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 0,
this.activationContext);
assertThat(contributor.hasUnprocessedImports(ImportPhase.BEFORE_PROFILE_ACTIVATION)).isFalse();
}
@Test
void hasUnprocessedImportsWhenHasNoChildrenForPhaseReturnsTrue() {
MockPropertySource propertySource = new MockPropertySource();
propertySource.setProperty("spring.config.import", "springboot");
ConfigData configData = new ConfigData(Collections.singleton(propertySource));
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 0,
this.activationContext);
assertThat(contributor.hasUnprocessedImports(ImportPhase.BEFORE_PROFILE_ACTIVATION)).isTrue();
}
@Test
void hasUnprocessedImportsWhenHasChildrenForPhaseReturnsFalse() {
MockPropertySource propertySource = new MockPropertySource();
propertySource.setProperty("spring.config.import", "springboot");
ConfigData configData = new ConfigData(Collections.singleton(propertySource));
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 0,
this.activationContext);
ConfigData childConfigData = new ConfigData(Collections.singleton(new MockPropertySource()));
ConfigDataEnvironmentContributor childContributor = ConfigDataEnvironmentContributor.ofImported(null,
childConfigData, 0, this.activationContext);
ConfigDataEnvironmentContributor withChildren = contributor.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION,
Collections.singletonList(childContributor));
assertThat(withChildren.hasUnprocessedImports(ImportPhase.BEFORE_PROFILE_ACTIVATION)).isFalse();
assertThat(withChildren.hasUnprocessedImports(ImportPhase.AFTER_PROFILE_ACTIVATION)).isTrue();
}
@Test
void getChildrenWhenHasNoChildrenReturnsEmptyList() {
ConfigData configData = new ConfigData(Collections.singleton(new MockPropertySource()));
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 0,
this.activationContext);
assertThat(contributor.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION)).isEmpty();
assertThat(contributor.getChildren(ImportPhase.AFTER_PROFILE_ACTIVATION)).isEmpty();
}
@Test
void getChildrenWhenHasChildrenReturnsChildren() {
MockPropertySource propertySource = new MockPropertySource();
propertySource.setProperty("spring.config.import", "springboot");
ConfigData configData = new ConfigData(Collections.singleton(propertySource));
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(null, configData, 0,
this.activationContext);
ConfigData childConfigData = new ConfigData(Collections.singleton(new MockPropertySource()));
ConfigDataEnvironmentContributor childContributor = ConfigDataEnvironmentContributor.ofImported(null,
childConfigData, 0, this.activationContext);
ConfigDataEnvironmentContributor withChildren = contributor.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION,
Collections.singletonList(childContributor));
assertThat(withChildren.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION)).containsExactly(childContributor);
assertThat(withChildren.getChildren(ImportPhase.AFTER_PROFILE_ACTIVATION)).isEmpty();
}
@Test
void streamReturnsStream() {
ConfigDataEnvironmentContributor contributor = createContributor("a");
Stream<String> stream = contributor.stream().map(this::getLocationName);
assertThat(stream).containsExactly("a");
}
@Test
void iteratorWhenSingleContributorReturnsSingletonIterator() {
ConfigDataEnvironmentContributor contributor = createContributor("a");
assertThat(asLocationsList(contributor.iterator())).containsExactly("a");
}
@Test
void iteratorWhenTypicalStructureReturnsCorrectlyOrderedIterator() {
ConfigDataEnvironmentContributor fileApplication = createContributor("file:application.properties");
ConfigDataEnvironmentContributor fileProfile = createContributor("file:application-profile.properties");
ConfigDataEnvironmentContributor fileImports = createContributor("file:./");
fileImports = fileImports.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION,
Collections.singletonList(fileApplication));
fileImports = fileImports.withChildren(ImportPhase.AFTER_PROFILE_ACTIVATION,
Collections.singletonList(fileProfile));
ConfigDataEnvironmentContributor classpathApplication = createContributor("classpath:application.properties");
ConfigDataEnvironmentContributor classpathProfile = createContributor(
"classpath:application-profile.properties");
ConfigDataEnvironmentContributor classpathImports = createContributor("classpath:/");
classpathImports = classpathImports.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION,
Arrays.asList(classpathApplication));
classpathImports = classpathImports.withChildren(ImportPhase.AFTER_PROFILE_ACTIVATION,
Arrays.asList(classpathProfile));
ConfigDataEnvironmentContributor root = createContributor("root");
root = root.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION, Arrays.asList(fileImports, classpathImports));
assertThat(asLocationsList(root.iterator())).containsExactly("file:application-profile.properties",
"file:application.properties", "file:./", "classpath:application-profile.properties",
"classpath:application.properties", "classpath:/", "root");
}
@Test
void withChildrenReturnsNewInstanceWithChildren() {
ConfigDataEnvironmentContributor root = createContributor("root");
ConfigDataEnvironmentContributor child = createContributor("child");
ConfigDataEnvironmentContributor withChildren = root.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION,
Collections.singletonList(child));
assertThat(root.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION)).isEmpty();
assertThat(withChildren.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION)).containsExactly(child);
}
@Test
void withReplacementReplacesChild() {
ConfigDataEnvironmentContributor root = createContributor("root");
ConfigDataEnvironmentContributor child = createContributor("child");
ConfigDataEnvironmentContributor grandchild = createContributor("grandchild");
child = child.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION, Collections.singletonList(grandchild));
root = root.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION, Collections.singletonList(child));
ConfigDataEnvironmentContributor updated = createContributor("updated");
ConfigDataEnvironmentContributor withReplacement = root.withReplacement(grandchild, updated);
assertThat(asLocationsList(root.iterator())).containsExactly("grandchild", "child", "root");
assertThat(asLocationsList(withReplacement.iterator())).containsExactly("updated", "child", "root");
}
@Test
void ofCreatesRootContributor() {
ConfigDataEnvironmentContributor one = createContributor("one");
ConfigDataEnvironmentContributor two = createContributor("two");
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.of(Arrays.asList(one, two));
assertThat(contributor.getKind()).isEqualTo(Kind.ROOT);
assertThat(contributor.getLocation()).isNull();
assertThat(contributor.getImports()).isEmpty();
assertThat(contributor.isActive(this.activationContext)).isTrue();
assertThat(contributor.getPropertySource()).isNull();
assertThat(contributor.getConfigurationPropertySource()).isNull();
assertThat(contributor.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION)).containsExactly(one, two);
}
@Test
void ofInitialImportCreatedInitialImportContributor() {
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("test");
assertThat(contributor.getKind()).isEqualTo(Kind.INITIAL_IMPORT);
assertThat(contributor.getLocation()).isNull();
assertThat(contributor.getImports()).containsExactly("test");
assertThat(contributor.isActive(this.activationContext)).isTrue();
assertThat(contributor.getPropertySource()).isNull();
assertThat(contributor.getConfigurationPropertySource()).isNull();
assertThat(contributor.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION)).isEmpty();
}
@Test
void ofExistingCreatesExistingContributor() {
MockPropertySource propertySource = new MockPropertySource();
propertySource.setProperty("spring.config.import", "test");
propertySource.setProperty("spring.config.activate.on-cloud-platform", "cloudfoundry");
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofExisting(propertySource);
assertThat(contributor.getKind()).isEqualTo(Kind.EXISTING);
assertThat(contributor.getLocation()).isNull();
assertThat(contributor.getImports()).isEmpty(); // Properties must not be bound
assertThat(contributor.isActive(this.activationContext)).isTrue();
assertThat(contributor.getPropertySource()).isEqualTo(propertySource);
assertThat(contributor.getConfigurationPropertySource()).isNotNull();
assertThat(contributor.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION)).isEmpty();
}
@Test
void ofImportedCreatesImportedContributor() {
TestLocation location = new TestLocation("test");
MockPropertySource propertySource = new MockPropertySource();
propertySource.setProperty("spring.config.import", "test");
ConfigData configData = new ConfigData(Collections.singleton(propertySource));
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(location, configData,
0, this.activationContext);
assertThat(contributor.getKind()).isEqualTo(Kind.IMPORTED);
assertThat(contributor.getLocation()).isSameAs(location);
assertThat(contributor.getImports()).containsExactly("test");
assertThat(contributor.isActive(this.activationContext)).isTrue();
assertThat(contributor.getPropertySource()).isEqualTo(propertySource);
assertThat(contributor.getConfigurationPropertySource()).isNotNull();
assertThat(contributor.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION)).isEmpty();
}
@Test
void ofImportedWhenConfigDataHasIgnoreImportsOptionsCreatesImportedContributorWithoutImports() {
TestLocation location = new TestLocation("test");
MockPropertySource propertySource = new MockPropertySource();
propertySource.setProperty("spring.config.import", "test");
ConfigData configData = new ConfigData(Collections.singleton(propertySource), ConfigData.Option.IGNORE_IMPORTS);
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofImported(location, configData,
0, this.activationContext);
assertThat(contributor.getKind()).isEqualTo(Kind.IMPORTED);
assertThat(contributor.getLocation()).isSameAs(location);
assertThat(contributor.getImports()).isEmpty();
assertThat(contributor.isActive(this.activationContext)).isTrue();
assertThat(contributor.getPropertySource()).isEqualTo(propertySource);
assertThat(contributor.getConfigurationPropertySource()).isNotNull();
assertThat(contributor.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION)).isEmpty();
}
@Test
void ofImportedWhenHasUseLegacyPropertyThrowsException() {
MockPropertySource propertySource = new MockPropertySource();
propertySource.setProperty("spring.config.use-legacy-processing", "true");
assertThatExceptionOfType(UseLegacyConfigProcessingException.class)
.isThrownBy(() -> ConfigDataEnvironmentContributor.ofImported(null,
new ConfigData(Collections.singleton(propertySource)), 0, this.activationContext));
}
private ConfigDataEnvironmentContributor createContributor(String location) {
return ConfigDataEnvironmentContributor.ofImported(new TestLocation(location),
new ConfigData(Collections.singleton(new MockPropertySource())), 0, this.activationContext);
}
private List<String> asLocationsList(Iterator<ConfigDataEnvironmentContributor> iterator) {
List<String> list = new ArrayList<>();
iterator.forEachRemaining((contributor) -> list.add(getLocationName(contributor)));
return list;
}
private String getLocationName(ConfigDataEnvironmentContributor contributor) {
return contributor.getLocation().toString();
}
static class TestLocation extends ConfigDataLocation {
private final String location;
TestLocation(String location) {
this.location = location;
}
@Override
public String toString() {
return this.location;
}
}
}

View File

@ -0,0 +1,349 @@
/*
* 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.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.Kind;
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.logging.DeferredLogFactory;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.mock.env.MockPropertySource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link ConfigDataEnvironmentContributors}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class ConfigDataEnvironmentContributorsTests {
private DeferredLogFactory logFactory = Supplier::get;
private MockEnvironment environment;
private Binder binder;
private ConfigDataImporter importer;
private ConfigDataActivationContext activationContext;
@Captor
private ArgumentCaptor<ConfigDataLocationResolverContext> locationResolverContext;
@BeforeEach
void setup() {
MockitoAnnotations.initMocks(this);
this.environment = new MockEnvironment();
this.binder = Binder.get(this.environment);
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.binder, null);
ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory);
this.importer = new ConfigDataImporter(resolvers, loaders);
this.activationContext = new ConfigDataActivationContext(CloudPlatform.KUBERNETES, null);
}
@Test
void createCreatesWithInitialContributors() {
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("test");
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
Arrays.asList(contributor));
Iterator<ConfigDataEnvironmentContributor> iterator = contributors.iterator();
assertThat(iterator.next()).isSameAs(contributor);
assertThat(iterator.next().getKind()).isEqualTo(Kind.ROOT);
}
@Test
void withProcessedImportsWhenHasNoUnprocessedImportsReturnsSameInstance() {
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor
.ofExisting(new MockPropertySource());
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
Arrays.asList(contributor));
ConfigDataEnvironmentContributors withProcessedImports = contributors.withProcessedImports(this.importer,
this.activationContext);
assertThat(withProcessedImports).isSameAs(contributors);
}
@Test
void withProcessedImportsResolvesAndLoads() {
this.importer = mock(ConfigDataImporter.class);
List<String> locations = Arrays.asList("testimport");
MockPropertySource propertySource = new MockPropertySource();
Map<ConfigDataLocation, ConfigData> 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);
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("testimport");
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
Arrays.asList(contributor));
ConfigDataEnvironmentContributors withProcessedImports = contributors.withProcessedImports(this.importer,
this.activationContext);
Iterator<ConfigDataEnvironmentContributor> iterator = withProcessedImports.iterator();
assertThat(iterator.next().getPropertySource()).isSameAs(propertySource);
assertThat(iterator.next().getKind()).isEqualTo(Kind.INITIAL_IMPORT);
assertThat(iterator.next().getKind()).isEqualTo(Kind.ROOT);
assertThat(iterator.hasNext()).isFalse();
}
@Test
void withProcessedImportsResolvesAndLoadsChainedImports() {
this.importer = mock(ConfigDataImporter.class);
List<String> initialLocations = Arrays.asList("initialimport");
MockPropertySource initialPropertySource = new MockPropertySource();
initialPropertySource.setProperty("spring.config.import", "secondimport");
Map<ConfigDataLocation, ConfigData> initialImported = new LinkedHashMap<>();
initialImported.put(new TestConfigDataLocation("a"), new ConfigData(Arrays.asList(initialPropertySource)));
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), eq(initialLocations)))
.willReturn(initialImported);
List<String> secondLocations = Arrays.asList("secondimport");
MockPropertySource secondPropertySource = new MockPropertySource();
Map<ConfigDataLocation, ConfigData> secondImported = new LinkedHashMap<>();
secondImported.put(new TestConfigDataLocation("b"), new ConfigData(Arrays.asList(secondPropertySource)));
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), eq(secondLocations)))
.willReturn(secondImported);
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor
.ofInitialImport("initialimport");
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
Arrays.asList(contributor));
ConfigDataEnvironmentContributors withProcessedImports = contributors.withProcessedImports(this.importer,
this.activationContext);
Iterator<ConfigDataEnvironmentContributor> iterator = withProcessedImports.iterator();
assertThat(iterator.next().getPropertySource()).isSameAs(secondPropertySource);
assertThat(iterator.next().getPropertySource()).isSameAs(initialPropertySource);
assertThat(iterator.next().getKind()).isEqualTo(Kind.INITIAL_IMPORT);
assertThat(iterator.next().getKind()).isEqualTo(Kind.ROOT);
assertThat(iterator.hasNext()).isFalse();
}
@Test
void withProcessedImportsProvidesLocationResolverContextWithAccessToBinder() {
MockPropertySource existingPropertySource = new MockPropertySource();
existingPropertySource.setProperty("test", "springboot");
ConfigDataEnvironmentContributor existingContributor = ConfigDataEnvironmentContributor
.ofExisting(existingPropertySource);
this.importer = mock(ConfigDataImporter.class);
List<String> locations = Arrays.asList("testimport");
MockPropertySource propertySource = new MockPropertySource();
Map<ConfigDataLocation, ConfigData> 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);
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("testimport");
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
Arrays.asList(existingContributor, contributor));
contributors.withProcessedImports(this.importer, this.activationContext);
verify(this.importer).resolveAndLoad(any(), this.locationResolverContext.capture(), any());
ConfigDataLocationResolverContext context = this.locationResolverContext.getValue();
assertThat(context.getBinder().bind("test", String.class).get()).isEqualTo("springboot");
}
@Test
void withProcessedImportsProvidesLocationResolverContextWithAccessToParent() {
this.importer = mock(ConfigDataImporter.class);
List<String> initialLocations = Arrays.asList("initialimport");
MockPropertySource initialPropertySource = new MockPropertySource();
initialPropertySource.setProperty("spring.config.import", "secondimport");
Map<ConfigDataLocation, ConfigData> initialImported = new LinkedHashMap<>();
initialImported.put(new TestConfigDataLocation("a"), new ConfigData(Arrays.asList(initialPropertySource)));
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), eq(initialLocations)))
.willReturn(initialImported);
List<String> secondLocations = Arrays.asList("secondimport");
MockPropertySource secondPropertySource = new MockPropertySource();
Map<ConfigDataLocation, ConfigData> secondImported = new LinkedHashMap<>();
secondImported.put(new TestConfigDataLocation("b"), new ConfigData(Arrays.asList(secondPropertySource)));
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), eq(secondLocations)))
.willReturn(secondImported);
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor
.ofInitialImport("initialimport");
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
Arrays.asList(contributor));
contributors.withProcessedImports(this.importer, this.activationContext);
verify(this.importer).resolveAndLoad(any(), this.locationResolverContext.capture(), eq(secondLocations));
ConfigDataLocationResolverContext context = this.locationResolverContext.getValue();
assertThat(context.getParent()).hasToString("a");
}
@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));
Binder binder = contributors.getBinder(this.activationContext);
assertThat(binder.bind("test", String.class).get()).isEqualTo("springboot");
}
@Test
void getBinderWhenHasMultipleSourcesPicksFirst() {
MockPropertySource firstPropertySource = new MockPropertySource();
firstPropertySource.setProperty("test", "one");
MockPropertySource secondPropertySource = new MockPropertySource();
secondPropertySource.setProperty("test", "two");
ConfigData configData = new ConfigData(Arrays.asList(firstPropertySource, secondPropertySource));
ConfigDataEnvironmentContributor firstContributor = ConfigDataEnvironmentContributor.ofImported(null,
configData, 0, this.activationContext);
ConfigDataEnvironmentContributor secondContributor = ConfigDataEnvironmentContributor.ofImported(null,
configData, 1, this.activationContext);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
Arrays.asList(firstContributor, secondContributor));
Binder binder = contributors.getBinder(this.activationContext);
assertThat(binder.bind("test", String.class).get()).isEqualTo("one");
}
@Test
void getBinderWhenHasInactiveIgnoresInactive() {
MockPropertySource firstPropertySource = new MockPropertySource();
firstPropertySource.setProperty("test", "one");
firstPropertySource.setProperty("spring.config.activate.on-profile", "production");
MockPropertySource secondPropertySource = new MockPropertySource();
secondPropertySource.setProperty("test", "two");
ConfigData configData = new ConfigData(Arrays.asList(firstPropertySource, secondPropertySource));
ConfigDataEnvironmentContributor firstContributor = ConfigDataEnvironmentContributor.ofImported(null,
configData, 0, this.activationContext);
ConfigDataEnvironmentContributor secondContributor = ConfigDataEnvironmentContributor.ofImported(null,
configData, 1, this.activationContext);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
Arrays.asList(firstContributor, secondContributor));
Binder binder = contributors.getBinder(this.activationContext);
assertThat(binder.bind("test", String.class).get()).isEqualTo("two");
}
@Test
void getBinderWhenHasPlaceholderResolvesPlaceholder() {
MockPropertySource propertySource = new MockPropertySource();
propertySource.setProperty("test", "${other}");
propertySource.setProperty("other", "springboot");
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofExisting(propertySource);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
Arrays.asList(contributor));
Binder binder = contributors.getBinder(this.activationContext);
assertThat(binder.bind("test", String.class).get()).isEqualTo("springboot");
}
@Test
void getBinderWhenHasPlaceholderAndInactiveResolvesPlaceholderOnlyFromActive() {
MockPropertySource firstPropertySource = new MockPropertySource();
firstPropertySource.setProperty("other", "one");
firstPropertySource.setProperty("spring.config.activate.on-profile", "production");
MockPropertySource secondPropertySource = new MockPropertySource();
secondPropertySource.setProperty("other", "two");
secondPropertySource.setProperty("test", "${other}");
ConfigData configData = new ConfigData(Arrays.asList(firstPropertySource, secondPropertySource));
ConfigDataEnvironmentContributor firstContributor = ConfigDataEnvironmentContributor.ofImported(null,
configData, 0, this.activationContext);
ConfigDataEnvironmentContributor secondContributor = ConfigDataEnvironmentContributor.ofImported(null,
configData, 1, this.activationContext);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
Arrays.asList(firstContributor, secondContributor));
Binder binder = contributors.getBinder(this.activationContext);
assertThat(binder.bind("test", String.class).get()).isEqualTo("two");
}
@Test
void getBinderWhenFailOnBindToInactiveSourceWithFirstInactiveThrowsException() {
MockPropertySource firstPropertySource = new MockPropertySource();
firstPropertySource.setProperty("test", "one");
firstPropertySource.setProperty("spring.config.activate.on-profile", "production");
MockPropertySource secondPropertySource = new MockPropertySource();
secondPropertySource.setProperty("test", "two");
ConfigData configData = new ConfigData(Arrays.asList(firstPropertySource, secondPropertySource));
ConfigDataEnvironmentContributor firstContributor = ConfigDataEnvironmentContributor.ofImported(null,
configData, 0, this.activationContext);
ConfigDataEnvironmentContributor secondContributor = ConfigDataEnvironmentContributor.ofImported(null,
configData, 1, this.activationContext);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
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));
}
@Test
void getBinderWhenFailOnBindToInactiveSourceWithLastInactiveThrowsException() {
MockPropertySource firstPropertySource = new MockPropertySource();
firstPropertySource.setProperty("test", "one");
MockPropertySource secondPropertySource = new MockPropertySource();
secondPropertySource.setProperty("spring.config.activate.on-profile", "production");
secondPropertySource.setProperty("test", "two");
ConfigData configData = new ConfigData(Arrays.asList(firstPropertySource, secondPropertySource));
ConfigDataEnvironmentContributor firstContributor = ConfigDataEnvironmentContributor.ofImported(null,
configData, 0, this.activationContext);
ConfigDataEnvironmentContributor secondContributor = ConfigDataEnvironmentContributor.ofImported(null,
configData, 1, this.activationContext);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
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));
}
@Test
void getBinderWhenFailOnBindToInactiveSourceWithResolveToInactiveThrowsException() {
MockPropertySource firstPropertySource = new MockPropertySource();
firstPropertySource.setProperty("other", "one");
firstPropertySource.setProperty("spring.config.activate.on-profile", "production");
MockPropertySource secondPropertySource = new MockPropertySource();
secondPropertySource.setProperty("test", "${other}");
secondPropertySource.setProperty("other", "one");
ConfigData configData = new ConfigData(Arrays.asList(firstPropertySource, secondPropertySource));
ConfigDataEnvironmentContributor firstContributor = ConfigDataEnvironmentContributor.ofImported(null,
configData, 0, this.activationContext);
ConfigDataEnvironmentContributor secondContributor = ConfigDataEnvironmentContributor.ofImported(null,
configData, 1, this.activationContext);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
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));
}
private static class TestConfigDataLocation extends ConfigDataLocation {
private final String value;
TestConfigDataLocation(String value) {
this.value = value;
}
@Override
public String toString() {
return this.value;
}
}
}

View File

@ -0,0 +1,540 @@
/*
* 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.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.assertj.core.api.Condition;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.Profiles;
import org.springframework.core.env.SimpleCommandLinePropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Integration tests for {@link ConfigDataEnvironmentPostProcessor}.
*
* @author Madhura Bhave
* @author Phillip Webb
*/
class ConfigDataEnvironmentPostProcessorIntegrationTests {
private SpringApplication application;
@BeforeEach
void setup() {
this.application = new SpringApplication(Config.class);
this.application.setWebApplicationType(WebApplicationType.NONE);
}
@AfterEach
void clearProperties() {
System.clearProperty("the.property");
}
@Test
void runWhenUsingCustomResourceLoader() {
this.application.setResourceLoader(new ResourceLoader() {
@Override
public Resource getResource(String location) {
if (location.equals("classpath:/custom.properties")) {
return new ByteArrayResource("the.property: fromcustom".getBytes(), location);
}
return new ClassPathResource("doesnotexist");
}
@Override
public ClassLoader getClassLoader() {
return getClass().getClassLoader();
}
});
ConfigurableApplicationContext context = this.application.run("--spring.config.name=custom");
String property = context.getEnvironment().getProperty("the.property");
assertThat(property).isEqualTo("fromcustom");
}
@Test
void runLoadsApplicationPropertiesOnClasspath() {
ConfigurableApplicationContext context = this.application.run();
String property = context.getEnvironment().getProperty("foo");
assertThat(property).isEqualTo("bucket");
}
@Test
void runLoadsApplicationYamlOnClasspath() {
ConfigurableApplicationContext context = this.application.run("--spring.config.name=customapplication");
String property = context.getEnvironment().getProperty("yamlkey");
assertThat(property).isEqualTo("yamlvalue");
}
@Test
void runLoadsFileWithCustomName() {
ConfigurableApplicationContext context = this.application.run("--spring.config.name=testproperties");
String property = context.getEnvironment().getProperty("the.property");
assertThat(property).isEqualTo("frompropertiesfile");
}
@Test
void runWhenMultipleCustomNamesLoadsEachName() {
ConfigurableApplicationContext context = this.application
.run("--spring.config.name=moreproperties,testproperties");
String property = context.getEnvironment().getProperty("the.property");
assertThat(property).isEqualTo("frompropertiesfile");
}
@Test
void runWhenNoActiveProfilesLoadsDefaultProfileFile() {
ConfigurableApplicationContext context = this.application.run("--spring.config.name=testprofiles");
String property = context.getEnvironment().getProperty("my.property");
assertThat(property).isEqualTo("fromdefaultpropertiesfile");
}
@Test
void runWhenActiveProfilesDoesNotLoadDefault() {
ConfigurableApplicationContext context = this.application.run("--spring.config.name=testprofilesdocument",
"--spring.profiles.default=thedefault", "--spring.profiles.active=other");
String property = context.getEnvironment().getProperty("my.property");
assertThat(property).isEqualTo("fromotherprofile");
}
@Test
void runWhenHasCustomDefaultProfileLoadsDefaultProfileFile() {
ConfigurableApplicationContext context = this.application.run("--spring.config.name=testprofiles",
"--spring.profiles.default=thedefault");
String property = context.getEnvironment().getProperty("the.property");
assertThat(property).isEqualTo("fromdefaultpropertiesfile");
}
@Test
void runWhenHasCustomSpringConfigLocationLoadsAllFromSpecifiedLocation() {
ConfigurableApplicationContext context = this.application
.run("--spring.config.location=classpath:application.properties,classpath:testproperties.properties");
String property1 = context.getEnvironment().getProperty("the.property");
String property2 = context.getEnvironment().getProperty("my.property");
String property3 = context.getEnvironment().getProperty("foo");
assertThat(property1).isEqualTo("frompropertiesfile");
assertThat(property2).isEqualTo("frompropertiesfile");
assertThat(property3).isEqualTo("bucket");
}
@Test
void runWhenOneCustomLocationDoesNotExistLoadsOthers() {
ConfigurableApplicationContext context = this.application.run(
"--spring.config.location=classpath:application.properties,classpath:testproperties.properties,classpath:nonexistent.properties");
String property = context.getEnvironment().getProperty("the.property");
assertThat(property).isEqualTo("frompropertiesfile");
}
@Test
void runWhenHasActiveProfilesFromMultipleLocationsActivatesProfileFromOneLocation() {
ConfigurableApplicationContext context = this.application
.run("--spring.config.location=classpath:enableprofile.properties,classpath:enableother.properties");
ConfigurableEnvironment environment = context.getEnvironment();
assertThat(environment.getActiveProfiles()).containsExactly("other");
String property = environment.getProperty("other.property");
assertThat(property).isEqualTo("fromotherpropertiesfile");
}
@Test
void runWhenHasActiveProfilesFromMultipleAdditionaLocationsWithOneSwitchedOffLoadsExpectedProperties() {
ConfigurableApplicationContext context = this.application.run(
"--spring.config.additional-location=classpath:enabletwoprofiles.properties,classpath:enableprofile.properties");
ConfigurableEnvironment environment = context.getEnvironment();
assertThat(environment.getActiveProfiles()).containsExactly("myprofile");
String property = environment.getProperty("my.property");
assertThat(property).isEqualTo("fromprofilepropertiesfile");
}
@Test
void runWhenHaslocalFileLoadsWithLocalFileTakingPrecedenceOverClasspath() throws Exception {
File localFile = new File(new File("."), "application.properties");
assertThat(localFile.exists()).isFalse();
try {
Properties properties = new Properties();
properties.put("my.property", "fromlocalfile");
try (OutputStream outputStream = new FileOutputStream(localFile)) {
properties.store(outputStream, "");
}
ConfigurableApplicationContext context = this.application.run();
String property = context.getEnvironment().getProperty("my.property");
assertThat(property).isEqualTo("fromlocalfile");
}
finally {
localFile.delete();
}
}
@Test
void runWhenHasCommandLinePropertiesLoadsWithCommandLineTakingPrecedence() {
StandardEnvironment environment = new StandardEnvironment();
environment.getPropertySources()
.addFirst(new SimpleCommandLinePropertySource("--the.property=fromcommandline"));
this.application.setEnvironment(environment);
ConfigurableApplicationContext context = this.application.run("--spring.config.name=testproperties");
String property = context.getEnvironment().getProperty("the.property");
assertThat(property).isEqualTo("fromcommandline");
}
@Test
void runWhenHasSystemPropertyLoadsWithSystemPropertyTakingPrecendence() {
System.setProperty("the.property", "fromsystem");
ConfigurableApplicationContext context = this.application.run("--spring.config.name=testproperties");
String property = context.getEnvironment().getProperty("the.property");
assertThat(property).isEqualTo("fromsystem");
}
@Test
void runWhenHasDefaultPropertiesIncluesDefaultPropertiesLast() {
this.application.setDefaultProperties(Collections.singletonMap("my.fallback", "foo"));
ConfigurableApplicationContext context = this.application.run();
String property = context.getEnvironment().getProperty("my.fallback");
assertThat(property).isEqualTo("foo");
}
@Test
void runWhenHasDefaultPropertiesWithConfigLocationConfigurationLoadsExpectedProperties() {
this.application.setDefaultProperties(Collections.singletonMap("spring.config.name", "testproperties"));
ConfigurableApplicationContext context = this.application.run();
String property = context.getEnvironment().getProperty("the.property");
assertThat(property).isEqualTo("frompropertiesfile");
}
@Test
void runWhenHasActiveProfilesFromDefaultPropertiesAndFileLoadsWithFileTakingPrecedence() {
this.application.setDefaultProperties(Collections.singletonMap("spring.profiles.active", "dev"));
ConfigurableApplicationContext context = this.application.run("--spring.config.name=enableprofile");
assertThat(context.getEnvironment().getActiveProfiles()).containsExactly("myprofile");
}
@Test
void runWhenProgrammticallySetProfilesLoadsWithSetProfilesTakePrecedenceOverDefaultProfile() {
this.application.setAdditionalProfiles("other");
ConfigurableApplicationContext context = this.application.run();
String property = context.getEnvironment().getProperty("my.property");
assertThat(property).isEqualTo("fromotherpropertiesfile");
}
@Test
void runWhenTwoProfilesSetProgrammaticallyLoadsWithPreservedProfileOrder() {
this.application.setAdditionalProfiles("other", "dev");
ConfigurableApplicationContext context = this.application.run();
String property = context.getEnvironment().getProperty("my.property");
assertThat(property).isEqualTo("fromdevpropertiesfile");
}
@Test
void runWhenProfilesPresentBeforeConfigFileProcessingAugmentsProfileActivatedByConfigFile() {
this.application.setAdditionalProfiles("other");
ConfigurableApplicationContext context = this.application.run("--spring.config.name=enableprofile");
assertThat(context.getEnvironment().getActiveProfiles()).containsExactly("myprofile", "other");
String property = context.getEnvironment().getProperty("other.property");
assertThat(property).isEqualTo("fromotherpropertiesfile");
property = context.getEnvironment().getProperty("the.property");
assertThat(property).isEqualTo("fromprofilepropertiesfile");
}
@Test
void runWhenProfilePropertiesUsedInPlaceholdersLoadsWithResolvedPlaceholders() {
ConfigurableApplicationContext context = this.application.run("--spring.config.name=enableprofile");
String property = context.getEnvironment().getProperty("one.more");
assertThat(property).isEqualTo("fromprofilepropertiesfile");
}
@Test
void runWhenDuplicateProfileSetProgrammaticallyAndViaPropertyLoadsWithProfiles() {
this.application.setAdditionalProfiles("dev");
ConfigurableApplicationContext context = this.application.run("--spring.profiles.active=dev,other");
assertThat(context.getEnvironment().getActiveProfiles()).contains("dev", "other");
assertThat(context.getEnvironment().getProperty("my.property")).isEqualTo("fromotherpropertiesfile");
}
@Test
void runWhenProfilesActivatedViaBracketNotationSetsProfiles() {
ConfigurableApplicationContext context = this.application.run("--spring.profiles.active[0]=dev",
"--spring.profiles.active[1]=other");
assertThat(context.getEnvironment().getActiveProfiles()).contains("dev", "other");
assertThat(context.getEnvironment().getProperty("my.property")).isEqualTo("fromotherpropertiesfile");
}
@Test
void loadWhenProfileInMultiDocumentFilesLoadsExpectedProperties() {
this.application.setAdditionalProfiles("dev");
ConfigurableApplicationContext context = this.application.run("--spring.config.name=testprofiles",
"--spring.config.location=classpath:configdata/profiles/");
String property = context.getEnvironment().getProperty("my.property");
assertThat(property).isEqualTo("fromdevprofile");
property = context.getEnvironment().getProperty("my.other");
assertThat(property).isEqualTo("notempty");
}
@Test
void runWhenMultipleActiveProfilesWithMultiDocumentFilesLoadsInOrderOfDocument() {
this.application.setAdditionalProfiles("other", "dev");
ConfigurableApplicationContext context = this.application.run("--spring.config.name=testprofiles",
"--spring.config.location=classpath:configdata/profiles/");
String property = context.getEnvironment().getProperty("my.property");
assertThat(property).isEqualTo("fromotherprofile");
property = context.getEnvironment().getProperty("my.other");
assertThat(property).isEqualTo("notempty");
property = context.getEnvironment().getProperty("dev.property");
assertThat(property).isEqualTo("devproperty");
}
@Test
void runWhenHasAndProfileExpressionLoadsExpectedProperties() {
assertProfileExpression("devandother", "dev", "other");
}
@Test
void runWhenHasComplexProfileExpressionsLoadsExpectedProperties() {
assertProfileExpression("devorotherandanother", "dev", "another");
}
@Test
void runWhenProfileExpressionsDoNotMatchLoadsExpectedProperties() {
assertProfileExpression("fromyamlfile", "dev");
}
@Test
void runWhenHasNegatedProfilesLoadsExpectedProperties() {
ConfigurableApplicationContext context = this.application.run("--spring.config.name=testnegatedprofiles",
"--spring.config.location=classpath:configdata/profiles/");
String property = context.getEnvironment().getProperty("my.property");
assertThat(property).isEqualTo("fromnototherprofile");
property = context.getEnvironment().getProperty("my.notother");
assertThat(property).isEqualTo("foo");
}
@Test
void runWhenHasNegatedProfilesWithProfileActiveLoadsExpectedProperties() {
this.application.setAdditionalProfiles("other");
ConfigurableApplicationContext context = this.application.run("--spring.config.name=testnegatedprofiles",
"--spring.config.location=classpath:configdata/profiles/");
String property = context.getEnvironment().getProperty("my.property");
assertThat(property).isEqualTo("fromotherprofile");
property = context.getEnvironment().getProperty("my.notother");
assertThat(property).isNull();
}
@Test
void runWhenHasActiveProfileConfigurationInMultiDocumentFileLoadsInExpectedOrder() {
ConfigurableApplicationContext context = this.application.run("--spring.config.name=testsetprofiles",
"--spring.config.location=classpath:configdata/profiles/");
assertThat(context.getEnvironment().getActiveProfiles()).containsExactly("dev");
String property = context.getEnvironment().getProperty("my.property");
assertThat(context.getEnvironment().getActiveProfiles()).contains("dev");
assertThat(property).isEqualTo("fromdevprofile");
List<String> names = StreamSupport.stream(context.getEnvironment().getPropertySources().spliterator(), false)
.map(org.springframework.core.env.PropertySource::getName).collect(Collectors.toList());
assertThat(names).contains(
"Resource config 'classpath:configdata/profiles/testsetprofiles.yml' imported via location \"classpath:configdata/profiles/\" (document #0)",
"Resource config 'classpath:configdata/profiles/testsetprofiles.yml' imported via location \"classpath:configdata/profiles/\" (document #1)");
}
@Test
void runWhenHasYamlWithCommaSeparatedMultipleProfilesLoadsExpectedProperties() {
ConfigurableApplicationContext context = this.application.run("--spring.config.name=testsetmultiprofiles");
assertThat(context.getEnvironment().getActiveProfiles()).containsExactly("dev", "healthcheck");
}
@Test
void runWhenHasYamlWithListProfilesLoadsExpectedProperties() {
ConfigurableApplicationContext context = this.application.run("--spring.config.name=testsetmultiprofileslist");
assertThat(context.getEnvironment().getActiveProfiles()).containsExactly("dev", "healthcheck");
}
@Test
void loadWhenHasWhitespaceTrims() {
ConfigurableApplicationContext context = this.application
.run("--spring.config.name=testsetmultiprofileswhitespace");
assertThat(context.getEnvironment().getActiveProfiles()).containsExactly("dev", "healthcheck");
}
@Test
void loadWhenHasConfigLocationAsFile() {
String location = "file:src/test/resources/specificlocation.properties";
ConfigurableApplicationContext context = this.application.run("--spring.config.location=" + location);
assertThat(context.getEnvironment()).has(matchingPropertySource(
"Resource config 'file:src/test/resources/specificlocation.properties' imported via location \""
+ location + "\""));
}
@Test
void loadWhenHasRelativeConfigLocationUsesFileLocation() {
String location = "src/test/resources/specificlocation.properties";
ConfigurableApplicationContext context = this.application.run("--spring.config.location=" + location);
assertThat(context.getEnvironment()).has(matchingPropertySource(
"Resource config 'src/test/resources/specificlocation.properties' imported via location \"" + location
+ "\""));
}
@Test
void loadWhenCustomDefaultProfileAndActiveFromPreviousSourceDoesNotActivateDefault() {
ConfigurableApplicationContext context = this.application.run("--spring.profiles.default=customdefault",
"--spring.profiles.active=dev");
String property = context.getEnvironment().getProperty("my.property");
assertThat(property).isEqualTo("fromdevpropertiesfile");
assertThat(context.getEnvironment().containsProperty("customdefault")).isFalse();
}
@Test
void runWhenCustomDefaultProfileSameAsActiveFromFileActivatesProfile() {
ConfigurableApplicationContext context = this.application.run("--spring.profiles.default=customdefault",
"--spring.config.name=customprofile");
ConfigurableEnvironment environment = context.getEnvironment();
assertThat(environment.containsProperty("customprofile")).isTrue();
assertThat(environment.containsProperty("customprofile-customdefault")).isTrue();
assertThat(environment.acceptsProfiles(Profiles.of("customdefault"))).isTrue();
}
@Test
void runWhenActiveProfilesCanBeConfiguredUsingPlaceholdersResolvedAgainstTheEnvironmentLoadsExpectedProperties() {
ConfigurableApplicationContext context = this.application.run("--activeProfile=testPropertySource",
"--spring.config.name=testactiveprofiles");
assertThat(context.getEnvironment().getActiveProfiles()).containsExactly("testPropertySource");
}
@Test
void runWhenHasAdditionalLocationLoadsWithAdditionalTakingPrecedenceOverDefaultLocation() {
ConfigurableApplicationContext context = this.application
.run("--spring.config.additional-location=classpath:override.properties");
assertThat(context.getEnvironment().getProperty("foo")).isEqualTo("bar");
assertThat(context.getEnvironment().getProperty("value")).isEqualTo("1234");
}
@Test
void runWhenMultipleAdditionalLocationsLoadsWithLastWinning() {
ConfigurableApplicationContext context = this.application
.run("--spring.config.additional-location=classpath:override.properties,classpath:some.properties");
assertThat(context.getEnvironment().getProperty("foo")).isEqualTo("spam");
assertThat(context.getEnvironment().getProperty("value")).isEqualTo("1234");
}
@Test
void runWhenAdditionalLocationAndLocationLoadsWithAdditionalTakingPrecedenceOverConfigured() {
ConfigurableApplicationContext context = this.application.run(
"--spring.config.location=classpath:some.properties",
"--spring.config.additional-location=classpath:override.properties");
assertThat(context.getEnvironment().getProperty("foo")).isEqualTo("bar");
assertThat(context.getEnvironment().getProperty("value")).isNull();
}
@Test
void runWhenPropertiesFromCustomPropertySourceLoaderShouldLoadFromCustomSource() {
ConfigurableApplicationContext context = this.application.run();
assertThat(context.getEnvironment().getProperty("customloader1")).isEqualTo("true");
}
@Test
void runWhenCustomDefaultPropertySourceLoadsWithoutReplacingCustomSource() {
// gh-17011
Map<String, Object> source = new HashMap<>();
source.put("mapkey", "mapvalue");
MapPropertySource propertySource = new MapPropertySource("defaultProperties", source) {
@Override
public Object getProperty(String name) {
if ("spring.config.name".equals(name)) {
return "gh17001";
}
return super.getProperty(name);
}
};
StandardEnvironment environment = new StandardEnvironment();
environment.getPropertySources().addFirst(propertySource);
this.application.setEnvironment(environment);
ConfigurableApplicationContext context = this.application.run();
assertThat(context.getEnvironment().getProperty("mapkey")).isEqualTo("mapvalue");
assertThat(context.getEnvironment().getProperty("gh17001loaded")).isEqualTo("true");
}
@Test
void runWhenConfigLocationHasUnknownFileExtensionFailsFast() {
String location = "classpath:application.unknown";
assertThatIllegalStateException().isThrownBy(() -> this.application.run("--spring.config.location=" + location))
.withMessageContaining("Unable to load config data").withMessageContaining(location)
.satisfies((ex) -> assertThat(ex.getCause()).hasMessageContaining("File extension is not known")
.hasMessageContaining("it must end in '/'"));
}
@Test
void runWhenConfigLocationHasUnknownDirectoryContinuesToLoad() {
String location = "classpath:application.unknown/";
this.application.run("--spring.config.location=" + location);
}
@Test
@Disabled("Disabled until spring.profiles suppport is dropped")
void runWhenUsingInvalidPropertyThrowsException() {
assertThatExceptionOfType(InvalidConfigDataPropertyException.class).isThrownBy(
() -> this.application.run("--spring.config.location=classpath:invalidproperty.properties"));
}
private Condition<ConfigurableEnvironment> matchingPropertySource(final String sourceName) {
return new Condition<ConfigurableEnvironment>("environment containing property source " + sourceName) {
@Override
public boolean matches(ConfigurableEnvironment value) {
return value.getPropertySources().contains(sourceName);
}
};
}
private void assertProfileExpression(String value, String... activeProfiles) {
this.application.setAdditionalProfiles(activeProfiles);
ConfigurableApplicationContext context = this.application.run("--spring.config.name=testprofileexpression",
"--spring.config.location=classpath:configdata/profiles/");
String property = context.getEnvironment().getProperty("my.property");
assertThat(property).isEqualTo(value);
}
@Configuration(proxyBeanMethods = false)
static class Config {
}
}

View File

@ -0,0 +1,121 @@
/*
* 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.util.Set;
import java.util.function.Supplier;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.springframework.boot.SpringApplication;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.willReturn;
import static org.mockito.BDDMockito.willThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
/**
* Tests for {@link ConfigDataEnvironmentPostProcessor}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class ConfigDataEnvironmentPostProcessorTests {
private ConfigDataEnvironment configDataEnvironment = mock(ConfigDataEnvironment.class);
private StandardEnvironment environment;
private SpringApplication application;
@Spy
private ConfigDataEnvironmentPostProcessor postProcessor = new ConfigDataEnvironmentPostProcessor(Supplier::get);
@Captor
private ArgumentCaptor<Set<String>> additionalProfilesCaptor;
@Captor
private ArgumentCaptor<ResourceLoader> resourceLoaderCaptor;
@BeforeEach
void setup() {
MockitoAnnotations.initMocks(this);
this.application = new SpringApplication();
this.environment = new StandardEnvironment();
}
@Test
@SuppressWarnings("deprecation")
void defaultOrderMatchesDeprecatedListener() {
assertThat(ConfigDataEnvironmentPostProcessor.ORDER).isEqualTo(ConfigFileApplicationListener.DEFAULT_ORDER);
}
@Test
void postProcessEnvironmentWhenNoLoaderCreatesDefaultLoaderInstance() {
willReturn(this.configDataEnvironment).given(this.postProcessor).getConfigDataEnvironment(any(), any(), any());
this.postProcessor.postProcessEnvironment(this.environment, this.application);
verify(this.postProcessor).getConfigDataEnvironment(any(), this.resourceLoaderCaptor.capture(), any());
verify(this.configDataEnvironment).processAndApply();
assertThat(this.resourceLoaderCaptor.getValue()).isInstanceOf(DefaultResourceLoader.class);
}
@Test
void postProcessEnvironmentWhenCustomLoaderUsesSpecifiedLoaderInstance() {
ResourceLoader resourceLoader = mock(ResourceLoader.class);
this.application.setResourceLoader(resourceLoader);
willReturn(this.configDataEnvironment).given(this.postProcessor).getConfigDataEnvironment(any(), any(), any());
this.postProcessor.postProcessEnvironment(this.environment, this.application);
verify(this.postProcessor).getConfigDataEnvironment(any(), this.resourceLoaderCaptor.capture(), any());
verify(this.configDataEnvironment).processAndApply();
assertThat(this.resourceLoaderCaptor.getValue()).isSameAs(resourceLoader);
}
@Test
void postProcessEnvironmentWhenHasAdditionalProfilesOnSpringApplicationUsesAdditionalProfiles() {
this.application.setAdditionalProfiles("dev");
willReturn(this.configDataEnvironment).given(this.postProcessor).getConfigDataEnvironment(any(), any(), any());
this.postProcessor.postProcessEnvironment(this.environment, this.application);
verify(this.postProcessor).getConfigDataEnvironment(any(), any(), this.additionalProfilesCaptor.capture());
verify(this.configDataEnvironment).processAndApply();
assertThat(this.additionalProfilesCaptor.getValue()).containsExactly("dev");
}
@Test
void postProcessEnvironmentWhenUseLegacyProcessingSwitchesToLegacyMethod() {
ConfigDataEnvironmentPostProcessor.LegacyConfigFileApplicationListener legacyListener = mock(
ConfigDataEnvironmentPostProcessor.LegacyConfigFileApplicationListener.class);
willThrow(new UseLegacyConfigProcessingException(null)).given(this.postProcessor)
.getConfigDataEnvironment(any(), any(), any());
willReturn(legacyListener).given(this.postProcessor).getLegacyListener();
this.postProcessor.postProcessEnvironment(this.environment, this.application);
verifyNoInteractions(this.configDataEnvironment);
verify(legacyListener).addPropertySources(eq(this.environment), any(DefaultResourceLoader.class));
}
}

View File

@ -0,0 +1,214 @@
/*
* 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.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
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.logging.DeferredLogFactory;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.mock.env.MockPropertySource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for {@link ConfigDataEnvironment}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class ConfigDataEnvironmentTests {
private DeferredLogFactory logFactory = Supplier::get;
private MockEnvironment environment = new MockEnvironment();
private ResourceLoader resourceLoader = new DefaultResourceLoader();
private Collection<String> additionalProfiles = Collections.emptyList();
@Test
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));
}
@Test
void createExposesEnvironmentBinderToConfigDataLocationResolvers() {
this.environment.setProperty("spring", "boot");
TestConfigDataEnvironment configDataEnvironment = new TestConfigDataEnvironment(this.logFactory,
this.environment, this.resourceLoader, this.additionalProfiles);
assertThat(configDataEnvironment.getConfigDataLocationResolversBinder().bind("spring", String.class).get())
.isEqualTo("boot");
}
@Test
void createCreatesContributorsBasedOnExistingSources() {
MockPropertySource propertySource1 = new MockPropertySource("p1");
MockPropertySource propertySource2 = new MockPropertySource("p2");
MockPropertySource propertySource3 = new MockPropertySource("p3");
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);
List<ConfigDataEnvironmentContributor> children = configDataEnvironment.getContributors().getRoot()
.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION);
Object[] wrapped = children.stream().filter((child) -> child.getKind() == Kind.EXISTING)
.map(ConfigDataEnvironmentContributor::getPropertySource).toArray();
assertThat(wrapped[1]).isEqualTo(propertySource1);
assertThat(wrapped[2]).isEqualTo(propertySource2);
assertThat(wrapped[3]).isEqualTo(propertySource3);
}
@Test
void createWhenHasDefaultPropertySourceMovesItToLastContributor() {
MockPropertySource defaultPropertySource = new MockPropertySource("defaultProperties");
MockPropertySource propertySource1 = new MockPropertySource("p2");
MockPropertySource propertySource2 = new MockPropertySource("p3");
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);
List<ConfigDataEnvironmentContributor> children = configDataEnvironment.getContributors().getRoot()
.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION);
Object[] wrapped = children.stream().filter((child) -> child.getKind() == Kind.EXISTING)
.map(ConfigDataEnvironmentContributor::getPropertySource).toArray();
assertThat(wrapped[1]).isEqualTo(propertySource1);
assertThat(wrapped[2]).isEqualTo(propertySource2);
assertThat(wrapped[3]).isEqualTo(defaultPropertySource);
}
@Test
void createCreatesInitialImportContributorsInCorrectOrder() {
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);
List<ConfigDataEnvironmentContributor> children = configDataEnvironment.getContributors().getRoot()
.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION);
Object[] imports = children.stream().filter((child) -> child.getKind() == Kind.INITIAL_IMPORT)
.map(ConfigDataEnvironmentContributor::getImports).map(Object::toString).toArray();
assertThat(imports).containsExactly("[i2]", "[i1]", "[a2]", "[a1]", "[l2]", "[l1]");
}
@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.processAndApply();
assertThat(this.environment.getProperty("spring")).isEqualTo("boot");
}
@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.processAndApply();
assertThat(this.environment.getProperty("spring")).isEqualTo("boot");
assertThat(this.environment.getProperty("other")).isNull();
}
@Test
void processAndApplyMovesDefaultProperySourceToLast(TestInfo info) {
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.processAndApply();
List<PropertySource<?>> sources = this.environment.getPropertySources().stream().collect(Collectors.toList());
assertThat(sources.get(sources.size() - 1)).isSameAs(defaultPropertySource);
}
@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.processAndApply();
assertThat(this.environment.getDefaultProfiles()).containsExactly("one", "two", "three");
}
@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.processAndApply();
assertThat(this.environment.getActiveProfiles()).containsExactly("one", "two", "three");
}
@Test
@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);
assertThatExceptionOfType(InvalidConfigDataPropertyException.class)
.isThrownBy(() -> configDataEnvironment.processAndApply());
}
private String getConfigLocation(TestInfo info) {
return "classpath:" + info.getTestClass().get().getName().replace('.', '/') + "-"
+ info.getTestMethod().get().getName() + ".properties";
}
static class TestConfigDataEnvironment extends ConfigDataEnvironment {
private Binder configDataLocationResolversBinder;
TestConfigDataEnvironment(DeferredLogFactory logFactory, ConfigurableEnvironment environment,
ResourceLoader resourceLoader, Collection<String> additionalProfiles) {
super(logFactory, environment, resourceLoader, additionalProfiles);
}
@Override
protected ConfigDataLocationResolvers createConfigDataLocationResolvers(DeferredLogFactory logFactory,
Binder binder, ResourceLoader resourceLoader) {
this.configDataLocationResolversBinder = binder;
return super.createConfigDataLocationResolvers(logFactory, binder, resourceLoader);
}
Binder getConfigDataLocationResolversBinder() {
return this.configDataLocationResolversBinder;
}
}
}

View File

@ -0,0 +1,117 @@
/*
* 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.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.mock.env.MockPropertySource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
/**
* Tests for {@link ConfigDataImporter}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class ConfigDataImporterTests {
@Mock
private ConfigDataLocationResolvers resolvers;
@Mock
private ConfigDataLoaders loaders;
@Mock
private Binder binder;
@Mock
private ConfigDataLocationResolverContext locationResolverContext;
@Mock
private ConfigDataActivationContext activationContext;
@Mock
private Profiles profiles;
@BeforeEach
void setup() {
MockitoAnnotations.initMocks(this);
given(this.activationContext.getProfiles()).willReturn(this.profiles);
}
@Test
void loadImportsResolvesAndLoadsLocations() throws Exception {
List<String> locations = Arrays.asList("test1", "test2");
TestLocation resolvedLocation1 = new TestLocation();
TestLocation resolvedLocation2 = new TestLocation();
List<ConfigDataLocation> resolvedLocations = Arrays.asList(resolvedLocation1, resolvedLocation2);
ConfigData configData1 = new ConfigData(Collections.singleton(new MockPropertySource()));
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);
ConfigDataImporter importer = new ConfigDataImporter(this.resolvers, this.loaders);
Collection<ConfigData> loaded = importer
.resolveAndLoad(this.activationContext, this.locationResolverContext, locations).values();
assertThat(loaded).containsExactly(configData2, configData1);
}
@Test
void loadImportsWhenAlreadyImportedLocationSkipsLoad() throws Exception {
List<String> locations1and2 = Arrays.asList("test1", "test2");
List<String> locations2and3 = Arrays.asList("test2", "test3");
TestLocation resolvedLocation1 = new TestLocation();
TestLocation resolvedLocation2 = new TestLocation();
TestLocation resolvedLocation3 = new TestLocation();
List<ConfigDataLocation> resolvedLocations1and2 = Arrays.asList(resolvedLocation1, resolvedLocation2);
List<ConfigDataLocation> resolvedLocations2and3 = Arrays.asList(resolvedLocation2, resolvedLocation3);
ConfigData configData1 = new ConfigData(Collections.singleton(new MockPropertySource()));
ConfigData configData2 = new ConfigData(Collections.singleton(new MockPropertySource()));
ConfigData configData3 = new ConfigData(Collections.singleton(new MockPropertySource()));
given(this.resolvers.resolveAll(this.locationResolverContext, locations1and2, this.profiles))
.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);
ConfigDataImporter importer = new ConfigDataImporter(this.resolvers, this.loaders);
Collection<ConfigData> loaded1and2 = importer
.resolveAndLoad(this.activationContext, this.locationResolverContext, locations1and2).values();
Collection<ConfigData> loaded2and3 = importer
.resolveAndLoad(this.activationContext, this.locationResolverContext, locations2and3).values();
assertThat(loaded1and2).containsExactly(configData2, configData1);
assertThat(loaded2and3).containsExactly(configData3);
}
static class TestLocation extends ConfigDataLocation {
}
}

View File

@ -0,0 +1,53 @@
/*
* 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 org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ConfigDataLoader}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class ConfigDataLoaderTests {
private TestConfigDataLoader loader = new TestConfigDataLoader();
@Test
void isLoadableAlwaysReturnsTrue() {
assertThat(this.loader.isLoadable(new TestConfigDataLocation())).isTrue();
}
static class TestConfigDataLoader implements ConfigDataLoader<TestConfigDataLocation> {
@Override
public ConfigData load(TestConfigDataLocation location) throws IOException {
return null;
}
}
static class TestConfigDataLocation extends ConfigDataLocation {
}
}

View File

@ -0,0 +1,166 @@
/*
* 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.Arrays;
import java.util.List;
import java.util.function.Supplier;
import org.apache.commons.logging.Log;
import org.junit.jupiter.api.Test;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.env.PropertySource;
import org.springframework.mock.env.MockPropertySource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link ConfigDataLoaders}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class ConfigDataLoadersTests {
private DeferredLogFactory logFactory = Supplier::get;
@Test
void createWhenLoaderHasLogParameterInjectsLog() {
new ConfigDataLoaders(this.logFactory, Arrays.asList(LoggingConfigDataLoader.class.getName()));
}
@Test
void loadWhenSingleLoaderSupportsLocationReturnsLoadedConfigData() throws Exception {
TestConfigDataLocation location = new TestConfigDataLocation("test");
ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory,
Arrays.asList(TestConfigDataLoader.class.getName()));
ConfigData loaded = loaders.load(location);
assertThat(getLoader(loaded)).isInstanceOf(TestConfigDataLoader.class);
}
@Test
void loadWhenMultipleLoadersSupportLocationThrowsException() throws Exception {
TestConfigDataLocation location = new TestConfigDataLocation("test");
ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory,
Arrays.asList(LoggingConfigDataLoader.class.getName(), TestConfigDataLoader.class.getName()));
assertThatIllegalStateException().isThrownBy(() -> loaders.load(location))
.withMessageContaining("Multiple loaders found for location test");
}
@Test
void loadWhenNoLoaderSupportsLocationThrowsException() {
TestConfigDataLocation location = new TestConfigDataLocation("test");
ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory,
Arrays.asList(NonLoadableConfigDataLoader.class.getName()));
assertThatIllegalStateException().isThrownBy(() -> loaders.load(location))
.withMessage("No loader found for location 'test'");
}
@Test
void loadWhenGenericTypeDoesNotMatchSkipsLoader() throws Exception {
TestConfigDataLocation location = new TestConfigDataLocation("test");
ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory,
Arrays.asList(OtherConfigDataLoader.class.getName(), SpecificConfigDataLoader.class.getName()));
ConfigData loaded = loaders.load(location);
assertThat(getLoader(loaded)).isInstanceOf(SpecificConfigDataLoader.class);
}
private ConfigDataLoader<?> getLoader(ConfigData loaded) {
return (ConfigDataLoader<?>) loaded.getPropertySources().get(0).getProperty("loader");
}
private static ConfigData createConfigData(ConfigDataLoader<?> loader, ConfigDataLocation location) {
MockPropertySource propertySource = new MockPropertySource();
propertySource.setProperty("loader", loader);
propertySource.setProperty("location", location);
List<PropertySource<?>> propertySources = Arrays.asList(propertySource);
return new ConfigData(propertySources);
}
static class TestConfigDataLocation extends ConfigDataLocation {
private final String value;
TestConfigDataLocation(String value) {
this.value = value;
}
@Override
public String toString() {
return this.value;
}
}
static class OtherConfigDataLocation extends ConfigDataLocation {
}
static class LoggingConfigDataLoader implements ConfigDataLoader<ConfigDataLocation> {
LoggingConfigDataLoader(Log log) {
assertThat(log).isNotNull();
}
@Override
public ConfigData load(ConfigDataLocation location) throws IOException {
throw new AssertionError("Unexpected call");
}
}
static class TestConfigDataLoader implements ConfigDataLoader<ConfigDataLocation> {
@Override
public ConfigData load(ConfigDataLocation location) throws IOException {
return createConfigData(this, location);
}
}
static class NonLoadableConfigDataLoader extends TestConfigDataLoader {
@Override
public boolean isLoadable(ConfigDataLocation location) {
return false;
}
}
static class SpecificConfigDataLoader implements ConfigDataLoader<TestConfigDataLocation> {
@Override
public ConfigData load(TestConfigDataLocation location) throws IOException {
return createConfigData(this, location);
}
}
static class OtherConfigDataLoader implements ConfigDataLoader<OtherConfigDataLocation> {
@Override
public ConfigData load(OtherConfigDataLocation location) throws IOException {
return createConfigData(this, location);
}
}
}

View File

@ -0,0 +1,57 @@
/*
* 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.util.List;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link ConfigDataLocationResolver}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class ConfigDataLocationResolverTests {
ConfigDataLocationResolver<?> resolver = new TestConfigDataLocationResolver();
ConfigDataLocationResolverContext context = mock(ConfigDataLocationResolverContext.class);
@Test
void resolveProfileSpecificReturnsEmptyList() {
assertThat(this.resolver.resolveProfileSpecific(this.context, null, null)).isEmpty();
}
static class TestConfigDataLocationResolver implements ConfigDataLocationResolver<ConfigDataLocation> {
@Override
public boolean isResolvable(ConfigDataLocationResolverContext context, String location) {
return true;
}
@Override
public List<ConfigDataLocation> resolve(ConfigDataLocationResolverContext context, String location) {
return null;
}
}
}

View File

@ -0,0 +1,222 @@
/*
* 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.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link ConfigDataLocationResolvers}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class ConfigDataLocationResolversTests {
private DeferredLogFactory logFactory = Supplier::get;
@Mock
private Binder binder;
@Mock
private ConfigDataLocationResolverContext context;
@Mock
private Profiles profiles;
private ResourceLoader resourceLoader = new DefaultResourceLoader();
@BeforeEach
void setup() {
MockitoAnnotations.initMocks(this);
}
@Test
void createWhenInjectingBinderCreatesResolver() {
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.binder,
this.resourceLoader, Collections.singletonList(TestBoundResolver.class.getName()));
assertThat(resolvers.getResolvers()).hasSize(1);
assertThat(resolvers.getResolvers().get(0)).isExactlyInstanceOf(TestBoundResolver.class);
assertThat(((TestBoundResolver) resolvers.getResolvers().get(0)).getBinder()).isSameAs(this.binder);
}
@Test
void createWhenNotInjectingBinderCreatesResolver() {
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.binder,
this.resourceLoader, Collections.singletonList(TestResolver.class.getName()));
assertThat(resolvers.getResolvers()).hasSize(1);
assertThat(resolvers.getResolvers().get(0)).isExactlyInstanceOf(TestResolver.class);
}
@Test
void createWhenNameIsNotConfigDataLocationResolverThrowsException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new ConfigDataLocationResolvers(this.logFactory, this.binder, this.resourceLoader,
Collections.singletonList(InputStream.class.getName())))
.withMessageContaining("Unable to instantiate").havingCause().withMessageContaining("not assignable");
}
@Test
void createOrdersResolvers() {
List<String> names = new ArrayList<>();
names.add(TestResolver.class.getName());
names.add(LowestTestResolver.class.getName());
names.add(HighestTestResolver.class.getName());
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.binder,
this.resourceLoader, names);
assertThat(resolvers.getResolvers().get(0)).isExactlyInstanceOf(HighestTestResolver.class);
assertThat(resolvers.getResolvers().get(1)).isExactlyInstanceOf(TestResolver.class);
assertThat(resolvers.getResolvers().get(2)).isExactlyInstanceOf(LowestTestResolver.class);
}
@Test
void resolveAllResolvesUsingFirstSupportedResolver() {
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.binder,
this.resourceLoader,
Arrays.asList(LowestTestResolver.class.getName(), HighestTestResolver.class.getName()));
List<ConfigDataLocation> resolved = resolvers.resolveAll(this.context,
Collections.singletonList("LowestTestResolver:test"), null);
assertThat(resolved).hasSize(1);
TestConfigDataLocation location = (TestConfigDataLocation) resolved.get(0);
assertThat(location.getResolver()).isInstanceOf(LowestTestResolver.class);
assertThat(location.getLocation()).isEqualTo("LowestTestResolver:test");
assertThat(location.isProfileSpecific()).isFalse();
}
@Test
void resolveAllWhenProfileMergesResolvedLocations() {
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.binder,
this.resourceLoader,
Arrays.asList(LowestTestResolver.class.getName(), HighestTestResolver.class.getName()));
List<ConfigDataLocation> resolved = resolvers.resolveAll(this.context,
Collections.singletonList("LowestTestResolver:test"), this.profiles);
assertThat(resolved).hasSize(2);
TestConfigDataLocation location = (TestConfigDataLocation) resolved.get(0);
assertThat(location.getResolver()).isInstanceOf(LowestTestResolver.class);
assertThat(location.getLocation()).isEqualTo("LowestTestResolver:test");
assertThat(location.isProfileSpecific()).isFalse();
TestConfigDataLocation profileLocation = (TestConfigDataLocation) resolved.get(1);
assertThat(profileLocation.getResolver()).isInstanceOf(LowestTestResolver.class);
assertThat(profileLocation.getLocation()).isEqualTo("LowestTestResolver:test");
assertThat(profileLocation.isProfileSpecific()).isTrue();
}
@Test
void resolveWhenNoResolverThrowsException() {
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.binder,
this.resourceLoader,
Arrays.asList(LowestTestResolver.class.getName(), HighestTestResolver.class.getName()));
assertThatExceptionOfType(UnsupportedConfigDataLocationException.class)
.isThrownBy(() -> resolvers.resolveAll(this.context, Collections.singletonList("Missing:test"), null))
.satisfies((ex) -> assertThat(ex.getLocation()).isEqualTo("Missing:test"));
}
static class TestResolver implements ConfigDataLocationResolver<TestConfigDataLocation> {
@Override
public boolean isResolvable(ConfigDataLocationResolverContext context, String location) {
String name = getClass().getName();
name = name.substring(name.lastIndexOf("$") + 1);
return location.startsWith(name + ":");
}
@Override
public List<TestConfigDataLocation> resolve(ConfigDataLocationResolverContext context, String location) {
return Collections.singletonList(new TestConfigDataLocation(this, location, false));
}
@Override
public List<TestConfigDataLocation> resolveProfileSpecific(ConfigDataLocationResolverContext context,
String location, Profiles profiles) {
return Collections.singletonList(new TestConfigDataLocation(this, location, true));
}
}
static class TestBoundResolver extends TestResolver {
private final Binder binder;
TestBoundResolver(Binder binder) {
this.binder = binder;
}
Binder getBinder() {
return this.binder;
}
}
@Order(Ordered.HIGHEST_PRECEDENCE)
static class HighestTestResolver extends TestResolver {
}
@Order(Ordered.LOWEST_PRECEDENCE)
static class LowestTestResolver extends TestResolver {
}
static class TestConfigDataLocation extends ConfigDataLocation {
private final TestResolver resolver;
private final String location;
private final boolean profileSpecific;
TestConfigDataLocation(TestResolver resolver, String location, boolean profileSpecific) {
this.resolver = resolver;
this.location = location;
this.profileSpecific = profileSpecific;
}
TestResolver getResolver() {
return this.resolver;
}
String getLocation() {
return this.location;
}
boolean isProfileSpecific() {
return this.profileSpecific;
}
}
}

View File

@ -0,0 +1,200 @@
/*
* 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.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.boot.context.config.ConfigDataProperties.Activate;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.mock.env.MockEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for {@link ConfigDataProperties}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class ConfigDataPropertiesTests {
private static final CloudPlatform NULL_CLOUD_PLATFORM = null;
private static final Profiles NULL_PROFILES = null;
private static final List<String> NO_IMPORTS = Collections.emptyList();
@Test
void getImportsReturnsImports() {
List<String> imports = Arrays.asList("one", "two", "three");
ConfigDataProperties properties = new ConfigDataProperties(imports, null);
assertThat(properties.getImports()).containsExactly("one", "two", "three");
}
@Test
void getImportsWhenImportsAreNullReturnsEmptyList() {
ConfigDataProperties properties = new ConfigDataProperties(null, null);
assertThat(properties.getImports()).isEmpty();
}
@Test
void isActiveWhenNullCloudPlatformAgainstNullCloudPlatform() {
ConfigDataProperties properties = new ConfigDataProperties(NO_IMPORTS, new Activate(null, null));
ConfigDataActivationContext context = new ConfigDataActivationContext(NULL_CLOUD_PLATFORM, NULL_PROFILES);
assertThat(properties.isActive(context)).isTrue();
}
@Test
void isActiveWhenNullCloudPlatformAgainstSpecificCloudPlatform() {
ConfigDataProperties properties = new ConfigDataProperties(NO_IMPORTS, new Activate(null, null));
ConfigDataActivationContext context = new ConfigDataActivationContext(CloudPlatform.KUBERNETES, NULL_PROFILES);
assertThat(properties.isActive(context)).isTrue();
}
@Test
void isActiveWhenSpecificCloudPlatformAgainstNullCloudPlatform() {
ConfigDataProperties properties = new ConfigDataProperties(NO_IMPORTS,
new Activate(CloudPlatform.KUBERNETES, null));
ConfigDataActivationContext context = new ConfigDataActivationContext(NULL_CLOUD_PLATFORM, NULL_PROFILES);
assertThat(properties.isActive(context)).isFalse();
}
@Test
void isActiveWhenSpecificCloudPlatformAgainstMatchingSpecificCloudPlatform() {
ConfigDataProperties properties = new ConfigDataProperties(NO_IMPORTS,
new Activate(CloudPlatform.KUBERNETES, null));
ConfigDataActivationContext context = new ConfigDataActivationContext(CloudPlatform.KUBERNETES, NULL_PROFILES);
assertThat(properties.isActive(context)).isTrue();
}
@Test
void isActiveWhenSpecificCloudPlatformAgainstDifferentSpecificCloudPlatform() {
ConfigDataProperties properties = new ConfigDataProperties(NO_IMPORTS,
new Activate(CloudPlatform.KUBERNETES, null));
ConfigDataActivationContext context = new ConfigDataActivationContext(CloudPlatform.HEROKU, NULL_PROFILES);
assertThat(properties.isActive(context)).isFalse();
}
@Test
void isActiveWhenNullProfilesAgainstNullProfiles() {
ConfigDataProperties properties = new ConfigDataProperties(NO_IMPORTS, new Activate(null, null));
ConfigDataActivationContext context = new ConfigDataActivationContext(NULL_CLOUD_PLATFORM, NULL_PROFILES);
assertThat(properties.isActive(context)).isTrue();
}
@Test
void isActiveWhenNullProfilesAgainstSpecificProfiles() {
ConfigDataProperties properties = new ConfigDataProperties(NO_IMPORTS, new Activate(null, null));
ConfigDataActivationContext context = new ConfigDataActivationContext(NULL_CLOUD_PLATFORM,
createTestProfiles());
assertThat(properties.isActive(context)).isTrue();
}
@Test
void isActiveWhenSpecificProfilesAgainstNullProfiles() {
ConfigDataProperties properties = new ConfigDataProperties(NO_IMPORTS,
new Activate(null, new String[] { "a" }));
ConfigDataActivationContext context = new ConfigDataActivationContext(NULL_CLOUD_PLATFORM, null);
assertThat(properties.isActive(context)).isFalse();
}
@Test
void isActiveWhenSpecificProfilesAgainstMatchingSpecificProfiles() {
ConfigDataProperties properties = new ConfigDataProperties(NO_IMPORTS,
new Activate(null, new String[] { "a" }));
ConfigDataActivationContext context = new ConfigDataActivationContext(NULL_CLOUD_PLATFORM,
createTestProfiles());
assertThat(properties.isActive(context)).isTrue();
}
@Test
void isActiveWhenSpecificProfilesAgainstMissingSpecificProfiles() {
ConfigDataProperties properties = new ConfigDataProperties(NO_IMPORTS,
new Activate(null, new String[] { "x" }));
ConfigDataActivationContext context = new ConfigDataActivationContext(NULL_CLOUD_PLATFORM,
createTestProfiles());
assertThat(properties.isActive(context)).isFalse();
}
@Test
void isActiveWhenProfileExpressionAgainstSpecificProfiles() {
ConfigDataProperties properties = new ConfigDataProperties(NO_IMPORTS,
new Activate(null, new String[] { "a | b" }));
ConfigDataActivationContext context = new ConfigDataActivationContext(NULL_CLOUD_PLATFORM,
createTestProfiles());
assertThat(properties.isActive(context)).isTrue();
}
@Test
void isActiveWhenActivateIsNull() {
ConfigDataProperties properties = new ConfigDataProperties(NO_IMPORTS, null);
ConfigDataActivationContext context = new ConfigDataActivationContext(NULL_CLOUD_PLATFORM,
createTestProfiles());
assertThat(properties.isActive(context)).isTrue();
}
@Test
void isActiveAgainstBoundData() {
MapConfigurationPropertySource source = new MapConfigurationPropertySource();
source.put("spring.config.import", "one,two,three");
source.put("spring.config.activate.on-cloud-platform", "kubernetes");
source.put("spring.config.activate.on-profiles", "a | b");
Binder binder = new Binder(source);
ConfigDataProperties properties = ConfigDataProperties.get(binder);
ConfigDataActivationContext context = new ConfigDataActivationContext(CloudPlatform.KUBERNETES,
createTestProfiles());
assertThat(properties.getImports()).containsExactly("one", "two", "three");
assertThat(properties.isActive(context)).isTrue();
}
@Test
void isActiveWhenBindingToLegacyProperty() {
MapConfigurationPropertySource source = new MapConfigurationPropertySource();
source.put("spring.profiles", "a,b");
Binder binder = new Binder(source);
ConfigDataProperties properties = ConfigDataProperties.get(binder);
ConfigDataActivationContext context = new ConfigDataActivationContext(CloudPlatform.KUBERNETES,
createTestProfiles());
assertThat(properties.isActive(context)).isTrue();
}
@Test
void getWhenHasLegacyAndNewPropertyThrowsException() {
MapConfigurationPropertySource source = new MapConfigurationPropertySource();
source.put("spring.profiles", "a,b");
source.put("spring.config.activate.on-profile", "a | b");
Binder binder = new Binder(source);
assertThatExceptionOfType(InvalidConfigDataPropertyException.class)
.isThrownBy(() -> ConfigDataProperties.get(binder));
}
private Profiles createTestProfiles() {
MockEnvironment environment = new MockEnvironment();
environment.setActiveProfiles("a", "b", "c");
environment.setDefaultProfiles("d", "e", "f");
Binder binder = Binder.get(environment);
return new Profiles(environment, binder, null);
}
}

View File

@ -0,0 +1,69 @@
/*
* 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.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.boot.context.config.ConfigData.Option;
import org.springframework.core.env.MapPropertySource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link ConfigData}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class ConfigDataTests {
@Test
void createWhenPropertySourcesIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> new ConfigData(null))
.withMessage("PropertySources must not be null");
}
@Test
void createWhenOptionsIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> new ConfigData(Collections.emptyList(), (Option[]) null))
.withMessage("Options must not be null");
}
@Test
void getPropertySourcesReturnsCopyOfSources() {
MapPropertySource source = new MapPropertySource("test", Collections.emptyMap());
List<MapPropertySource> sources = new ArrayList<>(Collections.singleton(source));
ConfigData configData = new ConfigData(sources);
sources.clear();
assertThat(configData.getPropertySources()).containsExactly(source);
}
@Test
void getOptionsReturnsCopyOfOptions() {
MapPropertySource source = new MapPropertySource("test", Collections.emptyMap());
Option[] options = { Option.IGNORE_IMPORTS };
ConfigData configData = new ConfigData(Collections.singleton(source), options);
options[0] = null;
assertThat(configData.getOptions()).containsExactly(Option.IGNORE_IMPORTS);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* 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.
@ -14,11 +14,14 @@
* limitations under the License.
*/
package org.springframework.boot;
package org.springframework.boot.context.config;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Profiles;
@ -31,7 +34,8 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Phillip Webb
* @author Dave Syer
*/
class ReproTests {
@ExtendWith(UseLegacyProcessing.class)
class ConfigFileApplicationListenerLegacyReproTests {
private ConfigurableApplicationContext context;
@ -46,7 +50,6 @@ class ReproTests {
void enableProfileViaApplicationProperties() {
// gh-308
SpringApplication application = new SpringApplication(Config.class);
application.setWebApplicationType(WebApplicationType.NONE);
this.context = application.run("--spring.config.name=enableprofileviaapplicationproperties",
"--spring.profiles.active=dev");

View File

@ -19,7 +19,6 @@ package org.springframework.boot.context.config;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@ -34,24 +33,19 @@ import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.Logger;
import org.assertj.core.api.Condition;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.testsupport.BuildOutput;
import org.springframework.boot.testsupport.system.CapturedOutput;
import org.springframework.boot.testsupport.system.OutputCaptureExtension;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.Profiles;
@ -76,7 +70,8 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
* @author Eddú Meléndez
* @author Madhura Bhave
*/
@ExtendWith(OutputCaptureExtension.class)
@Deprecated
@ExtendWith({ OutputCaptureExtension.class, UseLegacyProcessing.class })
class ConfigFileApplicationListenerTests {
private final BuildOutput buildOutput = new BuildOutput(getClass());
@ -85,10 +80,26 @@ class ConfigFileApplicationListenerTests {
private final SpringApplication application = new SpringApplication();
private final ConfigFileApplicationListener initializer = new ConfigFileApplicationListener();
private final ConfigFileApplicationListener initializer;
private final Logger logger;
private ConfigurableApplicationContext context;
private Level existingLogLevel;
ConfigFileApplicationListenerTests() {
Log log = LogFactory.getLog(ConfigFileApplicationListener.class);
this.logger = (Logger) ReflectionTestUtils.getField(log, "logger");
this.initializer = new ConfigFileApplicationListener(log);
}
@BeforeEach
void setup() {
this.existingLogLevel = this.logger.getLevel();
this.logger.setLevel(Level.DEBUG);
}
@AfterEach
void cleanUp() {
if (this.context != null) {
@ -96,6 +107,7 @@ class ConfigFileApplicationListenerTests {
}
System.clearProperty("the.property");
System.clearProperty("spring.config.location");
this.logger.setLevel(this.existingLogLevel);
}
@Test
@ -218,6 +230,7 @@ class ConfigFileApplicationListenerTests {
@Test
void moreSpecificLocationTakesPrecedenceOverRoot() {
// checking order of default locations
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, "spring.config.name=specific");
this.initializer.postProcessEnvironment(this.environment, this.application);
String property = this.environment.getProperty("my.property");
@ -236,6 +249,7 @@ class ConfigFileApplicationListenerTests {
@Test
void randomValue() {
// dont need
this.initializer.postProcessEnvironment(this.environment, this.application);
String property = this.environment.getProperty("random.value");
assertThat(property).isNotNull();
@ -270,6 +284,7 @@ class ConfigFileApplicationListenerTests {
@Test
void loadDefaultYamlDocument() {
// makes sense
this.environment.setDefaultProfiles("thedefault");
this.initializer.setSearchNames("testprofilesdocument");
this.initializer.postProcessEnvironment(this.environment, this.application);
@ -335,6 +350,7 @@ class ConfigFileApplicationListenerTests {
@Test
void includedProfilesFromDefaultPropertiesShouldNotTakePrecedence() {
// required?
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment,
"spring.profiles.active=morespecific");
this.environment.getPropertySources().addLast(
@ -463,6 +479,7 @@ class ConfigFileApplicationListenerTests {
@Test
void profilesAddedToEnvironmentAndViaPropertyDuplicateEnvironmentWins(CapturedOutput output) {
// ?
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment, "spring.profiles.active=other,dev");
this.environment.addActiveProfile("other");
this.initializer.postProcessEnvironment(this.environment, this.application);
@ -481,17 +498,7 @@ class ConfigFileApplicationListenerTests {
validateProfilePreference(output, null, "dev", "other");
}
@Test
void postProcessorsAreOrderedCorrectly() {
TestConfigFileApplicationListener testListener = new TestConfigFileApplicationListener();
testListener.onApplicationEvent(
new ApplicationEnvironmentPreparedEvent(this.application, new String[0], this.environment));
}
private void validateProfilePreference(CapturedOutput output, String... profiles) {
ApplicationPreparedEvent event = new ApplicationPreparedEvent(new SpringApplication(), new String[0],
new AnnotationConfigApplicationContext());
withDebugLogging(() -> this.initializer.onApplicationEvent(event));
String log = output.toString();
// First make sure that each profile got processed only once
for (String profile : profiles) {
@ -507,19 +514,6 @@ class ConfigFileApplicationListenerTests {
}
}
private void withDebugLogging(Runnable runnable) {
Log log = LogFactory.getLog(ConfigFileApplicationListener.class);
Logger logger = (Logger) ReflectionTestUtils.getField(log, "logger");
Level previousLevel = logger.getLevel();
logger.setLevel(Level.DEBUG);
try {
runnable.run();
}
finally {
logger.setLevel(previousLevel);
}
}
private String createLogForProfile(String profile) {
String suffix = (profile != null) ? "-" + profile : "";
String string = ".properties)";
@ -701,6 +695,7 @@ class ConfigFileApplicationListenerTests {
@Test
void absoluteResourceDefaultsToFile() {
// ?
String location = new File("src/test/resources/specificlocation.properties").getAbsolutePath().replace("\\",
"/");
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment,
@ -1195,23 +1190,4 @@ class ConfigFileApplicationListenerTests {
}
static class TestConfigFileApplicationListener extends ConfigFileApplicationListener {
@Override
List<EnvironmentPostProcessor> loadPostProcessors() {
return new ArrayList<>(Collections.singletonList(new LowestPrecedenceEnvironmentPostProcessor()));
}
}
@Order(Ordered.LOWEST_PRECEDENCE)
static class LowestPrecedenceEnvironmentPostProcessor implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
assertThat(environment.getPropertySources()).hasSize(5);
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* 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.
@ -18,6 +18,7 @@ package org.springframework.boot.context.config;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
@ -32,6 +33,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Madhura Bhave
*/
@ExtendWith(UseLegacyProcessing.class)
class ConfigFileApplicationListenerYamlProfileNegationTests {
private ConfigurableApplicationContext context;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* 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.
@ -35,6 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Phillip Webb
*/
@Deprecated
class FilteredPropertySourceTests {
@Test

View File

@ -0,0 +1,141 @@
/*
* 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.Test;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.origin.Origin;
import org.springframework.boot.origin.PropertySourceOrigin;
import org.springframework.core.env.PropertySource;
import org.springframework.mock.env.MockPropertySource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link InactiveConfigDataAccessException}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class InactiveConfigDataAccessExceptionTests {
private MockPropertySource propertySource = new MockPropertySource();
private ConfigDataLocation location = new TestConfigDataLocation();
private String propertyName = "spring";
private Origin origin = new PropertySourceOrigin(this.propertySource, this.propertyName);
@Test
void createHasCorrectMessage() {
InactiveConfigDataAccessException exception = new InactiveConfigDataAccessException(this.propertySource,
this.location, this.propertyName, this.origin);
assertThat(exception).hasMessage("Inactive property source 'mockProperties' imported from location 'test' "
+ "cannot contain property 'spring' [origin: \"spring\" from property source \"mockProperties\"]");
}
@Test
void createWhenNoLocationHasCorrectMessage() {
InactiveConfigDataAccessException exception = new InactiveConfigDataAccessException(this.propertySource, null,
this.propertyName, this.origin);
assertThat(exception).hasMessage("Inactive property source 'mockProperties' "
+ "cannot contain property 'spring' [origin: \"spring\" from property source \"mockProperties\"]");
}
@Test
void createWhenNoOriginHasCorrectMessage() {
InactiveConfigDataAccessException exception = new InactiveConfigDataAccessException(this.propertySource,
this.location, this.propertyName, null);
assertThat(exception).hasMessage("Inactive property source 'mockProperties' imported from location 'test' "
+ "cannot contain property 'spring'");
}
@Test
void getPropertySourceReturnsPropertySource() {
InactiveConfigDataAccessException exception = new InactiveConfigDataAccessException(this.propertySource,
this.location, this.propertyName, this.origin);
assertThat(exception.getPropertySource()).isSameAs(this.propertySource);
}
@Test
void getLocationReturnsLocation() {
InactiveConfigDataAccessException exception = new InactiveConfigDataAccessException(this.propertySource,
this.location, this.propertyName, this.origin);
assertThat(exception.getLocation()).isSameAs(this.location);
}
@Test
void getPropertyNameReturnsPropertyName() {
InactiveConfigDataAccessException exception = new InactiveConfigDataAccessException(this.propertySource,
this.location, this.propertyName, this.origin);
assertThat(exception.getPropertyName()).isSameAs(this.propertyName);
}
@Test
void getOriginReturnsOrigin() {
InactiveConfigDataAccessException exception = new InactiveConfigDataAccessException(this.propertySource,
this.location, this.propertyName, this.origin);
assertThat(exception.getOrigin()).isSameAs(this.origin);
}
@Test
void throwIfPropertyFoundWhenSourceIsNullDoesNothing() {
ConfigDataEnvironmentContributor contributor = mock(ConfigDataEnvironmentContributor.class);
given(contributor.getConfigurationPropertySource()).willReturn(null);
InactiveConfigDataAccessException.throwIfPropertyFound(contributor, ConfigurationPropertyName.of("spring"));
}
@Test
void throwIfPropertyFoundWhenPropertyNotFoundDoesNothing() {
ConfigDataEnvironmentContributor contributor = mock(ConfigDataEnvironmentContributor.class);
ConfigurationPropertySource configurationPropertySource = ConfigurationPropertySource.from(this.propertySource);
given(contributor.getConfigurationPropertySource()).willReturn(configurationPropertySource);
InactiveConfigDataAccessException.throwIfPropertyFound(contributor, ConfigurationPropertyName.of("spring"));
}
@Test
@SuppressWarnings({ "rawtypes", "unchecked" })
void throwIfPropertyFoundWhenPropertyFoundThrowsException() {
this.propertySource.setProperty("spring", "test");
ConfigDataEnvironmentContributor contributor = mock(ConfigDataEnvironmentContributor.class);
ConfigurationPropertySource configurationPropertySource = ConfigurationPropertySource.from(this.propertySource);
given(contributor.getConfigurationPropertySource()).willReturn(configurationPropertySource);
given(contributor.getPropertySource()).willReturn((PropertySource) this.propertySource);
given(contributor.getLocation()).willReturn(this.location);
assertThatExceptionOfType(InactiveConfigDataAccessException.class)
.isThrownBy(() -> InactiveConfigDataAccessException.throwIfPropertyFound(contributor,
ConfigurationPropertyName.of("spring")))
.withMessage("Inactive property source 'mockProperties' imported from location 'test' "
+ "cannot contain property 'spring' [origin: \"spring\" from property source \"mockProperties\"]");
}
private static class TestConfigDataLocation extends ConfigDataLocation {
@Override
public String toString() {
return "test";
}
}
}

View File

@ -0,0 +1,161 @@
/*
* 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.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link Instantiator}.
*
* @author Phillip Webb
*/
class InstantiatorTests {
private ParamA paramA = new ParamA();
private ParamB paramB = new ParamB();
private ParamC paramC;
@Test
void instantiateWhenOnlyDefaultConstructorCreatesInstance() {
WithDefaultConstructor instance = createInstance(WithDefaultConstructor.class);
assertThat(instance).isInstanceOf(WithDefaultConstructor.class);
}
@Test
void instantiateWhenMultipleConstructorPicksMostArguments() {
WithMultipleConstructors instance = createInstance(WithMultipleConstructors.class);
assertThat(instance).isInstanceOf(WithMultipleConstructors.class);
}
@Test
void instantiateWhenAdditionalConstructorPicksMostSuitable() {
WithAdditionalConstructor instance = createInstance(WithAdditionalConstructor.class);
assertThat(instance).isInstanceOf(WithAdditionalConstructor.class);
}
@Test
void instantiateOrdersInstances() {
List<Object> instances = createInstantiator(Object.class).instantiate(
Arrays.asList(WithMultipleConstructors.class.getName(), WithAdditionalConstructor.class.getName()));
assertThat(instances).hasSize(2);
assertThat(instances.get(0)).isInstanceOf(WithAdditionalConstructor.class);
assertThat(instances.get(1)).isInstanceOf(WithMultipleConstructors.class);
}
@Test
void instantiateWithFactory() {
assertThat(this.paramC).isNull();
WithFactory instance = createInstance(WithFactory.class);
assertThat(instance.getParamC()).isEqualTo(this.paramC);
}
@Test
void createWhenWrongTypeThrowsException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> createInstantiator(WithDefaultConstructor.class)
.instantiate(Collections.singleton(WithAdditionalConstructor.class.getName())))
.withMessageContaining("Unable to instantiate");
}
private <T> T createInstance(Class<T> type) {
return createInstantiator(type).instantiate(Collections.singleton(type.getName())).get(0);
}
private <T> Instantiator<T> createInstantiator(Class<T> type) {
return new Instantiator<>(type, (availableParameters) -> {
availableParameters.add(ParamA.class, this.paramA);
availableParameters.add(ParamB.class, this.paramB);
availableParameters.add(ParamC.class, ParamC::new);
});
}
static class WithDefaultConstructor {
}
@Order(Ordered.LOWEST_PRECEDENCE)
static class WithMultipleConstructors {
WithMultipleConstructors() {
throw new IllegalStateException();
}
WithMultipleConstructors(ParamA paramA) {
throw new IllegalStateException();
}
WithMultipleConstructors(ParamA paramA, ParamB paramB) {
}
}
@Order(Ordered.HIGHEST_PRECEDENCE)
static class WithAdditionalConstructor {
WithAdditionalConstructor(ParamA paramA, ParamB paramB) {
}
WithAdditionalConstructor(ParamA paramA, ParamB paramB, String extra) {
throw new IllegalStateException();
}
}
static class WithFactory {
private ParamC paramC;
WithFactory(ParamC paramC) {
this.paramC = paramC;
}
ParamC getParamC() {
return this.paramC;
}
}
class ParamA {
}
class ParamB {
}
class ParamC {
ParamC(Class<?> type) {
InstantiatorTests.this.paramC = this;
}
}
}

View File

@ -0,0 +1,135 @@
/*
* 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.apache.commons.logging.Log;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.boot.context.properties.source.ConfigurationProperty;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.origin.MockOrigin;
import org.springframework.mock.env.MockPropertySource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link InvalidConfigDataPropertyException}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class InvalidConfigDataPropertyExceptionTests {
private ConfigDataLocation location = new TestConfigDataLocation();
private ConfigurationPropertyName replacement = ConfigurationPropertyName.of("replacement");
private ConfigurationPropertyName invaid = ConfigurationPropertyName.of("invalid");
private ConfigurationProperty property = new ConfigurationProperty(this.invaid, "bad", MockOrigin.of("origin"));
private Log logger = mock(Log.class);
@Test
void createHasCorrectMessage() {
assertThat(new InvalidConfigDataPropertyException(this.property, this.replacement, this.location)).hasMessage(
"Property 'invalid' imported from location 'test' is invalid and should be replaced with 'replacement' [origin: origin]");
}
@Test
void createWhenNoLocationHasCorrectMessage() {
assertThat(new InvalidConfigDataPropertyException(this.property, this.replacement, null))
.hasMessage("Property 'invalid' is invalid and should be replaced with 'replacement' [origin: origin]");
}
@Test
void createWhenNoReplacementHasCorrectMessage() {
assertThat(new InvalidConfigDataPropertyException(this.property, null, this.location))
.hasMessage("Property 'invalid' imported from location 'test' is invalid [origin: origin]");
}
@Test
void createWhenNoOriginHasCorrectMessage() {
ConfigurationProperty property = new ConfigurationProperty(this.invaid, "bad", null);
assertThat(new InvalidConfigDataPropertyException(property, this.replacement, this.location)).hasMessage(
"Property 'invalid' imported from location 'test' is invalid and should be replaced with 'replacement'");
}
@Test
void getPropertyReturnsProperty() {
InvalidConfigDataPropertyException exception = new InvalidConfigDataPropertyException(this.property,
this.replacement, this.location);
assertThat(exception.getProperty()).isEqualTo(this.property);
}
@Test
void getLocationReturnsLocation() {
InvalidConfigDataPropertyException exception = new InvalidConfigDataPropertyException(this.property,
this.replacement, this.location);
assertThat(exception.getLocation()).isEqualTo(this.location);
}
@Test
void getReplacementReturnsReplacement() {
InvalidConfigDataPropertyException exception = new InvalidConfigDataPropertyException(this.property,
this.replacement, this.location);
assertThat(exception.getReplacement()).isEqualTo(this.replacement);
}
@Test
@Disabled("Disabled until spring.profiles suppport is dropped")
void throwOrWarnWhenHasInvalidPropertyThrowsException() {
MockPropertySource propertySource = new MockPropertySource();
propertySource.setProperty("spring.profiles", "a");
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofExisting(propertySource);
assertThatExceptionOfType(InvalidConfigDataPropertyException.class)
.isThrownBy(() -> InvalidConfigDataPropertyException.throwOrWarn(this.logger, contributor))
.withMessageStartingWith("Property 'spring.profiles' is invalid and should be replaced with "
+ "'spring.config.activate.on-profile'");
}
@Test
void throwOrWarnWhenHasNoInvalidPropertyDoesNothing() {
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor
.ofExisting(new MockPropertySource());
InvalidConfigDataPropertyException.throwOrWarn(this.logger, contributor);
}
@Test
void throwOrWarnWhenHasWarningPropertyLogsWarning() {
MockPropertySource propertySource = new MockPropertySource();
propertySource.setProperty("spring.profiles", "a");
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofExisting(propertySource);
InvalidConfigDataPropertyException.throwOrWarn(this.logger, contributor);
verify(this.logger).warn("Property 'spring.profiles' is invalid and should be replaced with "
+ "'spring.config.activate.on-profile' [origin: \"spring.profiles\" from property source \"mockProperties\"]");
}
private static class TestConfigDataLocation extends ConfigDataLocation {
@Override
public String toString() {
return "test";
}
}
}

View File

@ -0,0 +1,275 @@
/*
* 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.util.Arrays;
import java.util.Collections;
import org.junit.jupiter.api.Test;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.core.env.Environment;
import org.springframework.mock.env.MockEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link Profiles}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class ProfilesTests {
@Test
void getActiveWhenNoEnvironmentProfilesAndNoPropertyReturnsEmptyArray() {
Environment environment = new MockEnvironment();
Binder binder = Binder.get(environment);
Profiles profiles = new Profiles(environment, binder, null);
assertThat(profiles.getActive()).isEmpty();
}
@Test
void getActiveWhenNoEnvironmentProfilesAndBinderProperty() {
Environment environment = new MockEnvironment();
Binder binder = new Binder(
new MapConfigurationPropertySource(Collections.singletonMap("spring.profiles.active", "a,b,c")));
Profiles profiles = new Profiles(environment, binder, null);
assertThat(profiles.getActive()).containsExactly("a", "b", "c");
}
@Test
void getActiveWhenNoEnvironmentProfilesAndEnvironmentProperty() {
MockEnvironment environment = new MockEnvironment();
environment.setProperty("spring.profiles.active", "a,b,c");
Binder binder = Binder.get(environment);
Profiles profiles = new Profiles(environment, binder, null);
assertThat(profiles.getActive()).containsExactly("a", "b", "c");
}
@Test
void getActiveWhenEnvironmentProfilesAndBinderProperty() {
MockEnvironment environment = new MockEnvironment();
environment.setActiveProfiles("a", "b", "c");
Binder binder = new Binder(
new MapConfigurationPropertySource(Collections.singletonMap("spring.profiles.active", "d,e,f")));
Profiles profiles = new Profiles(environment, binder, null);
assertThat(profiles.getActive()).containsExactly("a", "b", "c");
}
@Test
void getActiveWhenEnvironmentProfilesAndEnvironmentProperty() {
MockEnvironment environment = new MockEnvironment();
environment.setActiveProfiles("a", "b", "c");
environment.setProperty("spring.profiles.active", "d,e,f");
Binder binder = Binder.get(environment);
Profiles profiles = new Profiles(environment, binder, null);
assertThat(profiles.getActive()).containsExactly("a", "b", "c");
}
@Test
void getActiveWhenNoEnvironmentProfilesAndEnvironmentPropertyInBindNotation() {
MockEnvironment environment = new MockEnvironment();
environment.setProperty("spring.profiles.active[0]", "a");
environment.setProperty("spring.profiles.active[1]", "b");
environment.setProperty("spring.profiles.active[2]", "c");
Binder binder = Binder.get(environment);
Profiles profiles = new Profiles(environment, binder, null);
assertThat(profiles.getActive()).containsExactly("a", "b", "c");
}
@Test
void getActiveWhenEnvironmentProfilesInBindNotationAndEnvironmentPropertyReturnsEnvironmentProfiles() {
MockEnvironment environment = new MockEnvironment();
environment.setActiveProfiles("a", "b", "c");
environment.setProperty("spring.profiles.active[0]", "d");
environment.setProperty("spring.profiles.active[1]", "e");
environment.setProperty("spring.profiles.active[2]", "f");
Binder binder = Binder.get(environment);
Profiles profiles = new Profiles(environment, binder, null);
assertThat(profiles.getActive()).containsExactly("a", "b", "c");
}
@Test
void getActiveWhenHasDuplicatesReturnsUniqueElements() {
MockEnvironment environment = new MockEnvironment();
environment.setProperty("spring.profiles.active", "a,b,a,b,c");
Binder binder = Binder.get(environment);
Profiles profiles = new Profiles(environment, binder, null);
assertThat(profiles.getActive()).containsExactly("a", "b", "c");
}
@Test
void getActiveWhenHasAdditionalIncludesAdditional() {
MockEnvironment environment = new MockEnvironment();
environment.setProperty("spring.profiles.active", "a,b,c");
Binder binder = Binder.get(environment);
Profiles profiles = new Profiles(environment, binder, Arrays.asList("d", "e", "f"));
assertThat(profiles.getActive()).containsExactly("a", "b", "c", "d", "e", "f");
}
@Test
void getDefaultWhenNoEnvironmentProfilesAndNoPropertyReturnsEmptyArray() {
Environment environment = new MockEnvironment();
Binder binder = Binder.get(environment);
Profiles profiles = new Profiles(environment, binder, null);
assertThat(profiles.getDefault()).containsExactly("default");
}
@Test
void getDefaultWhenNoEnvironmentProfilesAndBinderProperty() {
Environment environment = new MockEnvironment();
Binder binder = new Binder(
new MapConfigurationPropertySource(Collections.singletonMap("spring.profiles.default", "a,b,c")));
Profiles profiles = new Profiles(environment, binder, null);
assertThat(profiles.getDefault()).containsExactly("a", "b", "c");
}
@Test
void getDefaultWhenNoEnvironmentProfilesAndEnvironmentProperty() {
MockEnvironment environment = new MockEnvironment();
environment.setProperty("spring.profiles.default", "a,b,c");
Binder binder = Binder.get(environment);
Profiles profiles = new Profiles(environment, binder, null);
assertThat(profiles.getDefault()).containsExactly("a", "b", "c");
}
@Test
void getDefaultWhenEnvironmentProfilesAndBinderProperty() {
MockEnvironment environment = new MockEnvironment();
environment.setDefaultProfiles("a", "b", "c");
Binder binder = new Binder(
new MapConfigurationPropertySource(Collections.singletonMap("spring.profiles.default", "d,e,f")));
Profiles profiles = new Profiles(environment, binder, null);
assertThat(profiles.getDefault()).containsExactly("a", "b", "c");
}
@Test
void getDefaultWhenEnvironmentProfilesAndEnvironmentProperty() {
MockEnvironment environment = new MockEnvironment();
environment.setDefaultProfiles("a", "b", "c");
environment.setProperty("spring.profiles.default", "d,e,f");
Binder binder = Binder.get(environment);
Profiles profiles = new Profiles(environment, binder, null);
assertThat(profiles.getDefault()).containsExactly("a", "b", "c");
}
@Test
void getDefaultWhenNoEnvironmentProfilesAndEnvironmentPropertyInBindNotation() {
MockEnvironment environment = new MockEnvironment();
environment.setProperty("spring.profiles.default[0]", "a");
environment.setProperty("spring.profiles.default[1]", "b");
environment.setProperty("spring.profiles.default[2]", "c");
Binder binder = Binder.get(environment);
Profiles profiles = new Profiles(environment, binder, null);
assertThat(profiles.getDefault()).containsExactly("a", "b", "c");
}
@Test
void getDefaultWhenHasDuplicatesReturnsUniqueElements() {
MockEnvironment environment = new MockEnvironment();
environment.setProperty("spring.profiles.default", "a,b,a,b,c");
Binder binder = Binder.get(environment);
Profiles profiles = new Profiles(environment, binder, null);
assertThat(profiles.getDefault()).containsExactly("a", "b", "c");
}
@Test
void getDefaultWhenEnvironmentProfilesInBindNotationAndEnvironmentPropertyReturnsEnvironmentProfiles() {
MockEnvironment environment = new MockEnvironment();
environment.setDefaultProfiles("a", "b", "c");
environment.setProperty("spring.profiles.default[0]", "d");
environment.setProperty("spring.profiles.default[1]", "e");
environment.setProperty("spring.profiles.default[2]", "f");
Binder binder = Binder.get(environment);
Profiles profiles = new Profiles(environment, binder, null);
assertThat(profiles.getDefault()).containsExactly("a", "b", "c");
}
@Test
void iteratorIteratesAllActiveProfiles() {
MockEnvironment environment = new MockEnvironment();
environment.setActiveProfiles("a", "b", "c");
environment.setDefaultProfiles("d", "e", "f");
Binder binder = Binder.get(environment);
Profiles profiles1 = new Profiles(environment, binder, null);
Profiles profiles = profiles1;
assertThat(profiles).containsExactly("a", "b", "c");
}
@Test
void iteratorIteratesAllDefaultProfilesWhenNoActive() {
MockEnvironment environment = new MockEnvironment();
environment.setDefaultProfiles("d", "e", "f");
Binder binder = Binder.get(environment);
Profiles profiles1 = new Profiles(environment, binder, null);
Profiles profiles = profiles1;
assertThat(profiles).containsExactly("d", "e", "f");
}
@Test
void isActiveWhenActiveContainsProfileReturnsTrue() {
MockEnvironment environment = new MockEnvironment();
environment.setActiveProfiles("a", "b", "c");
Binder binder = Binder.get(environment);
Profiles profiles1 = new Profiles(environment, binder, null);
Profiles profiles = profiles1;
assertThat(profiles.isAccepted("a")).isTrue();
}
@Test
void isActiveWhenActiveDoesNotContainProfileReturnsFalse() {
MockEnvironment environment = new MockEnvironment();
environment.setActiveProfiles("a", "b", "c");
Binder binder = Binder.get(environment);
Profiles profiles1 = new Profiles(environment, binder, null);
Profiles profiles = profiles1;
assertThat(profiles.isAccepted("x")).isFalse();
}
@Test
void isActiveWhenNoActiveAndDefaultContainsProfileReturnsTrue() {
MockEnvironment environment = new MockEnvironment();
environment.setDefaultProfiles("d", "e", "f");
Binder binder = Binder.get(environment);
Profiles profiles1 = new Profiles(environment, binder, null);
Profiles profiles = profiles1;
assertThat(profiles.isAccepted("d")).isTrue();
}
@Test
void isActiveWhenNoActiveAndDefaultDoesNotContainProfileReturnsFalse() {
MockEnvironment environment = new MockEnvironment();
environment.setDefaultProfiles("d", "e", "f");
Binder binder = Binder.get(environment);
Profiles profiles1 = new Profiles(environment, binder, null);
Profiles profiles = profiles1;
assertThat(profiles.isAccepted("x")).isFalse();
}
@Test
void iteratorWithProfileGroupsAndNoActive() {
MockEnvironment environment = new MockEnvironment();
environment.setProperty("spring.profiles.group.a", "e,f");
environment.setProperty("spring.profiles.group.e", "x,y");
Binder binder = Binder.get(environment);
Profiles profiles = new Profiles(environment, binder, null);
assertThat(profiles).containsExactly("default");
}
}

View File

@ -0,0 +1,63 @@
/*
* 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 org.junit.jupiter.api.Test;
import org.springframework.boot.env.PropertiesPropertySourceLoader;
import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ClassPathResource;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ResourceConfigDataLoader}.
*
* @author Madhura Bhave
* @author Phillip Webb
*/
public class ResourceConfigDataLoaderTests {
private ResourceConfigDataLoader loader = new ResourceConfigDataLoader();
@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);
assertThat(configData.getPropertySources().size()).isEqualTo(2);
PropertySource<?> source1 = configData.getPropertySources().get(0);
PropertySource<?> source2 = configData.getPropertySources().get(1);
assertThat(source1.getName()).isEqualTo("application.yml (document #0)");
assertThat(source1.getProperty("foo")).isEqualTo("bar");
assertThat(source2.getName()).isEqualTo("application.yml (document #1)");
assertThat(source2.getProperty("hello")).isEqualTo("world");
}
@Test
void loadWhenPropertySourceIsEmptyAddsNothingToConfigData() throws IOException {
ResourceConfigDataLocation location = new ResourceConfigDataLocation("testproperties.properties",
new ClassPathResource("config/0-empty/testproperties.properties"),
new PropertiesPropertySourceLoader());
ConfigData configData = this.loader.load(location);
assertThat(configData.getPropertySources().size()).isEqualTo(0);
}
}

View File

@ -0,0 +1,230 @@
/*
* 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.util.Collections;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.env.PropertiesPropertySourceLoader;
import org.springframework.boot.logging.DeferredLog;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.mock.env.MockEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link ResourceConfigDataLocationResolver}.
*
* @author Madhura Bhave
* @author Phillip Webb
*/
public class ResourceConfigDataLocationResolverTests {
private ResourceConfigDataLocationResolver resolver;
private ConfigDataLocationResolverContext context = mock(ConfigDataLocationResolverContext.class);
private MockEnvironment environment;
private Binder environmentBinder;
private ResourceLoader resourceLoader = new DefaultResourceLoader();
@BeforeEach
void setup() {
this.environment = new MockEnvironment();
this.environmentBinder = Binder.get(this.environment);
this.resolver = new ResourceConfigDataLocationResolver(new DeferredLog(), this.environmentBinder,
this.resourceLoader);
}
@Test
void isResolvableAlwaysReturnsTrue() {
assertThat(this.resolver.isResolvable(this.context, "test")).isTrue();
}
@Test
void resolveWhenLocationIsDirectoryResolvesAllMatchingFilesInDirectory() {
String location = "classpath:/configdata/properties/";
List<ResourceConfigDataLocation> locations = this.resolver.resolve(this.context, location);
assertThat(locations.size()).isEqualTo(1);
assertThat(locations).extracting(Object::toString)
.containsExactly("class path resource [configdata/properties/application.properties]");
}
@Test
void resolveWhenLocationIsFileResolvesFile() {
String location = "file:src/test/resources/configdata/properties/application.properties";
List<ResourceConfigDataLocation> locations = this.resolver.resolve(this.context, location);
assertThat(locations.size()).isEqualTo(1);
assertThat(locations).extracting(Object::toString)
.containsExactly("file [src/test/resources/configdata/properties/application.properties]");
}
@Test
void resolveWhenLocationIsFileAndNoMatchingLoaderThrowsException() {
String location = "file:src/test/resources/configdata/properties/application.unknown";
assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location))
.withMessageStartingWith("Unable to load config data from")
.satisfies((ex) -> assertThat(ex.getCause()).hasMessageStartingWith("File extension is not known"));
}
@Test
void resolveWhenLocationWildcardIsSpecifiedForClasspathLocationThrowsException() {
String location = "classpath*:application.properties";
assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location))
.withMessageContaining("Classpath wildcard patterns cannot be used as a search location");
}
@Test
void resolveWhenLocationWildcardIsNotBeforeLastSlashThrowsException() {
String location = "file:src/test/resources/*/config/";
assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location))
.withMessageStartingWith("Search location '").withMessageEndingWith("' must end with '*/'");
}
@Test
void createWhenConfigNameHasWildcardThrowsException() {
this.environment.setProperty("spring.config.name", "*/application");
assertThatIllegalStateException()
.isThrownBy(
() -> new ResourceConfigDataLocationResolver(null, this.environmentBinder, this.resourceLoader))
.withMessageStartingWith("Config name '").withMessageEndingWith("' cannot contain '*'");
}
@Test
void resolveWhenLocationHasMultipleWildcardsThrowsException() {
String location = "file:src/test/resources/config/**/";
assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location))
.withMessageStartingWith("Search location '")
.withMessageEndingWith("' cannot contain multiple wildcards");
}
@Test
void resolveWhenLocationIsWildcardDirectoriesRestrictsToOneLevelDeep() {
String location = "file:src/test/resources/config/*/";
this.environment.setProperty("spring.config.name", "testproperties");
this.resolver = new ResourceConfigDataLocationResolver(null, this.environmentBinder, this.resourceLoader);
List<ResourceConfigDataLocation> locations = this.resolver.resolve(this.context, location);
assertThat(locations.size()).isEqualTo(3);
assertThat(locations).extracting(Object::toString)
.contains("file [src/test/resources/config/1-first/testproperties.properties]")
.contains("file [src/test/resources/config/2-second/testproperties.properties]")
.doesNotContain("file [src/test/resources/config/nested/3-third/testproperties.properties]");
}
@Test
void resolveWhenLocationIsWildcardDirectoriesSortsAlphabeticallyBasedOnAbsolutePath() {
String location = "file:src/test/resources/config/*/";
this.environment.setProperty("spring.config.name", "testproperties");
this.resolver = new ResourceConfigDataLocationResolver(null, this.environmentBinder, this.resourceLoader);
List<ResourceConfigDataLocation> locations = this.resolver.resolve(this.context, location);
assertThat(locations).extracting(Object::toString).containsExactly(
"file [src/test/resources/config/0-empty/testproperties.properties]",
"file [src/test/resources/config/1-first/testproperties.properties]",
"file [src/test/resources/config/2-second/testproperties.properties]");
}
@Test
void resolveWhenLocationIsWildcardFilesLoadsAllFilesThatMatch() {
String location = "file:src/test/resources/config/*/testproperties.properties";
List<ResourceConfigDataLocation> locations = this.resolver.resolve(this.context, location);
assertThat(locations.size()).isEqualTo(3);
assertThat(locations).extracting(Object::toString)
.contains("file [src/test/resources/config/1-first/testproperties.properties]")
.contains("file [src/test/resources/config/2-second/testproperties.properties]")
.doesNotContain("file [src/test/resources/config/nested/3-third/testproperties.properties]");
}
@Test
void resolveWhenLocationIsRelativeAndFileResolves() {
this.environment.setProperty("spring.config.name", "other");
String location = "other.properties";
this.resolver = new ResourceConfigDataLocationResolver(new DeferredLog(), this.environmentBinder,
this.resourceLoader);
ClassPathResource parentResource = new ClassPathResource("configdata/properties/application.properties");
ResourceConfigDataLocation parent = new ResourceConfigDataLocation(
"classpath:/configdata/properties/application.properties", parentResource,
new PropertiesPropertySourceLoader());
given(this.context.getParent()).willReturn(parent);
List<ResourceConfigDataLocation> locations = this.resolver.resolve(this.context, location);
assertThat(locations.size()).isEqualTo(1);
assertThat(locations).extracting(Object::toString)
.contains("class path resource [configdata/properties/other.properties]");
}
@Test
void resolveWhenLocationIsRelativeAndDirectoryResolves() {
this.environment.setProperty("spring.config.name", "testproperties");
String location = "nested/3-third/";
this.resolver = new ResourceConfigDataLocationResolver(new DeferredLog(), this.environmentBinder,
this.resourceLoader);
ClassPathResource parentResource = new ClassPathResource("config/specific.properties");
ResourceConfigDataLocation parent = new ResourceConfigDataLocation("classpath:/config/specific.properties",
parentResource, new PropertiesPropertySourceLoader());
given(this.context.getParent()).willReturn(parent);
List<ResourceConfigDataLocation> locations = this.resolver.resolve(this.context, location);
assertThat(locations.size()).isEqualTo(1);
assertThat(locations).extracting(Object::toString)
.contains("class path resource [config/nested/3-third/testproperties.properties]");
}
@Test
void resolveWhenLocationIsRelativeAndNoMatchingLoaderThrowsException() {
String location = "application.other";
ClassPathResource parentResource = new ClassPathResource("configdata/application.properties");
ResourceConfigDataLocation parent = new ResourceConfigDataLocation(
"classpath:/configdata/application.properties", parentResource, new PropertiesPropertySourceLoader());
given(this.context.getParent()).willReturn(parent);
assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location))
.withMessageStartingWith("Unable to load config data from 'application.other'")
.satisfies((ex) -> assertThat(ex.getCause()).hasMessageStartingWith("File extension is not known"));
}
@Test
void resolveProfileSpecificReturnsProfileSpecificFiles() {
String location = "classpath:/configdata/properties/";
Profiles profiles = mock(Profiles.class);
given(profiles.iterator()).willReturn(Collections.singletonList("dev").iterator());
List<ResourceConfigDataLocation> locations = this.resolver.resolveProfileSpecific(this.context, location,
profiles);
assertThat(locations.size()).isEqualTo(1);
assertThat(locations).extracting(Object::toString)
.containsExactly("class path resource [configdata/properties/application-dev.properties]");
}
@Test
void resolveProfileSpecificWhenLocationIsFileReturnsEmptyList() {
String location = "classpath:/configdata/properties/application.properties";
Profiles profiles = mock(Profiles.class);
given(profiles.iterator()).willReturn(Collections.emptyIterator());
given(profiles.getActive()).willReturn(Collections.singletonList("dev"));
List<ResourceConfigDataLocation> locations = this.resolver.resolveProfileSpecific(this.context, location,
profiles);
assertThat(locations).isEmpty();
}
}

View File

@ -0,0 +1,85 @@
/*
* 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.Test;
import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link ResourceConfigDataLocation}.
*
* @author Madhura Bhave
* @author Phillip Webb
*/
public class ResourceConfigDataLocationTests {
private final String location = "location";
private final Resource resource = mock(Resource.class);
private final PropertySourceLoader propertySource = mock(PropertySourceLoader.class);
@Test
void constructorWhenNameIsNullThrowsException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new ResourceConfigDataLocation(null, this.resource, this.propertySource))
.withMessage("Name must not be null");
}
@Test
void constructorWhenResourceIsNullThrowsException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new ResourceConfigDataLocation(this.location, null, this.propertySource))
.withMessage("Resource must not be null");
}
@Test
void constructorWhenLoaderIsNullThrowsException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new ResourceConfigDataLocation(this.location, this.resource, null))
.withMessage("PropertySourceLoader must not be null");
}
@Test
void equalsWhenResourceIsTheSameReturnsTrue() {
Resource resource = new ClassPathResource("config/");
ResourceConfigDataLocation location = new ResourceConfigDataLocation("my-location", resource,
this.propertySource);
ResourceConfigDataLocation other = new ResourceConfigDataLocation("other-location", resource,
this.propertySource);
assertThat(location).isEqualTo(other);
}
@Test
void equalsWhenResourceIsDifferentReturnsFalse() {
Resource resource1 = new ClassPathResource("config/");
Resource resource2 = new ClassPathResource("configdata/");
ResourceConfigDataLocation location = new ResourceConfigDataLocation("my-location", resource1,
this.propertySource);
ResourceConfigDataLocation other = new ResourceConfigDataLocation("other-location", resource2,
this.propertySource);
assertThat(location).isNotEqualTo(other);
}
}

View File

@ -0,0 +1,43 @@
/*
* 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.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link UnsupportedConfigDataLocationException}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class UnsupportedConfigDataLocationExceptionTests {
@Test
void createSetsMessage() {
UnsupportedConfigDataLocationException exception = new UnsupportedConfigDataLocationException("test");
assertThat(exception).hasMessage("Unsupported config data location 'test'");
}
@Test
void getLocationReturnsLocation() {
UnsupportedConfigDataLocationException exception = new UnsupportedConfigDataLocationException("test");
assertThat(exception.getLocation()).isEqualTo("test");
}
}

View File

@ -0,0 +1,61 @@
/*
* 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.Test;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.MockConfigurationPropertySource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for {@link UseLegacyConfigProcessingException}.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class UseLegacyConfigProcessingExceptionTests {
@Test
void throwIfRequestedWhenMissingDoesNothing() {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
Binder binder = new Binder(source);
UseLegacyConfigProcessingException.throwIfRequested(binder);
}
@Test
void throwIfRequestedWhenFalseDoesNothing() {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("spring.config.use-legacy-processing", "false");
Binder binder = new Binder(source);
UseLegacyConfigProcessingException.throwIfRequested(binder);
}
@Test
void throwIfRequestedWhenTrueThrowsException() {
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
source.put("spring.config.use-legacy-processing", "true");
Binder binder = new Binder(source);
assertThatExceptionOfType(UseLegacyConfigProcessingException.class)
.isThrownBy(() -> UseLegacyConfigProcessingException.throwIfRequested(binder))
.satisfies((ex) -> assertThat(ex.getConfigurationProperty().getName())
.hasToString("spring.config.use-legacy-processing"));
}
}

View File

@ -0,0 +1,51 @@
/*
* 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.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.Extension;
import org.junit.jupiter.api.extension.ExtensionContext;
/**
* JUnit {@link Extension @Extension} to switch a test to use legacy processing.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
public class UseLegacyProcessing implements BeforeAllCallback, AfterAllCallback {
private static final String PROPERTY_NAME = "spring.config.use-legacy-processing";
private String propertyValue;
@Override
public void beforeAll(ExtensionContext context) throws Exception {
this.propertyValue = System.setProperty(PROPERTY_NAME, "true");
}
@Override
public void afterAll(ExtensionContext context) throws Exception {
if (this.propertyValue != null) {
System.setProperty(PROPERTY_NAME, this.propertyValue);
}
else {
System.clearProperty(PROPERTY_NAME);
}
}
}

View File

@ -0,0 +1,33 @@
/*
* 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.env;
import org.junit.jupiter.api.Test;
/**
* Tests for {@link EnvironmentPostProcessorApplicationListener}.
*
* @author Phillip Webb
*/
class EnvironmentPostProcessorApplicationListenerTests {
@Test
void test() {
// fail("Not yet implemented");
}
}

View File

@ -0,0 +1,51 @@
/*
* 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.env;
import org.apache.commons.logging.LogFactory;
import org.junit.jupiter.api.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor;
import org.springframework.mock.env.MockEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link RandomValuePropertySourceEnvironmentPostProcessor}.
*
* @author Phillip Webb
*/
class RandomValuePropertySourceEnvironmentPostProcessorTests {
private RandomValuePropertySourceEnvironmentPostProcessor postProcessor = new RandomValuePropertySourceEnvironmentPostProcessor(
LogFactory.getLog(getClass()));
@Test
void getOrderIsBeforeConfigData() {
assertThat(this.postProcessor.getOrder()).isLessThan(ConfigDataEnvironmentPostProcessor.ORDER);
}
@Test
void postProcessEnvironmentAddsPropertySource() {
MockEnvironment environment = new MockEnvironment();
this.postProcessor.postProcessEnvironment(environment, mock(SpringApplication.class));
assertThat(environment.getProperty("random.string")).isNotNull();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* 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.
@ -16,11 +16,17 @@
package org.springframework.boot.env;
import java.util.Collections;
import java.util.Random;
import java.util.UUID;
import org.junit.jupiter.api.Test;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.env.SystemEnvironmentPropertySource;
import org.springframework.mock.env.MockEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.spy;
@ -36,30 +42,30 @@ class RandomValuePropertySourceTests {
private RandomValuePropertySource source = new RandomValuePropertySource();
@Test
void notRandom() {
void getPropertyWhenNotRandomReturnsNull() {
assertThat(this.source.getProperty("foo")).isNull();
}
@Test
void string() {
void getPropertyWhenStringReturnsValue() {
assertThat(this.source.getProperty("random.string")).isNotNull();
}
@Test
void intValue() {
void getPropertyWhenIntReturnsValue() {
Integer value = (Integer) this.source.getProperty("random.int");
assertThat(value).isNotNull();
}
@Test
void uuidValue() {
void getPropertyWhenUuidReturnsValue() {
String value = (String) this.source.getProperty("random.uuid");
assertThat(value).isNotNull();
assertThat(UUID.fromString(value)).isNotNull();
}
@Test
void intRange() {
void getPropertyWhenIntRangeReturnsValue() {
Integer value = (Integer) this.source.getProperty("random.int[4,10]");
assertThat(value).isNotNull();
assertThat(value >= 4).isTrue();
@ -67,31 +73,31 @@ class RandomValuePropertySourceTests {
}
@Test
void intMax() {
void getPropertyWhenIntMaxReturnsValue() {
Integer value = (Integer) this.source.getProperty("random.int(10)");
assertThat(value).isNotNull().isLessThan(10);
}
@Test
void longValue() {
void getPropertyWhenLongReturnsValue() {
Long value = (Long) this.source.getProperty("random.long");
assertThat(value).isNotNull();
}
@Test
void longRange() {
void getPropertyWhenLongRangeReturnsValue() {
Long value = (Long) this.source.getProperty("random.long[4,10]");
assertThat(value).isNotNull().isBetween(4L, 10L);
}
@Test
void longMax() {
void getPropertyWhenLongMaxReturnsValue() {
Long value = (Long) this.source.getProperty("random.long(10)");
assertThat(value).isNotNull().isLessThan(10L);
}
@Test
void longOverflow() {
void getPropertyWhenLongOverflowReturnsValue() {
RandomValuePropertySource source = spy(this.source);
given(source.getSource()).willReturn(new Random() {
@ -108,4 +114,30 @@ class RandomValuePropertySourceTests {
assertThat(value).isNotNull().isGreaterThanOrEqualTo(4L).isLessThan(10L);
}
@Test
void addToEnvironmentAddsSource() {
MockEnvironment environment = new MockEnvironment();
RandomValuePropertySource.addToEnvironment(environment);
assertThat(environment.getProperty("random.string")).isNotNull();
}
@Test
void addToEnvironmentWhenAlreadyAddedAddsSource() {
MockEnvironment environment = new MockEnvironment();
RandomValuePropertySource.addToEnvironment(environment);
RandomValuePropertySource.addToEnvironment(environment);
assertThat(environment.getProperty("random.string")).isNotNull();
}
@Test
void addToEnvironmentAddsAfterSystemEnvironment() {
MockEnvironment environment = new MockEnvironment();
environment.getPropertySources().addFirst(new SystemEnvironmentPropertySource(
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, Collections.emptyMap()));
RandomValuePropertySource.addToEnvironment(environment);
assertThat(environment.getPropertySources().stream().map(PropertySource::getName)).containsExactly(
StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
RandomValuePropertySource.RANDOM_PROPERTY_SOURCE_NAME, "mockProperties");
}
}

View File

@ -0,0 +1,17 @@
---
my:
property: fromyamlfile
other: notempty
---
spring.config.activate.on-profile: dev
my:
property: fromdevprofile
---
spring.config.activate.on-profile: other
my:
property: fromotherprofile
---
spring.config.activate.on-profile: "!other"
my:
property: fromnototherprofile
notother: foo

View File

@ -0,0 +1,12 @@
---
my:
property: fromyamlfile
---
spring.config.activate.on-profile: dev & other
my:
property: devandother
---
spring.config.activate.on-profile: (dev | other) & another
my:
property: devorotherandanother
---

View File

@ -0,0 +1,15 @@
---
my:
property: fromyamlfile
other: notempty
---
spring.config.activate.on-profile: dev
my:
property: fromdevprofile
dev:
property: devproperty
---
spring.config.activate.on-profile: other
my:
property: fromotherprofile

View File

@ -0,0 +1,12 @@
---
my:
property: fromyamlfile
other: notempty
---
spring.config.activate.on-profile: thedefault
my:
property: fromdefaultprofile
---
spring.config.activate.on-profile: other
my:
property: fromotherprofile

View File

@ -0,0 +1,12 @@
---
my:
property: fromyamlfile
other: notempty
---
spring.config.activate.on-profile:
my:
property: fromemptyprofile
---
spring.config.activate.on-profile: other
my:
property: fromotherprofile

View File

@ -0,0 +1,10 @@
---
spring:
profiles:
active: dev
my:
property: fromyamlfile
---
spring.config.activate.on-profile: dev
my:
property: fromdevprofile

View File

@ -0,0 +1,3 @@
foo: bar
---
hello: world

View File

@ -0,0 +1 @@
yamlkey: yamlvalue

View File

@ -0,0 +1 @@
spring.profile=a

View File

@ -0,0 +1 @@
my.property=fromdefaultpropertiesfile

View File

@ -2,6 +2,5 @@ name: Phil
---
spring:
profiles: goodbye,dev
spring.config.activate.on-profile: goodbye | dev
name: Everyone