Commit 3352024b authored by Phillip Webb's avatar Phillip Webb

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: 's avatarMadhura Bhave <mbhave@vmware.com>
parent 44f18362
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -25,13 +25,14 @@ import org.springframework.boot.ResourceBanner; ...@@ -25,13 +25,14 @@ import org.springframework.boot.ResourceBanner;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType; import org.springframework.boot.WebApplicationType;
import org.springframework.boot.context.config.AnsiOutputApplicationListener; 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.ClasspathLoggingApplicationListener;
import org.springframework.boot.context.logging.LoggingApplicationListener; import org.springframework.boot.context.logging.LoggingApplicationListener;
import org.springframework.boot.devtools.remote.client.RemoteClientConfiguration; import org.springframework.boot.devtools.remote.client.RemoteClientConfiguration;
import org.springframework.boot.devtools.restart.RestartInitializer; import org.springframework.boot.devtools.restart.RestartInitializer;
import org.springframework.boot.devtools.restart.RestartScopeInitializer; import org.springframework.boot.devtools.restart.RestartScopeInitializer;
import org.springframework.boot.devtools.restart.Restarter; import org.springframework.boot.devtools.restart.Restarter;
import org.springframework.boot.env.EnvironmentPostProcessorApplicationListener;
import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
...@@ -71,7 +72,7 @@ public final class RemoteSpringApplication { ...@@ -71,7 +72,7 @@ public final class RemoteSpringApplication {
private Collection<ApplicationListener<?>> getListeners() { private Collection<ApplicationListener<?>> getListeners() {
List<ApplicationListener<?>> listeners = new ArrayList<>(); List<ApplicationListener<?>> listeners = new ArrayList<>();
listeners.add(new AnsiOutputApplicationListener()); listeners.add(new AnsiOutputApplicationListener());
listeners.add(new ConfigFileApplicationListener()); listeners.add(new EnvironmentPostProcessorApplicationListener(ConfigDataEnvironmentPostProcessor.class));
listeners.add(new ClasspathLoggingApplicationListener()); listeners.add(new ClasspathLoggingApplicationListener());
listeners.add(new LoggingApplicationListener()); listeners.add(new LoggingApplicationListener());
listeners.add(new RemoteUrlPropertyExtractor()); listeners.add(new RemoteUrlPropertyExtractor());
......
/*
* 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);
}
}
}
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
package org.springframework.boot.test.context; package org.springframework.boot.test.context;
import org.springframework.boot.context.config.ConfigFileApplicationListener;
import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
...@@ -28,14 +27,16 @@ import org.springframework.test.context.ContextConfiguration; ...@@ -28,14 +27,16 @@ import org.springframework.test.context.ContextConfiguration;
* *
* @author Phillip Webb * @author Phillip Webb
* @since 1.4.0 * @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 public class ConfigFileApplicationContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> { implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override @Override
public void initialize(ConfigurableApplicationContext applicationContext) { public void initialize(ConfigurableApplicationContext applicationContext) {
new ConfigFileApplicationListener() { new org.springframework.boot.context.config.ConfigFileApplicationListener() {
public void apply() { public void apply() {
addPropertySources(applicationContext.getEnvironment(), applicationContext); addPropertySources(applicationContext.getEnvironment(), applicationContext);
addPostProcessors(applicationContext); addPostProcessors(applicationContext);
......
/*
* 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 {
}
}
/*
* 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 {
}
}
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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; ...@@ -33,6 +33,8 @@ import static org.assertj.core.api.Assertions.assertThat;
* *
* @author Phillip Webb * @author Phillip Webb
*/ */
@Deprecated
@SuppressWarnings("deprecation")
@ExtendWith(SpringExtension.class) @ExtendWith(SpringExtension.class)
@DirtiesContext @DirtiesContext
@ContextConfiguration(classes = ConfigFileApplicationContextInitializerTests.Config.class, @ContextConfiguration(classes = ConfigFileApplicationContextInitializerTests.Config.class,
......
...@@ -23,7 +23,6 @@ import java.util.Arrays; ...@@ -23,7 +23,6 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
...@@ -237,7 +236,7 @@ public class SpringApplication { ...@@ -237,7 +236,7 @@ public class SpringApplication {
private Map<String, Object> defaultProperties; private Map<String, Object> defaultProperties;
private Set<String> additionalProfiles = new HashSet<>(); private Set<String> additionalProfiles = Collections.emptySet();
private boolean allowBeanDefinitionOverriding; private boolean allowBeanDefinitionOverriding;
...@@ -352,6 +351,7 @@ public class SpringApplication { ...@@ -352,6 +351,7 @@ public class SpringApplication {
ConfigurationPropertySources.attach(environment); ConfigurationPropertySources.attach(environment);
listeners.environmentPrepared(environment); listeners.environmentPrepared(environment);
DefaultPropertiesPropertySource.moveToEnd(environment); DefaultPropertiesPropertySource.moveToEnd(environment);
configureAdditionalProfiles(environment);
bindToSpringApplication(environment); bindToSpringApplication(environment);
if (!this.isCustomEnvironment) { if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
...@@ -527,9 +527,16 @@ public class SpringApplication { ...@@ -527,9 +527,16 @@ public class SpringApplication {
* @see org.springframework.boot.context.config.ConfigFileApplicationListener * @see org.springframework.boot.context.config.ConfigFileApplicationListener
*/ */
protected void configureProfiles(ConfigurableEnvironment environment, String[] args) { 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) { private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
...@@ -1043,7 +1050,15 @@ public class SpringApplication { ...@@ -1043,7 +1050,15 @@ public class SpringApplication {
* @param profiles the additional profiles to set * @param profiles the additional profiles to set
*/ */
public void setAdditionalProfiles(String... profiles) { 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; ...@@ -22,8 +22,10 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import org.apache.commons.logging.Log;
import org.springframework.boot.SpringApplication; 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.context.event.ApplicationPreparedEvent;
import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.json.JsonParser; import org.springframework.boot.json.JsonParser;
...@@ -92,14 +94,36 @@ import org.springframework.util.StringUtils; ...@@ -92,14 +94,36 @@ import org.springframework.util.StringUtils;
public class CloudFoundryVcapEnvironmentPostProcessor public class CloudFoundryVcapEnvironmentPostProcessor
implements EnvironmentPostProcessor, Ordered, ApplicationListener<ApplicationPreparedEvent> { 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_APPLICATION = "VCAP_APPLICATION";
private static final String VCAP_SERVICES = "VCAP_SERVICES"; 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 // 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) { public void setOrder(int order) {
this.order = order; this.order = order;
...@@ -128,9 +152,17 @@ public class CloudFoundryVcapEnvironmentPostProcessor ...@@ -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 @Override
public void onApplicationEvent(ApplicationPreparedEvent event) { 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) { private void addWithPrefix(Properties properties, Properties other, String prefix) {
...@@ -148,7 +180,7 @@ public class CloudFoundryVcapEnvironmentPostProcessor ...@@ -148,7 +180,7 @@ public class CloudFoundryVcapEnvironmentPostProcessor
extractPropertiesFromApplication(properties, map); extractPropertiesFromApplication(properties, map);
} }
catch (Exception ex) { catch (Exception ex) {
logger.error("Could not parse VCAP_APPLICATION", ex); this.logger.error("Could not parse VCAP_APPLICATION", ex);
} }
return properties; return properties;
} }
...@@ -161,7 +193,7 @@ public class CloudFoundryVcapEnvironmentPostProcessor ...@@ -161,7 +193,7 @@ public class CloudFoundryVcapEnvironmentPostProcessor
extractPropertiesFromServices(properties, map); extractPropertiesFromServices(properties, map);
} }
catch (Exception ex) { catch (Exception ex) {
logger.error("Could not parse VCAP_SERVICES", ex); this.logger.error("Could not parse VCAP_SERVICES", ex);
} }
return properties; return properties;
} }
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -20,6 +20,7 @@ import org.springframework.boot.ansi.AnsiOutput; ...@@ -20,6 +20,7 @@ import org.springframework.boot.ansi.AnsiOutput;
import org.springframework.boot.ansi.AnsiOutput.Enabled; import org.springframework.boot.ansi.AnsiOutput.Enabled;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.env.EnvironmentPostProcessorApplicationListener;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
...@@ -46,8 +47,8 @@ public class AnsiOutputApplicationListener ...@@ -46,8 +47,8 @@ public class AnsiOutputApplicationListener
@Override @Override
public int getOrder() { public int getOrder() {
// Apply after ConfigFileApplicationListener has called EnvironmentPostProcessors // Apply after EnvironmentPostProcessorApplicationListener
return ConfigFileApplicationListener.DEFAULT_ORDER + 1; return EnvironmentPostProcessorApplicationListener.DEFAULT_ORDER + 1;
} }
} }
/*
* 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;
}
}
/*
* 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();
}
}
/*
* 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;
}
}
/*
* 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;
}
}
/*
* 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);
}
}
}
/*
* 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);
}
}
/*
* 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);
}
}
/*
* 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;
}
/*
* 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;
}
}
/*
* 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 {
}
/*
* 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();
}
}
/*
* 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();
}
/*
* 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;
}
}
/*
* 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; ...@@ -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.Binder;
import org.springframework.boot.context.properties.bind.PropertySourcesPlaceholdersResolver; import org.springframework.boot.context.properties.bind.PropertySourcesPlaceholdersResolver;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources; 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.EnvironmentPostProcessor;
import org.springframework.boot.env.PropertySourceLoader; import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.boot.env.RandomValuePropertySource; import org.springframework.boot.env.RandomValuePropertySource;
...@@ -58,7 +59,6 @@ import org.springframework.context.ConfigurableApplicationContext; ...@@ -58,7 +59,6 @@ import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ConfigurationClassPostProcessor; import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.event.SmartApplicationListener; import org.springframework.context.event.SmartApplicationListener;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.MutablePropertySources;
...@@ -109,11 +109,11 @@ import org.springframework.util.StringUtils; ...@@ -109,11 +109,11 @@ import org.springframework.util.StringUtils;
* @author Eddú Meléndez * @author Eddú Meléndez
* @author Madhura Bhave * @author Madhura Bhave
* @since 1.0.0 * @since 1.0.0
* @deprecated since 2.4.0 in favor of {@link ConfigDataEnvironmentPostProcessor}
*/ */
@Deprecated
public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered { 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) // 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/"; private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/*/,file:./config/";
...@@ -164,7 +164,7 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor, ...@@ -164,7 +164,7 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
*/ */
public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 10; 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 = {}; private static final Resource[] EMPTY_RESOURCES = {};
...@@ -176,6 +176,14 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor, ...@@ -176,6 +176,14 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
private int order = DEFAULT_ORDER; private int order = DEFAULT_ORDER;
public ConfigFileApplicationListener() {
this(new DeferredLog());
}
ConfigFileApplicationListener(Log logger) {
this.logger = logger;
}
@Override @Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) { public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return ApplicationEnvironmentPreparedEvent.class.isAssignableFrom(eventType) return ApplicationEnvironmentPreparedEvent.class.isAssignableFrom(eventType)
...@@ -184,25 +192,8 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor, ...@@ -184,25 +192,8 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
@Override @Override
public void onApplicationEvent(ApplicationEvent event) { public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) { throw new IllegalStateException(
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event); "ConfigFileApplicationListener is deprected and can only be used as an EnvironmentPostProcessor");
}
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());
} }
@Override @Override
...@@ -210,11 +201,6 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor, ...@@ -210,11 +201,6 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
addPropertySources(environment, application.getResourceLoader()); 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. * Add config file property sources to the specified environment.
* @param environment the environment to add source to * @param environment the environment to add source to
...@@ -290,10 +276,7 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor, ...@@ -290,10 +276,7 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
} }
private void reorderSources(ConfigurableEnvironment environment) { private void reorderSources(ConfigurableEnvironment environment) {
PropertySource<?> defaultProperties = environment.getPropertySources().remove(DEFAULT_PROPERTIES); DefaultPropertiesPropertySource.moveToEnd(environment);
if (defaultProperties != null) {
environment.getPropertySources().addLast(defaultProperties);
}
} }
} }
...@@ -332,26 +315,27 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor, ...@@ -332,26 +315,27 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
} }
void load() { void load() {
FilteredPropertySource.apply(this.environment, DEFAULT_PROPERTIES, LOAD_FILTERED_PROPERTY, FilteredPropertySource.apply(this.environment, DefaultPropertiesPropertySource.NAME, LOAD_FILTERED_PROPERTY,
(defaultProperties) -> { this::loadWithFilteredProperties);
this.profiles = new LinkedList<>(); }
this.processedProfiles = new LinkedList<>();
this.activatedProfiles = false; private void loadWithFilteredProperties(PropertySource<?> defaultProperties) {
this.loaded = new LinkedHashMap<>(); this.profiles = new LinkedList<>();
initializeProfiles(); this.processedProfiles = new LinkedList<>();
while (!this.profiles.isEmpty()) { this.activatedProfiles = false;
Profile profile = this.profiles.poll(); this.loaded = new LinkedHashMap<>();
if (isDefaultProfile(profile)) { initializeProfiles();
addProfileToEnvironment(profile.getName()); while (!this.profiles.isEmpty()) {
} Profile profile = this.profiles.poll();
load(profile, this::getPositiveProfileFilter, if (isDefaultProfile(profile)) {
addToLoaded(MutablePropertySources::addLast, false)); addProfileToEnvironment(profile.getName());
this.processedProfiles.add(profile); }
} load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false));
load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true)); this.processedProfiles.add(profile);
addLoadedPropertySources(); }
applyActiveProfiles(defaultProperties); load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
}); addLoadedPropertySources();
applyActiveProfiles(defaultProperties);
} }
/** /**
...@@ -748,8 +732,8 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor, ...@@ -748,8 +732,8 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
private void addLoadedPropertySource(MutablePropertySources destination, String lastAdded, private void addLoadedPropertySource(MutablePropertySources destination, String lastAdded,
PropertySource<?> source) { PropertySource<?> source) {
if (lastAdded == null) { if (lastAdded == null) {
if (destination.contains(DEFAULT_PROPERTIES)) { if (destination.contains(DefaultPropertiesPropertySource.NAME)) {
destination.addBefore(DEFAULT_PROPERTIES, source); destination.addBefore(DefaultPropertiesPropertySource.NAME, source);
} }
else { else {
destination.addLast(source); destination.addLast(source);
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -28,7 +28,9 @@ import org.springframework.core.env.PropertySource; ...@@ -28,7 +28,9 @@ import org.springframework.core.env.PropertySource;
* {@link ConfigFileApplicationListener} to filter out properties for specific operations. * {@link ConfigFileApplicationListener} to filter out properties for specific operations.
* *
* @author Phillip Webb * @author Phillip Webb
* @deprecated since 2.4.0 along with {@link ConfigFileApplicationListener}
*/ */
@Deprecated
class FilteredPropertySource extends PropertySource<PropertySource<?>> { class FilteredPropertySource extends PropertySource<PropertySource<?>> {
private final Set<String> filteredProperties; private final Set<String> filteredProperties;
......
/*
* 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());
}
}
}
/*
* 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);
}
}
/*
* 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();
}
}
/*
* 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();
}
}
/*
* 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());
}
}
/*
* 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();
}
}
/*
* 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;
}
}
/*
* 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;
}
}
}
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -18,6 +18,6 @@ ...@@ -18,6 +18,6 @@
* External configuration support allowing 'application.properties' to be loaded and used * External configuration support allowing 'application.properties' to be loaded and used
* within a Spring Boot application. * 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; package org.springframework.boot.context.config;
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -16,7 +16,10 @@ ...@@ -16,7 +16,10 @@
package org.springframework.boot.env; package org.springframework.boot.env;
import org.apache.commons.logging.Log;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
...@@ -26,12 +29,14 @@ import org.springframework.core.env.Environment; ...@@ -26,12 +29,14 @@ import org.springframework.core.env.Environment;
* <p> * <p>
* EnvironmentPostProcessor implementations have to be registered in * EnvironmentPostProcessor implementations have to be registered in
* {@code META-INF/spring.factories}, using the fully qualified name of this class as the * {@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> * <p>
* {@code EnvironmentPostProcessor} processors are encouraged to detect whether Spring's * Since Spring Boot 2.4, {@code EnvironmentPostProcessor} implementations may optionally
* {@link org.springframework.core.Ordered Ordered} interface has been implemented or if * take a single {@link Log} or {@link DeferredLogFactory} instance as a constructor
* the {@link org.springframework.core.annotation.Order @Order} annotation is present and * argument. The injected {@link Log} instance will defer output until the application has
* to sort instances accordingly if so prior to invocation. * been full prepared to allow the environment itself to configure logging levels.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Stephane Nicoll * @author Stephane Nicoll
......
/*
* 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; ...@@ -23,8 +23,10 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySource;
import org.springframework.core.env.StandardEnvironment; import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.log.LogMessage;
import org.springframework.util.DigestUtils; import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
...@@ -75,9 +77,7 @@ public class RandomValuePropertySource extends PropertySource<Random> { ...@@ -75,9 +77,7 @@ public class RandomValuePropertySource extends PropertySource<Random> {
if (!name.startsWith(PREFIX)) { if (!name.startsWith(PREFIX)) {
return null; return null;
} }
if (logger.isTraceEnabled()) { logger.trace(LogMessage.format("Generating random property for '%s'", name));
logger.trace("Generating random property for '" + name + "'");
}
return getRandomValue(name.substring(PREFIX.length())); return getRandomValue(name.substring(PREFIX.length()));
} }
...@@ -138,8 +138,23 @@ public class RandomValuePropertySource extends PropertySource<Random> { ...@@ -138,8 +138,23 @@ public class RandomValuePropertySource extends PropertySource<Random> {
} }
public static void addToEnvironment(ConfigurableEnvironment environment) { public static void addToEnvironment(ConfigurableEnvironment environment) {
environment.getPropertySources().addAfter(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, addToEnvironment(environment, logger);
new RandomValuePropertySource(RANDOM_PROPERTY_SOURCE_NAME)); }
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"); logger.trace("RandomValuePropertySource add to Environment");
} }
......
/*
* 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 @@ ...@@ -752,7 +752,11 @@
"name": "spring.profiles", "name": "spring.profiles",
"type": "java.util.List<java.lang.String>", "type": "java.util.List<java.lang.String>",
"sourceType": "org.springframework.boot.context.config.ConfigFileApplicationListener", "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", "name": "spring.profiles.active",
...@@ -779,6 +783,24 @@ ...@@ -779,6 +783,24 @@
"description": "Enable trace logs.", "description": "Enable trace logs.",
"sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener", "sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener",
"defaultValue": false "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": [ "hints": [
...@@ -861,6 +883,25 @@ ...@@ -861,6 +883,25 @@
"name": "spring-profile-name" "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=\ ...@@ -3,6 +3,14 @@ org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\ org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader 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 # Run Listeners
org.springframework.boot.SpringApplicationRunListener=\ org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener org.springframework.boot.context.event.EventPublishingRunListener
...@@ -23,18 +31,19 @@ org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer ...@@ -23,18 +31,19 @@ org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
org.springframework.context.ApplicationListener=\ org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\ org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\ org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.context.FileEncodingApplicationListener,\ org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\ org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\ org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\ org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\ org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.env.EnvironmentPostProcessorApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
# Environment Post Processors # Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\ org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\ 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.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor,\ org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor,\
org.springframework.boot.reactor.DebugAgentEnvironmentPostProcessor org.springframework.boot.reactor.DebugAgentEnvironmentPostProcessor
......
...@@ -587,8 +587,8 @@ class SpringApplicationTests { ...@@ -587,8 +587,8 @@ class SpringApplicationTests {
ConfigurableEnvironment environment = new StandardEnvironment(); ConfigurableEnvironment environment = new StandardEnvironment();
application.setEnvironment(environment); application.setEnvironment(environment);
this.context = application.run("--spring.profiles.active=bar,spam"); this.context = application.run("--spring.profiles.active=bar,spam");
// Command line should always come last // Since Boot 2.4 additional should always be last
assertThat(environment.getActiveProfiles()).containsExactly("foo", "bar", "spam"); assertThat(environment.getActiveProfiles()).containsExactly("bar", "spam", "foo");
} }
@Test @Test
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package org.springframework.boot.cloud.cloudfoundry; package org.springframework.boot.cloud.cloudfoundry;
import org.apache.commons.logging.LogFactory;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor; import org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor;
...@@ -33,7 +34,8 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -33,7 +34,8 @@ import static org.assertj.core.api.Assertions.assertThat;
*/ */
class CloudFoundryVcapEnvironmentPostProcessorTests { class CloudFoundryVcapEnvironmentPostProcessorTests {
private final CloudFoundryVcapEnvironmentPostProcessor initializer = new CloudFoundryVcapEnvironmentPostProcessor(); private final CloudFoundryVcapEnvironmentPostProcessor initializer = new CloudFoundryVcapEnvironmentPostProcessor(
LogFactory.getLog(getClass()));
private final ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(); private final ConfigurableApplicationContext context = new AnnotationConfigApplicationContext();
......
/*
* 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;
}
}
/*
* 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;
}
}
}
/*
* 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 {
}
}
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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; ...@@ -35,6 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* *
* @author Phillip Webb * @author Phillip Webb
*/ */
@Deprecated
class FilteredPropertySourceTests { class FilteredPropertySourceTests {
@Test @Test
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment