diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/HostInfoEnvironmentPostProcessor.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/HostInfoEnvironmentPostProcessor.java index 92a88210..e44d4364 100644 --- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/HostInfoEnvironmentPostProcessor.java +++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/HostInfoEnvironmentPostProcessor.java @@ -19,7 +19,6 @@ package org.springframework.cloud.client; import java.util.LinkedHashMap; import org.springframework.boot.SpringApplication; -import org.springframework.boot.context.config.ConfigFileApplicationListener; import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.context.properties.source.ConfigurationPropertySources; @@ -36,8 +35,8 @@ import org.springframework.core.env.MapPropertySource; */ public class HostInfoEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered { - // Before ConfigFileApplicationListener - private int order = ConfigFileApplicationListener.DEFAULT_ORDER - 1; + // Before BootstrapConfigFileApplicationListener + private int order = Ordered.HIGHEST_PRECEDENCE + 9; @Override public int getOrder() { diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapConfigFileApplicationListener.java b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapConfigFileApplicationListener.java index 9e3ef98c..b6744eee 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapConfigFileApplicationListener.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/BootstrapConfigFileApplicationListener.java @@ -16,21 +16,999 @@ package org.springframework.cloud.bootstrap; -import org.springframework.boot.context.config.ConfigFileApplicationListener; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.commons.logging.Log; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.boot.DefaultPropertiesPropertySource; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor; +import org.springframework.boot.context.config.ConfigDataLocation; +import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; +import org.springframework.boot.context.event.ApplicationPreparedEvent; +import org.springframework.boot.context.properties.bind.Bindable; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.boot.context.properties.bind.PropertySourcesPlaceholdersResolver; +import org.springframework.boot.context.properties.source.ConfigurationPropertySources; +import org.springframework.boot.env.EnvironmentPostProcessor; +import org.springframework.boot.env.PropertySourceLoader; +import org.springframework.boot.env.RandomValuePropertySource; +import org.springframework.boot.logging.DeferredLog; +import org.springframework.cloud.util.PropertyUtils; import org.springframework.context.ApplicationEvent; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.ConfigurationClassPostProcessor; +import org.springframework.context.event.SmartApplicationListener; +import org.springframework.core.Ordered; +import org.springframework.core.env.AbstractEnvironment; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Environment; +import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.Profiles; +import org.springframework.core.env.PropertySource; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.core.io.support.SpringFactoriesLoader; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; +import org.springframework.util.ResourceUtils; +import org.springframework.util.StringUtils; /** - * Overrides ConfigFileApplicationListener to disable exception. + * {@link EnvironmentPostProcessor} that configures the context environment by loading + * properties from well known file locations. By default properties will be loaded from + * 'application.properties' and/or 'application.yml' files in the following locations: + * + * The list is ordered by precedence (properties defined in locations higher in the list + * override those defined in lower locations). + *

+ * Alternative search locations and names can be specified using + * {@link #setSearchLocations(String)} and {@link #setSearchNames(String)}. + *

+ * Additional files will also be loaded based on active profiles. For example if a 'web' + * profile is active 'application-web.properties' and 'application-web.yml' will be + * considered. + *

+ * The 'spring.config.name' property can be used to specify an alternative name to load + * and the 'spring.config.location' property can be used to specify alternative search + * locations or specific files. + *

* - * @author Spencer Gibb - * @since 3.0.0 + * @author Dave Syer + * @author Phillip Webb + * @author Stephane Nicoll + * @author Andy Wilkinson + * @author EddĂș MelĂ©ndez + * @author Madhura Bhave + * @author Scott Frederick + * @since 1.0.0 {@link ConfigDataEnvironmentPostProcessor} */ -@SuppressWarnings("deprecation") -public class BootstrapConfigFileApplicationListener extends ConfigFileApplicationListener { +public class BootstrapConfigFileApplicationListener + implements EnvironmentPostProcessor, SmartApplicationListener, Ordered { + + // 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_NAMES = "application"; + + private static final Set NO_SEARCH_NAMES = Collections.singleton(null); + + private static final Bindable STRING_ARRAY = Bindable.of(String[].class); + + private static final Bindable> STRING_LIST = Bindable.listOf(String.class); + + private static final Set LOAD_FILTERED_PROPERTY; + + static { + Set filteredProperties = new HashSet<>(); + filteredProperties.add("spring.profiles.active"); + filteredProperties.add("spring.profiles.include"); + LOAD_FILTERED_PROPERTY = Collections.unmodifiableSet(filteredProperties); + } + + /** + * The "active profiles" property name. + */ + public static final String ACTIVE_PROFILES_PROPERTY = "spring.profiles.active"; + + /** + * The "includes profiles" property name. + */ + public static final String INCLUDE_PROFILES_PROPERTY = "spring.profiles.include"; + + /** + * The "config name" property name. + */ + public static final String CONFIG_NAME_PROPERTY = "spring.config.name"; + + /** + * The "config location" property name. + */ + public static final String CONFIG_LOCATION_PROPERTY = "spring.config.location"; + + /** + * The "config additional location" property name. + */ + public static final String CONFIG_ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location"; + + /** + * The default order for the processor. + */ + public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 10; + + private final Log logger; + + private static final Resource[] EMPTY_RESOURCES = {}; + + private static final Comparator FILE_COMPARATOR = Comparator.comparing(File::getAbsolutePath); + + private String searchLocations; + + private String names; + + private int order = DEFAULT_ORDER; + + public BootstrapConfigFileApplicationListener() { + this(new DeferredLog()); + } + + BootstrapConfigFileApplicationListener(Log logger) { + this.logger = logger; + } + + @Override + public boolean supportsEventType(Class eventType) { + return ApplicationEnvironmentPreparedEvent.class.isAssignableFrom(eventType) + || ApplicationPreparedEvent.class.isAssignableFrom(eventType); + } @Override public void onApplicationEvent(ApplicationEvent event) { - // do nothing, overrides exception thrown. + // do nothing + } + + @Override + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { + if (PropertyUtils.bootstrapEnabled(environment)) { + addPropertySources(environment, application.getResourceLoader()); + } + } + + /** + * Add config file property sources to the specified environment. + * @param environment the environment to add source to + * @param resourceLoader the resource loader + * @see #addPostProcessors(ConfigurableApplicationContext) + */ + protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { + RandomValuePropertySource.addToEnvironment(environment); + new Loader(environment, resourceLoader).load(); + } + + /** + * Add appropriate post-processors to post-configure the property-sources. + * @param context the context to configure + */ + protected void addPostProcessors(ConfigurableApplicationContext context) { + context.addBeanFactoryPostProcessor(new PropertySourceOrderingPostProcessor(context)); + } + + public void setOrder(int order) { + this.order = order; + } + + @Override + public int getOrder() { + return this.order; + } + + /** + * Set the search locations that will be considered as a comma-separated list. Each + * search location should be a directory path (ending in "/") and it will be prefixed + * by the file names constructed from {@link #setSearchNames(String) search names} and + * profiles (if any) plus file extensions supported by the properties loaders. + * Locations are considered in the order specified, with later items taking precedence + * (like a map merge). + * @param locations the search locations + */ + public void setSearchLocations(String locations) { + Assert.hasLength(locations, "Locations must not be empty"); + this.searchLocations = locations; + } + + /** + * Sets the names of the files that should be loaded (excluding file extension) as a + * comma-separated list. + * @param names the names to load + */ + public void setSearchNames(String names) { + Assert.hasLength(names, "Names must not be empty"); + this.names = names; + } + + /** + * {@link BeanFactoryPostProcessor} to re-order our property sources below any + * {@code @PropertySource} items added by the {@link ConfigurationClassPostProcessor}. + */ + private static class PropertySourceOrderingPostProcessor implements BeanFactoryPostProcessor, Ordered { + + private final ConfigurableApplicationContext context; + + PropertySourceOrderingPostProcessor(ConfigurableApplicationContext context) { + this.context = context; + } + + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE; + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + reorderSources(this.context.getEnvironment()); + } + + private void reorderSources(ConfigurableEnvironment environment) { + DefaultPropertiesPropertySource.moveToEnd(environment); + } + + } + + /** + * Loads candidate property sources and configures the active profiles. + */ + private class Loader { + + private final Log logger = BootstrapConfigFileApplicationListener.this.logger; + + private final ConfigurableEnvironment environment; + + private final PropertySourcesPlaceholdersResolver placeholdersResolver; + + private final ResourceLoader resourceLoader; + + private final List propertySourceLoaders; + + private Deque profiles; + + private List processedProfiles; + + private boolean activatedProfiles; + + private Map loaded; + + private Map> loadDocumentsCache = new HashMap<>(); + + Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { + this.environment = environment; + this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment); + this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader(null); + this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class, + this.resourceLoader.getClassLoader()); + } + + void load() { + FilteredPropertySource.apply(this.environment, DefaultPropertiesPropertySource.NAME, LOAD_FILTERED_PROPERTY, + this::loadWithFilteredProperties); + } + + private void loadWithFilteredProperties(PropertySource defaultProperties) { + this.profiles = new LinkedList<>(); + this.processedProfiles = new LinkedList<>(); + this.activatedProfiles = false; + this.loaded = new LinkedHashMap<>(); + initializeProfiles(); + while (!this.profiles.isEmpty()) { + Profile profile = this.profiles.poll(); + if (isDefaultProfile(profile)) { + addProfileToEnvironment(profile.getName()); + } + load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false)); + this.processedProfiles.add(profile); + } + load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true)); + addLoadedPropertySources(); + applyActiveProfiles(defaultProperties); + } + + /** + * Initialize profile information from both the {@link Environment} active + * profiles and any {@code spring.profiles.active}/{@code spring.profiles.include} + * properties that are already set. + */ + private void initializeProfiles() { + // The default profile for these purposes is represented as null. We add it + // first so that it is processed first and has lowest priority. + this.profiles.add(null); + Binder binder = Binder.get(this.environment); + Set activatedViaProperty = getProfiles(binder, ACTIVE_PROFILES_PROPERTY); + Set includedViaProperty = getProfiles(binder, INCLUDE_PROFILES_PROPERTY); + List otherActiveProfiles = getOtherActiveProfiles(activatedViaProperty, includedViaProperty); + this.profiles.addAll(otherActiveProfiles); + // Any pre-existing active profiles set via property sources (e.g. + // System properties) take precedence over those added in config files. + this.profiles.addAll(includedViaProperty); + addActiveProfiles(activatedViaProperty); + if (this.profiles.size() == 1) { // only has null profile + for (String defaultProfileName : getDefaultProfiles(binder)) { + Profile defaultProfile = new Profile(defaultProfileName, true); + this.profiles.add(defaultProfile); + } + } + } + + private String[] getDefaultProfiles(Binder binder) { + return binder.bind(AbstractEnvironment.DEFAULT_PROFILES_PROPERTY_NAME, STRING_ARRAY) + .orElseGet(this.environment::getDefaultProfiles); + } + + private List getOtherActiveProfiles(Set activatedViaProperty, + Set includedViaProperty) { + return Arrays.stream(this.environment.getActiveProfiles()).map(Profile::new).filter( + (profile) -> !activatedViaProperty.contains(profile) && !includedViaProperty.contains(profile)) + .collect(Collectors.toList()); + } + + void addActiveProfiles(Set profiles) { + if (profiles.isEmpty()) { + return; + } + if (this.activatedProfiles) { + if (this.logger.isDebugEnabled()) { + this.logger.debug("Profiles already activated, '" + profiles + "' will not be applied"); + } + return; + } + this.profiles.addAll(profiles); + if (this.logger.isDebugEnabled()) { + this.logger.debug("Activated activeProfiles " + StringUtils.collectionToCommaDelimitedString(profiles)); + } + this.activatedProfiles = true; + removeUnprocessedDefaultProfiles(); + } + + private void removeUnprocessedDefaultProfiles() { + this.profiles.removeIf((profile) -> (profile != null && profile.isDefaultProfile())); + } + + private DocumentFilter getPositiveProfileFilter(Profile profile) { + return (Document document) -> { + if (profile == null) { + return ObjectUtils.isEmpty(document.getProfiles()); + } + return ObjectUtils.containsElement(document.getProfiles(), profile.getName()) + && this.environment.acceptsProfiles(Profiles.of(document.getProfiles())); + }; + } + + private DocumentFilter getNegativeProfileFilter(Profile profile) { + return (Document document) -> (profile == null && !ObjectUtils.isEmpty(document.getProfiles()) + && this.environment.acceptsProfiles(Profiles.of(document.getProfiles()))); + } + + private DocumentConsumer addToLoaded(BiConsumer> addMethod, + boolean checkForExisting) { + return (profile, document) -> { + if (checkForExisting) { + for (MutablePropertySources merged : this.loaded.values()) { + if (merged.contains(document.getPropertySource().getName())) { + return; + } + } + } + MutablePropertySources merged = this.loaded.computeIfAbsent(profile, + (k) -> new MutablePropertySources()); + addMethod.accept(merged, document.getPropertySource()); + }; + } + + private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) { + getSearchLocations().forEach((location) -> { + String nonOptionalLocation = ConfigDataLocation.of(location).getValue(); + boolean isDirectory = location.endsWith("/"); + Set names = isDirectory ? getSearchNames() : NO_SEARCH_NAMES; + names.forEach((name) -> load(nonOptionalLocation, name, profile, filterFactory, consumer)); + }); + } + + private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory, + DocumentConsumer consumer) { + if (!StringUtils.hasText(name)) { + for (PropertySourceLoader loader : this.propertySourceLoaders) { + if (canLoadFileExtension(loader, location)) { + load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer); + return; + } + } + throw new IllegalStateException("File extension of config file location '" + location + + "' is not known to any PropertySourceLoader. If the location is meant to reference " + + "a directory, it must end in '/'"); + } + Set processed = new HashSet<>(); + for (PropertySourceLoader loader : this.propertySourceLoaders) { + for (String fileExtension : loader.getFileExtensions()) { + if (processed.add(fileExtension)) { + loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory, + consumer); + } + } + } + } + + private boolean canLoadFileExtension(PropertySourceLoader loader, String name) { + return Arrays.stream(loader.getFileExtensions()) + .anyMatch((fileExtension) -> StringUtils.endsWithIgnoreCase(name, fileExtension)); + } + + private void loadForFileExtension(PropertySourceLoader loader, String prefix, String fileExtension, + Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) { + DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null); + DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile); + if (profile != null) { + // Try profile-specific file & profile section in profile file (gh-340) + String profileSpecificFile = prefix + "-" + profile + fileExtension; + load(loader, profileSpecificFile, profile, defaultFilter, consumer); + load(loader, profileSpecificFile, profile, profileFilter, consumer); + // Try profile specific sections in files we've already processed + for (Profile processedProfile : this.processedProfiles) { + if (processedProfile != null) { + String previouslyLoaded = prefix + "-" + processedProfile + fileExtension; + load(loader, previouslyLoaded, profile, profileFilter, consumer); + } + } + } + // Also try the profile-specific section (if any) of the normal file + load(loader, prefix + fileExtension, profile, profileFilter, consumer); + } + + private void load(PropertySourceLoader loader, String location, Profile profile, DocumentFilter filter, + DocumentConsumer consumer) { + Resource[] resources = getResources(location); + for (Resource resource : resources) { + try { + if (resource == null || !resource.exists()) { + if (this.logger.isTraceEnabled()) { + StringBuilder description = getDescription("Skipped missing config ", location, resource, + profile); + this.logger.trace(description); + } + continue; + } + if (!StringUtils.hasText(StringUtils.getFilenameExtension(resource.getFilename()))) { + if (this.logger.isTraceEnabled()) { + StringBuilder description = getDescription("Skipped empty config extension ", location, + resource, profile); + this.logger.trace(description); + } + continue; + } + if (resource.isFile() && isPatternLocation(location) && hasHiddenPathElement(resource)) { + if (this.logger.isTraceEnabled()) { + StringBuilder description = getDescription("Skipped location with hidden path element ", + location, resource, profile); + this.logger.trace(description); + } + continue; + } + String name = "applicationConfig: [" + getLocationName(location, resource) + "]"; + List documents = loadDocuments(loader, name, resource); + if (CollectionUtils.isEmpty(documents)) { + if (this.logger.isTraceEnabled()) { + StringBuilder description = getDescription("Skipped unloaded config ", location, resource, + profile); + this.logger.trace(description); + } + continue; + } + List loaded = new ArrayList<>(); + for (Document document : documents) { + if (filter.match(document)) { + addActiveProfiles(document.getActiveProfiles()); + addIncludedProfiles(document.getIncludeProfiles()); + loaded.add(document); + } + } + Collections.reverse(loaded); + if (!loaded.isEmpty()) { + loaded.forEach((document) -> consumer.accept(profile, document)); + if (this.logger.isDebugEnabled()) { + StringBuilder description = getDescription("Loaded config file ", location, resource, + profile); + this.logger.debug(description); + } + } + } + catch (Exception ex) { + StringBuilder description = getDescription("Failed to load property source from ", location, + resource, profile); + throw new IllegalStateException(description.toString(), ex); + } + } + } + + private boolean hasHiddenPathElement(Resource resource) throws IOException { + String cleanPath = StringUtils.cleanPath(resource.getFile().getAbsolutePath()); + for (Path value : Paths.get(cleanPath)) { + if (value.toString().startsWith("..")) { + return true; + } + } + return false; + } + + private String getLocationName(String locationReference, Resource resource) { + if (!locationReference.contains("*")) { + return locationReference; + } + if (resource instanceof FileSystemResource) { + return ((FileSystemResource) resource).getPath(); + } + return resource.getDescription(); + } + + private Resource[] getResources(String locationReference) { + try { + if (isPatternLocation(locationReference)) { + return getResourcesFromPatternLocationReference(locationReference); + } + return new Resource[] { this.resourceLoader.getResource(locationReference) }; + } + catch (Exception ex) { + return EMPTY_RESOURCES; + } + } + + private boolean isPatternLocation(String location) { + return location.contains("*"); + } + + private Resource[] getResourcesFromPatternLocationReference(String locationReference) throws IOException { + String directoryPath = locationReference.substring(0, locationReference.indexOf("*/")); + Resource resource = this.resourceLoader.getResource(directoryPath); + File[] files = resource.getFile().listFiles(File::isDirectory); + if (files != null) { + String fileName = locationReference.substring(locationReference.lastIndexOf("/") + 1); + Arrays.sort(files, FILE_COMPARATOR); + return Arrays.stream(files).map((file) -> file.listFiles((dir, name) -> name.equals(fileName))) + .filter(Objects::nonNull).flatMap((Function>) Arrays::stream) + .map(FileSystemResource::new).toArray(Resource[]::new); + } + return EMPTY_RESOURCES; + } + + private void addIncludedProfiles(Set includeProfiles) { + LinkedList existingProfiles = new LinkedList<>(this.profiles); + this.profiles.clear(); + this.profiles.addAll(includeProfiles); + this.profiles.removeAll(this.processedProfiles); + this.profiles.addAll(existingProfiles); + } + + private List loadDocuments(PropertySourceLoader loader, String name, Resource resource) + throws IOException { + DocumentsCacheKey cacheKey = new DocumentsCacheKey(loader, resource); + List documents = this.loadDocumentsCache.get(cacheKey); + if (documents == null) { + List> loaded = loader.load(name, resource); + documents = asDocuments(loaded); + this.loadDocumentsCache.put(cacheKey, documents); + } + return documents; + } + + private List asDocuments(List> loaded) { + if (loaded == null) { + return Collections.emptyList(); + } + return loaded.stream().map((propertySource) -> { + Binder binder = new Binder(ConfigurationPropertySources.from(propertySource), + this.placeholdersResolver); + String[] profiles = binder.bind("spring.profiles", STRING_ARRAY).orElse(null); + Set activeProfiles = getProfiles(binder, ACTIVE_PROFILES_PROPERTY); + Set includeProfiles = getProfiles(binder, INCLUDE_PROFILES_PROPERTY); + return new Document(propertySource, profiles, activeProfiles, includeProfiles); + }).collect(Collectors.toList()); + } + + private StringBuilder getDescription(String prefix, String locationReference, Resource resource, + Profile profile) { + StringBuilder result = new StringBuilder(prefix); + try { + if (resource != null) { + String uri = resource.getURI().toASCIIString(); + result.append("'"); + result.append(uri); + result.append("' ("); + result.append(locationReference); + result.append(")"); + } + } + catch (IOException ex) { + result.append(locationReference); + } + if (profile != null) { + result.append(" for profile "); + result.append(profile); + } + return result; + } + + private Set getProfiles(Binder binder, String name) { + return binder.bind(name, STRING_ARRAY).map(this::asProfileSet).orElse(Collections.emptySet()); + } + + private Set asProfileSet(String[] profileNames) { + List profiles = new ArrayList<>(); + for (String profileName : profileNames) { + profiles.add(new Profile(profileName)); + } + return new LinkedHashSet<>(profiles); + } + + private void addProfileToEnvironment(String profile) { + for (String activeProfile : this.environment.getActiveProfiles()) { + if (activeProfile.equals(profile)) { + return; + } + } + this.environment.addActiveProfile(profile); + } + + private Set getSearchLocations() { + Set locations = getSearchLocations(CONFIG_ADDITIONAL_LOCATION_PROPERTY); + if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) { + locations.addAll(getSearchLocations(CONFIG_LOCATION_PROPERTY)); + } + else { + locations.addAll(asResolvedSet(BootstrapConfigFileApplicationListener.this.searchLocations, + DEFAULT_SEARCH_LOCATIONS)); + } + return locations; + } + + private Set getSearchLocations(String propertyName) { + Set locations = new LinkedHashSet<>(); + if (this.environment.containsProperty(propertyName)) { + for (String path : asResolvedSet(this.environment.getProperty(propertyName), null)) { + if (!path.contains("$")) { + path = StringUtils.cleanPath(path); + Assert.state(!path.startsWith(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX), + "Classpath wildcard patterns cannot be used as a search location"); + validateWildcardLocation(path); + if (!ResourceUtils.isUrl(path)) { + path = ResourceUtils.FILE_URL_PREFIX + path; + } + } + locations.add(path); + } + } + return locations; + } + + private void validateWildcardLocation(String path) { + if (path.contains("*")) { + Assert.state(StringUtils.countOccurrencesOf(path, "*") == 1, + () -> "Search location '" + path + "' cannot contain multiple wildcards"); + String directoryPath = path.substring(0, path.lastIndexOf("/") + 1); + Assert.state(directoryPath.endsWith("*/"), () -> "Search location '" + path + "' must end with '*/'"); + } + } + + private Set getSearchNames() { + if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) { + String property = this.environment.getProperty(CONFIG_NAME_PROPERTY); + Set names = asResolvedSet(property, null); + names.forEach(this::assertValidConfigName); + return names; + } + return asResolvedSet(BootstrapConfigFileApplicationListener.this.names, DEFAULT_NAMES); + } + + private Set asResolvedSet(String value, String fallback) { + List list = Arrays.asList(StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray( + (value != null) ? this.environment.resolvePlaceholders(value) : fallback))); + Collections.reverse(list); + return new LinkedHashSet<>(list); + } + + private void assertValidConfigName(String name) { + Assert.state(!name.contains("*"), () -> "Config name '" + name + "' cannot contain wildcards"); + } + + private void addLoadedPropertySources() { + MutablePropertySources destination = this.environment.getPropertySources(); + List loaded = new ArrayList<>(this.loaded.values()); + Collections.reverse(loaded); + String lastAdded = null; + Set added = new HashSet<>(); + for (MutablePropertySources sources : loaded) { + for (PropertySource source : sources) { + if (added.add(source.getName())) { + addLoadedPropertySource(destination, lastAdded, source); + lastAdded = source.getName(); + } + } + } + } + + private void addLoadedPropertySource(MutablePropertySources destination, String lastAdded, + PropertySource source) { + if (lastAdded == null) { + if (destination.contains(DefaultPropertiesPropertySource.NAME)) { + destination.addBefore(DefaultPropertiesPropertySource.NAME, source); + } + else { + destination.addLast(source); + } + } + else { + destination.addAfter(lastAdded, source); + } + } + + private void applyActiveProfiles(PropertySource defaultProperties) { + List activeProfiles = new ArrayList<>(); + if (defaultProperties != null) { + Binder binder = new Binder(ConfigurationPropertySources.from(defaultProperties), + new PropertySourcesPlaceholdersResolver(this.environment)); + activeProfiles.addAll(bindStringList(binder, "spring.profiles.include")); + if (!this.activatedProfiles) { + activeProfiles.addAll(bindStringList(binder, "spring.profiles.active")); + } + } + this.processedProfiles.stream().filter(this::isDefaultProfile).map(Profile::getName) + .forEach(activeProfiles::add); + this.environment.setActiveProfiles(activeProfiles.toArray(new String[0])); + } + + private boolean isDefaultProfile(Profile profile) { + return profile != null && !profile.isDefaultProfile(); + } + + private List bindStringList(Binder binder, String property) { + return binder.bind(property, STRING_LIST).orElse(Collections.emptyList()); + } + + } + + /** + * A Spring Profile that can be loaded. + */ + private static class Profile { + + private final String name; + + private final boolean defaultProfile; + + Profile(String name) { + this(name, false); + } + + Profile(String name, boolean defaultProfile) { + Assert.notNull(name, "Name must not be null"); + this.name = name; + this.defaultProfile = defaultProfile; + } + + String getName() { + return this.name; + } + + boolean isDefaultProfile() { + return this.defaultProfile; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null || obj.getClass() != getClass()) { + return false; + } + return ((Profile) obj).name.equals(this.name); + } + + @Override + public int hashCode() { + return this.name.hashCode(); + } + + @Override + public String toString() { + return this.name; + } + + } + + /** + * Cache key used to save loading the same document multiple times. + */ + private static class DocumentsCacheKey { + + private final PropertySourceLoader loader; + + private final Resource resource; + + DocumentsCacheKey(PropertySourceLoader loader, Resource resource) { + this.loader = loader; + this.resource = resource; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + DocumentsCacheKey other = (DocumentsCacheKey) obj; + return this.loader.equals(other.loader) && this.resource.equals(other.resource); + } + + @Override + public int hashCode() { + return this.loader.hashCode() * 31 + this.resource.hashCode(); + } + + } + + /** + * A single document loaded by a {@link PropertySourceLoader}. + */ + private static class Document { + + private final PropertySource propertySource; + + private String[] profiles; + + private final Set activeProfiles; + + private final Set includeProfiles; + + Document(PropertySource propertySource, String[] profiles, Set activeProfiles, + Set includeProfiles) { + this.propertySource = propertySource; + this.profiles = profiles; + this.activeProfiles = activeProfiles; + this.includeProfiles = includeProfiles; + } + + PropertySource getPropertySource() { + return this.propertySource; + } + + String[] getProfiles() { + return this.profiles; + } + + Set getActiveProfiles() { + return this.activeProfiles; + } + + Set getIncludeProfiles() { + return this.includeProfiles; + } + + @Override + public String toString() { + return this.propertySource.toString(); + } + + } + + /** + * Factory used to create a {@link DocumentFilter}. + */ + @FunctionalInterface + private interface DocumentFilterFactory { + + /** + * Create a filter for the given profile. + * @param profile the profile or {@code null} + * @return the filter + */ + DocumentFilter getDocumentFilter(Profile profile); + + } + + /** + * Filter used to restrict when a {@link Document} is loaded. + */ + @FunctionalInterface + private interface DocumentFilter { + + boolean match(Document document); + + } + + /** + * Consumer used to handle a loaded {@link Document}. + */ + @FunctionalInterface + private interface DocumentConsumer { + + void accept(Profile profile, Document document); + + } + + /** + * Internal {@link PropertySource} implementation used by + * {@link BootstrapConfigFileApplicationListener} to filter out properties for + * specific operations. + * + * @author Phillip Webb + */ + static class FilteredPropertySource extends PropertySource> { + + private final Set filteredProperties; + + FilteredPropertySource(PropertySource original, Set filteredProperties) { + super(original.getName(), original); + this.filteredProperties = filteredProperties; + } + + @Override + public Object getProperty(String name) { + if (this.filteredProperties.contains(name)) { + return null; + } + return getSource().getProperty(name); + } + + static void apply(ConfigurableEnvironment environment, String propertySourceName, + Set filteredProperties, Consumer> operation) { + MutablePropertySources propertySources = environment.getPropertySources(); + PropertySource original = propertySources.get(propertySourceName); + if (original == null) { + operation.accept(null); + return; + } + propertySources.replace(propertySourceName, new FilteredPropertySource(original, filteredProperties)); + try { + operation.accept(original); + } + finally { + propertySources.replace(propertySourceName, original); + } + } + } } diff --git a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/config/PropertySourceBootstrapConfiguration.java b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/config/PropertySourceBootstrapConfiguration.java index f3a7439c..4791cb6f 100644 --- a/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/config/PropertySourceBootstrapConfiguration.java +++ b/spring-cloud-context/src/main/java/org/springframework/cloud/bootstrap/config/PropertySourceBootstrapConfiguration.java @@ -28,7 +28,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.context.config.ConfigFileApplicationListener; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Binder; @@ -36,6 +35,7 @@ import org.springframework.boot.logging.LogFile; import org.springframework.boot.logging.LoggingInitializationContext; import org.springframework.boot.logging.LoggingSystem; import org.springframework.cloud.bootstrap.BootstrapApplicationListener; +import org.springframework.cloud.bootstrap.BootstrapConfigFileApplicationListener; import org.springframework.cloud.context.environment.EnvironmentChangeEvent; import org.springframework.cloud.logging.LoggingRebinder; import org.springframework.context.ApplicationContextInitializer; @@ -239,7 +239,7 @@ public class PropertySourceBootstrapConfiguration } else { Collections.addAll(profiles, getProfilesForValue( - propertySource.getProperty(ConfigFileApplicationListener.INCLUDE_PROFILES_PROPERTY))); + propertySource.getProperty(BootstrapConfigFileApplicationListener.INCLUDE_PROFILES_PROPERTY))); } return profiles; } diff --git a/spring-cloud-context/src/main/resources/META-INF/spring.factories b/spring-cloud-context/src/main/resources/META-INF/spring.factories index d2e2ed82..3565caad 100644 --- a/spring-cloud-context/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-context/src/main/resources/META-INF/spring.factories @@ -22,5 +22,6 @@ org.springframework.cloud.bootstrap.RefreshBootstrapRegistryInitializer,\ org.springframework.cloud.bootstrap.TextEncryptorConfigBootstrapper # Environment Post Processors org.springframework.boot.env.EnvironmentPostProcessor=\ +org.springframework.cloud.bootstrap.BootstrapConfigFileApplicationListener,\ org.springframework.cloud.bootstrap.encrypt.DecryptEnvironmentPostProcessor,\ org.springframework.cloud.util.random.CachedRandomPropertySourceEnvironmentPostProcessor diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/BootstrapOrderingAutoConfigurationIntegrationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/BootstrapOrderingAutoConfigurationIntegrationTests.java index cbedca47..693314ac 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/BootstrapOrderingAutoConfigurationIntegrationTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/BootstrapOrderingAutoConfigurationIntegrationTests.java @@ -29,7 +29,7 @@ import org.springframework.test.context.ActiveProfiles; import static org.assertj.core.api.BDDAssertions.then; @SpringBootTest(classes = Application.class, - properties = { "encrypt.key:deadbeef", "spring.config.use-legacy-processing=true" }) + properties = { "encrypt.key:deadbeef", "spring.cloud.bootstrap.enabled=true" }) @ActiveProfiles("encrypt") public class BootstrapOrderingAutoConfigurationIntegrationTests { diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/BootstrapOrderingCustomOverrideSystemPropertiesIntegrationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/BootstrapOrderingCustomOverrideSystemPropertiesIntegrationTests.java index 002af5ab..4842d873 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/BootstrapOrderingCustomOverrideSystemPropertiesIntegrationTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/BootstrapOrderingCustomOverrideSystemPropertiesIntegrationTests.java @@ -39,7 +39,7 @@ import static java.util.Collections.singletonMap; import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest(classes = Application.class, - properties = { "spring.config.use-legacy-processing=true", "spring.cloud.bootstrap.name:ordering" }) + properties = { "spring.cloud.bootstrap.enabled=true", "spring.cloud.bootstrap.name:ordering" }) public class BootstrapOrderingCustomOverrideSystemPropertiesIntegrationTests { @Autowired diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/BootstrapOrderingCustomPropertySourceIntegrationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/BootstrapOrderingCustomPropertySourceIntegrationTests.java index ad236854..e9d1012e 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/BootstrapOrderingCustomPropertySourceIntegrationTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/BootstrapOrderingCustomPropertySourceIntegrationTests.java @@ -37,7 +37,7 @@ import org.springframework.test.context.ActiveProfiles; import static org.assertj.core.api.BDDAssertions.then; @SpringBootTest(classes = Application.class, properties = { "encrypt.key:deadbeef", - "spring.cloud.bootstrap.name:custom", "spring.config.use-legacy-processing=true" }) + "spring.cloud.bootstrap.name:custom", "spring.cloud.bootstrap.enabled=true" }) @ActiveProfiles("encrypt") public class BootstrapOrderingCustomPropertySourceIntegrationTests { diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/BootstrapSourcesOrderingTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/BootstrapSourcesOrderingTests.java index 11b9d6cd..86ce73b1 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/BootstrapSourcesOrderingTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/BootstrapSourcesOrderingTests.java @@ -26,7 +26,7 @@ import org.springframework.context.annotation.Configuration; import static org.assertj.core.api.BDDAssertions.then; import static org.springframework.cloud.bootstrap.TestHigherPriorityBootstrapConfiguration.firstToBeCreated; -@SpringBootTest(classes = Application.class, properties = "spring.config.use-legacy-processing=true") +@SpringBootTest(classes = Application.class, properties = "spring.cloud.bootstrap.enabled=true") public class BootstrapSourcesOrderingTests { @Test diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/config/BootstrapConfigurationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/config/BootstrapConfigurationTests.java index 7d9228ae..016dba39 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/config/BootstrapConfigurationTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/config/BootstrapConfigurationTests.java @@ -80,7 +80,7 @@ public class BootstrapConfigurationTests { this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE).sources(BareConfiguration.class) .properties("spring.cloud.bootstrap.location=" + externalPropertiesPath, - "spring.config.use-legacy-processing=true") + "spring.cloud.bootstrap.enabled=true") .run(); then(this.context.getEnvironment().getProperty("info.name")).isEqualTo("externalPropertiesInfoName"); then(this.context.getEnvironment().getProperty("info.desc")).isNull(); @@ -95,7 +95,7 @@ public class BootstrapConfigurationTests { this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE).sources(BareConfiguration.class) .properties("spring.cloud.bootstrap.additional-location=" + externalPropertiesPath, - "spring.config.use-legacy-processing=true") + "spring.cloud.bootstrap.enabled=true") .run(); then(this.context.getEnvironment().getProperty("info.name")).isEqualTo("externalPropertiesInfoName"); then(this.context.getEnvironment().getProperty("info.desc")).isEqualTo("defaultPropertiesInfoDesc"); @@ -107,7 +107,7 @@ public class BootstrapConfigurationTests { @Test public void bootstrapPropertiesAvailableInInitializer() { this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE) - .properties("spring.config.use-legacy-processing=true").sources(BareConfiguration.class) + .properties("spring.cloud.bootstrap.enabled=true").sources(BareConfiguration.class) .initializers(new ApplicationContextInitializer() { @Override public void initialize(ConfigurableApplicationContext applicationContext) { @@ -134,7 +134,7 @@ public class BootstrapConfigurationTests { public void picksUpAdditionalPropertySource() { PropertySourceConfiguration.MAP.put("bootstrap.foo", "bar"); this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE) - .properties("spring.config.use-legacy-processing=true").sources(BareConfiguration.class).run(); + .properties("spring.cloud.bootstrap.enabled=true").sources(BareConfiguration.class).run(); then(this.context.getEnvironment().getProperty("bootstrap.foo")).isEqualTo("bar"); then(this.context.getEnvironment().getPropertySources() .contains(PropertySourceBootstrapConfiguration.BOOTSTRAP_PROPERTY_SOURCE_NAME + "-testBootstrap")) @@ -146,7 +146,7 @@ public class BootstrapConfigurationTests { System.setProperty("expected.fail", "true"); Throwable throwable = Assertions.assertThrows(RuntimeException.class, () -> { this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE) - .properties("spring.config.use-legacy-processing=true").sources(BareConfiguration.class).run(); + .properties("spring.cloud.bootstrap.enabled=true").sources(BareConfiguration.class).run(); }); then(throwable.getMessage().equals("Planned")); } @@ -156,7 +156,7 @@ public class BootstrapConfigurationTests { PropertySourceConfiguration.MAP.put("bootstrap.foo", "bar"); System.setProperty("bootstrap.foo", "system"); this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE) - .properties("spring.config.use-legacy-processing=true").sources(BareConfiguration.class).run(); + .properties("spring.cloud.bootstrap.enabled=true").sources(BareConfiguration.class).run(); then(this.context.getEnvironment().getProperty("bootstrap.foo")).isEqualTo("bar"); } @@ -166,7 +166,7 @@ public class BootstrapConfigurationTests { PropertySourceConfiguration.MAP.put("spring.cloud.config.overrideSystemProperties", "false"); System.setProperty("bootstrap.foo", "system"); this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE) - .properties("spring.config.use-legacy-processing=true").sources(BareConfiguration.class).run(); + .properties("spring.cloud.bootstrap.enabled=true").sources(BareConfiguration.class).run(); then(this.context.getEnvironment().getProperty("bootstrap.foo")).isEqualTo("system"); } @@ -180,7 +180,7 @@ public class BootstrapConfigurationTests { PropertySourceConfiguration.MAP.put("spring.cloud.config.allowOverride", "false"); System.setProperty("bootstrap.foo", "system"); this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE) - .properties("spring.config.use-legacy-processing=true").sources(BareConfiguration.class).run(); + .properties("spring.cloud.bootstrap.enabled=true").sources(BareConfiguration.class).run(); then(this.context.getEnvironment().getProperty("bootstrap.foo")).isEqualTo("bar"); } @@ -191,7 +191,7 @@ public class BootstrapConfigurationTests { PropertySourceConfiguration.MAP.put("spring.cloud.config.allowOverride", "true"); System.setProperty("bootstrap.foo", "system"); this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE) - .properties("spring.config.use-legacy-processing=true").sources(BareConfiguration.class).run(); + .properties("spring.cloud.bootstrap.enabled=true").sources(BareConfiguration.class).run(); then(this.context.getEnvironment().getProperty("bootstrap.foo")).isEqualTo("system"); } @@ -204,7 +204,7 @@ public class BootstrapConfigurationTests { environment.getPropertySources().addLast( new MapPropertySource("last", Collections.singletonMap("bootstrap.foo", "splat"))); this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE) - .properties("spring.config.use-legacy-processing=true").environment(environment) + .properties("spring.cloud.bootstrap.enabled=true").environment(environment) .sources(BareConfiguration.class).run(); then(this.context.getEnvironment().getProperty("bootstrap.foo")).isEqualTo("splat"); } @@ -214,7 +214,7 @@ public class BootstrapConfigurationTests { System.setProperty("expected.name", "main"); this.context = new SpringApplicationBuilder() .web(WebApplicationType.NONE).properties("spring.cloud.bootstrap.name:other", - "spring.config.use-legacy-processing=true", "spring.config.name:plain") + "spring.cloud.bootstrap.enabled=true", "spring.config.name:plain") .sources(BareConfiguration.class).run(); then(this.context.getEnvironment().getProperty("spring.application.name")).isEqualTo("app"); // The parent is called "main" because spring.application.name is specified in @@ -231,7 +231,7 @@ public class BootstrapConfigurationTests { System.setProperty("expected.name", "main"); this.context = new SpringApplicationBuilder() .web(WebApplicationType.NONE).properties("spring.cloud.bootstrap.name:application", - "spring.config.use-legacy-processing=true", "spring.config.name:other") + "spring.cloud.bootstrap.enabled=true", "spring.config.name:other") .sources(BareConfiguration.class).run(); then(this.context.getEnvironment().getProperty("spring.application.name")).isEqualTo("main"); // The parent has no name because spring.application.name is not @@ -243,7 +243,7 @@ public class BootstrapConfigurationTests { public void applicationNameOnlyInBootstrap() { System.setProperty("expected.name", "main"); this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE) - .properties("spring.cloud.bootstrap.name:other", "spring.config.use-legacy-processing=true") + .properties("spring.cloud.bootstrap.name:other", "spring.cloud.bootstrap.enabled=true") .sources(BareConfiguration.class).run(); // The main context is called "main" because spring.application.name is specified // in other.properties (and not in the main config file) @@ -258,7 +258,7 @@ public class BootstrapConfigurationTests { public void environmentEnrichedOnceWhenSharedWithChildContext() { PropertySourceConfiguration.MAP.put("bootstrap.foo", "bar"); this.context = new SpringApplicationBuilder().sources(BareConfiguration.class) - .properties("spring.config.use-legacy-processing=true").environment(new StandardEnvironment()) + .properties("spring.cloud.bootstrap.enabled=true").environment(new StandardEnvironment()) .child(BareConfiguration.class).web(WebApplicationType.NONE).run(); then(this.context.getEnvironment().getProperty("bootstrap.foo")).isEqualTo("bar"); then(this.context.getParent().getEnvironment()).isEqualTo(this.context.getEnvironment()); @@ -274,7 +274,7 @@ public class BootstrapConfigurationTests { TestHigherPriorityBootstrapConfiguration.count.set(0); PropertySourceConfiguration.MAP.put("bootstrap.foo", "bar"); this.context = new SpringApplicationBuilder().sources(BareConfiguration.class) - .properties("spring.config.use-legacy-processing=true").child(BareConfiguration.class) + .properties("spring.cloud.bootstrap.enabled=true").child(BareConfiguration.class) .web(WebApplicationType.NONE).run(); then(TestHigherPriorityBootstrapConfiguration.count.get()).isEqualTo(1); then(this.context.getParent()).isNotNull(); @@ -286,7 +286,7 @@ public class BootstrapConfigurationTests { @Test public void listOverride() { this.context = new SpringApplicationBuilder().sources(BareConfiguration.class) - .properties("spring.config.use-legacy-processing=true").child(BareConfiguration.class) + .properties("spring.cloud.bootstrap.enabled=true").child(BareConfiguration.class) .web(WebApplicationType.NONE).run(); ListProperties listProperties = new ListProperties(); Binder.get(this.context.getEnvironment()).bind("list", Bindable.ofInstance(listProperties)); @@ -299,7 +299,7 @@ public class BootstrapConfigurationTests { TestHigherPriorityBootstrapConfiguration.count.set(0); PropertySourceConfiguration.MAP.put("bootstrap.foo", "bar"); SpringApplicationBuilder builder = new SpringApplicationBuilder() - .properties("spring.config.use-legacy-processing=true").sources(BareConfiguration.class); + .properties("spring.cloud.bootstrap.enabled=true").sources(BareConfiguration.class); this.sibling = builder.child(BareConfiguration.class).properties("spring.application.name=sibling") .web(WebApplicationType.NONE).run(); this.context = builder.child(BareConfiguration.class).properties("spring.application.name=context") @@ -321,7 +321,7 @@ public class BootstrapConfigurationTests { public void environmentEnrichedInParentContext() { PropertySourceConfiguration.MAP.put("bootstrap.foo", "bar"); this.context = new SpringApplicationBuilder().sources(BareConfiguration.class) - .properties("spring.config.use-legacy-processing=true").child(BareConfiguration.class) + .properties("spring.cloud.bootstrap.enabled=true").child(BareConfiguration.class) .web(WebApplicationType.NONE).run(); then(this.context.getEnvironment().getProperty("bootstrap.foo")).isEqualTo("bar"); then(this.context.getParent().getEnvironment()).isNotSameAs(this.context.getEnvironment()); @@ -341,7 +341,7 @@ public class BootstrapConfigurationTests { ConfigurableApplicationContext parent = new SpringApplicationBuilder().sources(BareConfiguration.class) .profiles("parent").web(WebApplicationType.NONE).run(); this.context = new SpringApplicationBuilder(BareConfiguration.class) - .properties("spring.config.use-legacy-processing=true").profiles("child").parent(parent) + .properties("spring.cloud.bootstrap.enabled=true").profiles("child").parent(parent) .web(WebApplicationType.NONE).run(); then(this.context.getParent().getEnvironment()).isNotSameAs(this.context.getEnvironment()); // The ApplicationContext merges profiles (profiles and property sources), see @@ -367,7 +367,7 @@ public class BootstrapConfigurationTests { public void includeProfileFromBootstrapPropertySource() { PropertySourceConfiguration.MAP.put("spring.profiles.include", "bar,baz"); this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE) - .properties("spring.config.use-legacy-processing=true").profiles("foo").sources(BareConfiguration.class) + .properties("spring.cloud.bootstrap.enabled=true").profiles("foo").sources(BareConfiguration.class) .run(); then(this.context.getEnvironment().acceptsProfiles("baz")).isTrue(); then(this.context.getEnvironment().acceptsProfiles("bar")).isTrue(); @@ -376,7 +376,7 @@ public class BootstrapConfigurationTests { @Test public void includeProfileFromBootstrapProperties() { this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE).sources(BareConfiguration.class) - .properties("spring.config.use-legacy-processing=true", "spring.cloud.bootstrap.name=local").run(); + .properties("spring.cloud.bootstrap.enabled=true", "spring.cloud.bootstrap.name=local").run(); then(this.context.getEnvironment().acceptsProfiles("local")).isTrue(); then(this.context.getEnvironment().getProperty("added")).isEqualTo("Hello added!"); } @@ -384,8 +384,7 @@ public class BootstrapConfigurationTests { @Test public void nonEnumerablePropertySourceWorks() { this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE).sources(BareConfiguration.class) - .properties("spring.config.use-legacy-processing=true", "spring.cloud.bootstrap.name=nonenumerable") - .run(); + .properties("spring.cloud.bootstrap.enabled=true", "spring.cloud.bootstrap.name=nonenumerable").run(); then(this.context.getEnvironment().getProperty("foo")).isEqualTo("bar"); } diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/config/BootstrapListenerHierarchyIntegrationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/config/BootstrapListenerHierarchyIntegrationTests.java index 9c2b0b0c..aa5a8085 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/config/BootstrapListenerHierarchyIntegrationTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/config/BootstrapListenerHierarchyIntegrationTests.java @@ -37,8 +37,7 @@ public class BootstrapListenerHierarchyIntegrationTests { @Test public void shouldAddInABootstrapContext() { ConfigurableApplicationContext context = new SpringApplicationBuilder() - .properties("spring.config.use-legacy-processing=true").sources(BasicConfiguration.class).web(NONE) - .run(); + .properties("spring.cloud.bootstrap.enabled=true").sources(BasicConfiguration.class).web(NONE).run(); then(context.getParent()).isNotNull(); } @@ -46,7 +45,7 @@ public class BootstrapListenerHierarchyIntegrationTests { @Test public void shouldAddInOneBootstrapForABasicParentChildHierarchy() { ConfigurableApplicationContext context = new SpringApplicationBuilder() - .properties("spring.config.use-legacy-processing=true").sources(RootConfiguration.class).web(NONE) + .properties("spring.cloud.bootstrap.enabled=true").sources(RootConfiguration.class).web(NONE) .child(BasicConfiguration.class).web(NONE).run(); // Should be RootConfiguration based context @@ -65,7 +64,7 @@ public class BootstrapListenerHierarchyIntegrationTests { @Test public void shouldAddInOneBootstrapForSiblingsBasedHierarchy() { ConfigurableApplicationContext context = new SpringApplicationBuilder() - .properties("spring.config.use-legacy-processing=true").sources(RootConfiguration.class).web(NONE) + .properties("spring.cloud.bootstrap.enabled=true").sources(RootConfiguration.class).web(NONE) .child(BasicConfiguration.class).web(NONE).sibling(BasicConfiguration.class).web(NONE).run(); // Should be RootConfiguration based context diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/encrypt/EncryptionIntegrationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/encrypt/EncryptionIntegrationTests.java index 926f44ab..fd3635b4 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/encrypt/EncryptionIntegrationTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/bootstrap/encrypt/EncryptionIntegrationTests.java @@ -39,7 +39,7 @@ public class EncryptionIntegrationTests { @Test public void legacySymmetricPropertyValues() { ConfigurableApplicationContext context = new SpringApplicationBuilder(TestConfiguration.class) - .web(WebApplicationType.NONE).properties("spring.config.use-legacy-processing=true", "encrypt.key:pie", + .web(WebApplicationType.NONE).properties("spring.cloud.bootstrap.enabled=true", "encrypt.key:pie", "foo.password:{cipher}bf29452295df354e6153c5b31b03ef23c70e55fba24299aa85c63438f1c43c95") .run(); then(context.getEnvironment().getProperty("foo.password")).isEqualTo("test"); @@ -48,7 +48,7 @@ public class EncryptionIntegrationTests { @Test public void legacySymmetricConfigurationProperties() { ConfigurableApplicationContext context = new SpringApplicationBuilder(TestConfiguration.class) - .web(WebApplicationType.NONE).properties("spring.config.use-legacy-processing=true", "encrypt.key:pie", + .web(WebApplicationType.NONE).properties("spring.cloud.bootstrap.enabled=true", "encrypt.key:pie", "foo.password:{cipher}bf29452295df354e6153c5b31b03ef23c70e55fba24299aa85c63438f1c43c95") .run(); then(context.getBean(PasswordProperties.class).getPassword()).isEqualTo("test"); diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderIntegrationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderIntegrationTests.java index c1a4ffa5..a9b3c49c 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderIntegrationTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderIntegrationTests.java @@ -40,7 +40,7 @@ import org.springframework.test.context.ActiveProfiles; import static org.assertj.core.api.BDDAssertions.then; -@SpringBootTest(classes = TestConfiguration.class, properties = "spring.config.use-legacy-processing=true") +@SpringBootTest(classes = TestConfiguration.class, properties = "spring.cloud.bootstrap.enabled=true") @ActiveProfiles("config") public class ConfigurationPropertiesRebinderIntegrationTests { diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderRefreshScopeIntegrationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderRefreshScopeIntegrationTests.java index e6c8398b..6b7a918b 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderRefreshScopeIntegrationTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/context/properties/ConfigurationPropertiesRebinderRefreshScopeIntegrationTests.java @@ -37,7 +37,7 @@ import org.springframework.test.annotation.DirtiesContext; import static org.assertj.core.api.BDDAssertions.then; -@SpringBootTest(classes = TestConfiguration.class, properties = "spring.config.use-legacy-processing=true") +@SpringBootTest(classes = TestConfiguration.class, properties = "spring.cloud.bootstrap.enabled=true") public class ConfigurationPropertiesRebinderRefreshScopeIntegrationTests { @Autowired diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/context/refresh/ContextRefresherIntegrationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/context/refresh/ContextRefresherIntegrationTests.java index 0c00c3a4..44053693 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/context/refresh/ContextRefresherIntegrationTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/context/refresh/ContextRefresherIntegrationTests.java @@ -34,7 +34,7 @@ import org.springframework.test.annotation.DirtiesContext; import static org.assertj.core.api.BDDAssertions.then; @SpringBootTest(classes = TestConfiguration.class, - properties = { "spring.datasource.hikari.read-only=false", "spring.config.use-legacy-processing=true" }) + properties = { "spring.datasource.hikari.read-only=false", "spring.cloud.bootstrap.enabled=true" }) public class ContextRefresherIntegrationTests { @Autowired diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/context/refresh/ContextRefresherOrderingIntegrationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/context/refresh/ContextRefresherOrderingIntegrationTests.java index 7fde8ea8..41b741c3 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/context/refresh/ContextRefresherOrderingIntegrationTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/context/refresh/ContextRefresherOrderingIntegrationTests.java @@ -41,7 +41,7 @@ import org.springframework.test.annotation.DirtiesContext; import static java.util.Collections.singletonMap; import static org.assertj.core.api.Assertions.assertThat; -@SpringBootTest(properties = "spring.config.use-legacy-processing=true") +@SpringBootTest(properties = "spring.cloud.bootstrap.enabled=true") @DirtiesContext public class ContextRefresherOrderingIntegrationTests { diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/context/refresh/ContextRefresherTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/context/refresh/ContextRefresherTests.java index da69559b..03721db0 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/context/refresh/ContextRefresherTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/context/refresh/ContextRefresherTests.java @@ -62,8 +62,8 @@ public class ContextRefresherTests { @Disabled // FIXME: legacy config public void orderNewPropertiesConsistentWithNewContext() { try (ConfigurableApplicationContext context = SpringApplication.run(Empty.class, - "--spring.config.use-legacy-processing=true", "--spring.main.web-application-type=none", - "--debug=false", "--spring.main.bannerMode=OFF")) { + "--spring.cloud.bootstrap.enabled=true", "--spring.main.web-application-type=none", "--debug=false", + "--spring.main.bannerMode=OFF")) { context.getEnvironment().setActiveProfiles("refresh"); List names = names(context.getEnvironment().getPropertySources()); then(names).doesNotContain("applicationConfig: [classpath:/bootstrap-refresh.properties]"); @@ -82,8 +82,8 @@ public class ContextRefresherTests { // Use spring.cloud.bootstrap.name to switch off the defaults (which would pick up // a bootstrapProperties immediately try (ConfigurableApplicationContext context = SpringApplication.run(Empty.class, - "--spring.config.use-legacy-processing=true", "--spring.main.web-application-type=none", - "--debug=false", "--spring.main.bannerMode=OFF", "--spring.cloud.bootstrap.name=refresh")) { + "--spring.cloud.bootstrap.enabled=true", "--spring.main.web-application-type=none", "--debug=false", + "--spring.main.bannerMode=OFF", "--spring.cloud.bootstrap.name=refresh")) { List names = names(context.getEnvironment().getPropertySources()); System.err.println("***** " + context.getEnvironment().getPropertySources()); then(names).doesNotContain("bootstrapProperties"); @@ -103,8 +103,8 @@ public class ContextRefresherTests { // Use spring.cloud.bootstrap.name to switch off the defaults (which would pick up // a bootstrapProperties immediately try (ConfigurableApplicationContext context = SpringApplication.run(ContextRefresherTests.class, - "--spring.main.web-application-type=none", "--spring.config.use-legacy-processing=true", - "--debug=false", "--spring.main.bannerMode=OFF", "--spring.cloud.bootstrap.name=refresh")) { + "--spring.main.web-application-type=none", "--spring.cloud.bootstrap.enabled=true", "--debug=false", + "--spring.main.bannerMode=OFF", "--spring.cloud.bootstrap.name=refresh")) { LegacyContextRefresher refresher = new LegacyContextRefresher(context, this.scope); TestPropertyValues.of("spring.cloud.bootstrap.sources: " + "org.springframework.cloud.context.refresh.ContextRefresherTests.PropertySourceConfiguration") @@ -123,8 +123,8 @@ public class ContextRefresherTests { TestLoggingSystem system = (TestLoggingSystem) LoggingSystem.get(getClass().getClassLoader()); then(system.getCount()).isEqualTo(0); try (ConfigurableApplicationContext context = SpringApplication.run(Empty.class, - "--spring.config.use-legacy-processing=true", "--spring.main.web-application-type=none", - "--debug=false", "--spring.main.bannerMode=OFF", "--spring.cloud.bootstrap.name=refresh")) { + "--spring.cloud.bootstrap.enabled=true", "--spring.main.web-application-type=none", "--debug=false", + "--spring.main.bannerMode=OFF", "--spring.cloud.bootstrap.name=refresh")) { then(system.getCount()).isEqualTo(4); ContextRefresher refresher = new LegacyContextRefresher(context, this.scope); refresher.refresh(); @@ -138,9 +138,8 @@ public class ContextRefresherTests { TestBootstrapConfiguration.fooSightings = new ArrayList<>(); try (ConfigurableApplicationContext context = SpringApplication.run(ContextRefresherTests.class, - "--spring.main.web-application-type=none", "--spring.config.use-legacy-processing=true", - "--debug=false", "--spring.main.bannerMode=OFF", "--spring.cloud.bootstrap.name=refresh", - "--test.bootstrap.foo=bar")) { + "--spring.main.web-application-type=none", "--spring.cloud.bootstrap.enabled=true", "--debug=false", + "--spring.main.bannerMode=OFF", "--spring.cloud.bootstrap.name=refresh", "--test.bootstrap.foo=bar")) { context.getEnvironment().setActiveProfiles("refresh"); ContextRefresher refresher = new LegacyContextRefresher(context, this.scope); refresher.refresh(); @@ -162,7 +161,7 @@ public class ContextRefresherTests { @Test public void legacyContextRefresherCreated() { new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(RefreshAutoConfiguration.class)) - .withPropertyValues("spring.config.use-legacy-processing=true").run(context -> { + .withPropertyValues("spring.cloud.bootstrap.enabled=true").run(context -> { assertThat(context).hasSingleBean(LegacyContextRefresher.class); assertThat(context).hasSingleBean(ContextRefresher.class); }); diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/context/restart/RestartIntegrationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/context/restart/RestartIntegrationTests.java index c3eaa3fe..3e28fa14 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/context/restart/RestartIntegrationTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/context/restart/RestartIntegrationTests.java @@ -45,7 +45,7 @@ public class RestartIntegrationTests { public void testRestartTwice() throws Exception { this.context = SpringApplication.run(TestConfiguration.class, "--management.endpoint.restart.enabled=true", - "--server.port=0", "--spring.config.use-legacy-processing=true", + "--server.port=0", "--spring.cloud.bootstrap.enabled=true", "--management.endpoints.web.exposure.include=restart", "--spring.liveBeansView.mbeanDomain=livebeans"); RestartEndpoint endpoint = this.context.getBean(RestartEndpoint.class); diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/context/scope/refresh/RefreshScopeSerializationTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/context/scope/refresh/RefreshScopeSerializationTests.java index df903e29..88f7e896 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/context/scope/refresh/RefreshScopeSerializationTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/context/scope/refresh/RefreshScopeSerializationTests.java @@ -35,7 +35,7 @@ public class RefreshScopeSerializationTests { @Test public void defaultApplicationContextId() throws Exception { ConfigurableApplicationContext context = new SpringApplicationBuilder(TestConfiguration.class) - .properties("spring.config.use-legacy-processing=true").web(WebApplicationType.NONE).run(); + .properties("spring.cloud.bootstrap.enabled=true").web(WebApplicationType.NONE).run(); then(context.getId()).isEqualTo("application-1"); } diff --git a/spring-cloud-context/src/test/java/org/springframework/cloud/endpoint/RefreshEndpointTests.java b/spring-cloud-context/src/test/java/org/springframework/cloud/endpoint/RefreshEndpointTests.java index ef57e923..05db3418 100644 --- a/spring-cloud-context/src/test/java/org/springframework/cloud/endpoint/RefreshEndpointTests.java +++ b/spring-cloud-context/src/test/java/org/springframework/cloud/endpoint/RefreshEndpointTests.java @@ -72,7 +72,7 @@ public class RefreshEndpointTests { @Disabled // FIXME: legacy public void keysComputedWhenAdded() throws Exception { this.context = new SpringApplicationBuilder(Empty.class).web(WebApplicationType.NONE).bannerMode(Mode.OFF) - .properties("spring.config.use-legacy-processing=true", "spring.cloud.bootstrap.name:none").run(); + .properties("spring.cloud.bootstrap.enabled=true", "spring.cloud.bootstrap.name:none").run(); RefreshScope scope = new RefreshScope(); scope.setApplicationContext(this.context); this.context.getEnvironment().setActiveProfiles("local"); @@ -86,7 +86,7 @@ public class RefreshEndpointTests { @Disabled // FIXME: legacy public void keysComputedWhenOveridden() throws Exception { this.context = new SpringApplicationBuilder(Empty.class).web(WebApplicationType.NONE).bannerMode(Mode.OFF) - .properties("spring.config.use-legacy-processing=true", "spring.cloud.bootstrap.name:none").run(); + .properties("spring.cloud.bootstrap.enabled=true", "spring.cloud.bootstrap.name:none").run(); RefreshScope scope = new RefreshScope(); scope.setApplicationContext(this.context); this.context.getEnvironment().setActiveProfiles("override"); @@ -99,7 +99,7 @@ public class RefreshEndpointTests { @Test public void keysComputedWhenChangesInExternalProperties() throws Exception { this.context = new SpringApplicationBuilder(Empty.class).web(WebApplicationType.NONE).bannerMode(Mode.OFF) - .properties("spring.cloud.bootstrap.name:none", "spring.config.use-legacy-processing=true").run(); + .properties("spring.cloud.bootstrap.name:none", "spring.cloud.bootstrap.enabled=true").run(); RefreshScope scope = new RefreshScope(); scope.setApplicationContext(this.context); TestPropertyValues.of("spring.cloud.bootstrap.sources=" + ExternalPropertySourceLocator.class.getName())