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:
parent
44f18362d3
commit
3352024b1c
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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");
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
---
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
spring:
|
||||
profiles:
|
||||
active: dev
|
||||
my:
|
||||
property: fromyamlfile
|
||||
---
|
||||
spring.config.activate.on-profile: dev
|
||||
my:
|
||||
property: fromdevprofile
|
||||
|
|
@ -0,0 +1 @@
|
|||
foo=bar
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
foo: bar
|
||||
---
|
||||
hello: world
|
||||
|
|
@ -0,0 +1 @@
|
|||
yamlkey: yamlvalue
|
||||
|
|
@ -0,0 +1 @@
|
|||
spring.profile=a
|
||||
|
|
@ -0,0 +1 @@
|
|||
spring=boot
|
||||
|
|
@ -0,0 +1 @@
|
|||
spring=boot
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
spring=boot
|
||||
#---
|
||||
spring.config.activate.on-profile=missing
|
||||
other=value
|
||||
|
|
@ -0,0 +1 @@
|
|||
spring.profiles.active=one,two,three
|
||||
|
|
@ -0,0 +1 @@
|
|||
spring.profiles.default=one,two,three
|
||||
|
|
@ -0,0 +1 @@
|
|||
my.property=fromdefaultpropertiesfile
|
||||
|
|
@ -2,6 +2,5 @@ name: Phil
|
|||
|
||||
---
|
||||
|
||||
spring:
|
||||
profiles: goodbye,dev
|
||||
spring.config.activate.on-profile: goodbye | dev
|
||||
name: Everyone
|
||||
|
|
|
|||
Loading…
Reference in New Issue