001/* 002 * Copyright 2012-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.boot.context.config; 018 019import java.io.IOException; 020import java.util.ArrayList; 021import java.util.Arrays; 022import java.util.Collections; 023import java.util.Deque; 024import java.util.HashMap; 025import java.util.HashSet; 026import java.util.LinkedHashMap; 027import java.util.LinkedHashSet; 028import java.util.LinkedList; 029import java.util.List; 030import java.util.Map; 031import java.util.Set; 032import java.util.function.BiConsumer; 033import java.util.stream.Collectors; 034 035import org.apache.commons.logging.Log; 036 037import org.springframework.beans.BeansException; 038import org.springframework.beans.factory.config.BeanFactoryPostProcessor; 039import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 040import org.springframework.boot.SpringApplication; 041import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; 042import org.springframework.boot.context.event.ApplicationPreparedEvent; 043import org.springframework.boot.context.properties.bind.Bindable; 044import org.springframework.boot.context.properties.bind.Binder; 045import org.springframework.boot.context.properties.bind.PropertySourcesPlaceholdersResolver; 046import org.springframework.boot.context.properties.source.ConfigurationPropertySources; 047import org.springframework.boot.env.EnvironmentPostProcessor; 048import org.springframework.boot.env.PropertySourceLoader; 049import org.springframework.boot.env.RandomValuePropertySource; 050import org.springframework.boot.logging.DeferredLog; 051import org.springframework.context.ApplicationEvent; 052import org.springframework.context.ConfigurableApplicationContext; 053import org.springframework.context.annotation.ConfigurationClassPostProcessor; 054import org.springframework.context.event.SmartApplicationListener; 055import org.springframework.core.Ordered; 056import org.springframework.core.annotation.AnnotationAwareOrderComparator; 057import org.springframework.core.env.ConfigurableEnvironment; 058import org.springframework.core.env.Environment; 059import org.springframework.core.env.MutablePropertySources; 060import org.springframework.core.env.Profiles; 061import org.springframework.core.env.PropertySource; 062import org.springframework.core.io.DefaultResourceLoader; 063import org.springframework.core.io.Resource; 064import org.springframework.core.io.ResourceLoader; 065import org.springframework.core.io.support.SpringFactoriesLoader; 066import org.springframework.util.Assert; 067import org.springframework.util.CollectionUtils; 068import org.springframework.util.ObjectUtils; 069import org.springframework.util.ResourceUtils; 070import org.springframework.util.StringUtils; 071 072/** 073 * {@link EnvironmentPostProcessor} that configures the context environment by loading 074 * properties from well known file locations. By default properties will be loaded from 075 * 'application.properties' and/or 'application.yml' files in the following locations: 076 * <ul> 077 * <li>classpath:</li> 078 * <li>file:./</li> 079 * <li>classpath:config/</li> 080 * <li>file:./config/:</li> 081 * </ul> 082 * <p> 083 * Alternative search locations and names can be specified using 084 * {@link #setSearchLocations(String)} and {@link #setSearchNames(String)}. 085 * <p> 086 * Additional files will also be loaded based on active profiles. For example if a 'web' 087 * profile is active 'application-web.properties' and 'application-web.yml' will be 088 * considered. 089 * <p> 090 * The 'spring.config.name' property can be used to specify an alternative name to load 091 * and the 'spring.config.location' property can be used to specify alternative search 092 * locations or specific files. 093 * <p> 094 * 095 * @author Dave Syer 096 * @author Phillip Webb 097 * @author Stephane Nicoll 098 * @author Andy Wilkinson 099 * @author EddĂș MelĂ©ndez 100 * @author Madhura Bhave 101 */ 102public class ConfigFileApplicationListener 103 implements EnvironmentPostProcessor, SmartApplicationListener, Ordered { 104 105 private static final String DEFAULT_PROPERTIES = "defaultProperties"; 106 107 // Note the order is from least to most specific (last one wins) 108 private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/"; 109 110 private static final String DEFAULT_NAMES = "application"; 111 112 private static final Set<String> NO_SEARCH_NAMES = Collections.singleton(null); 113 114 private static final Bindable<String[]> STRING_ARRAY = Bindable.of(String[].class); 115 116 /** 117 * The "active profiles" property name. 118 */ 119 public static final String ACTIVE_PROFILES_PROPERTY = "spring.profiles.active"; 120 121 /** 122 * The "includes profiles" property name. 123 */ 124 public static final String INCLUDE_PROFILES_PROPERTY = "spring.profiles.include"; 125 126 /** 127 * The "config name" property name. 128 */ 129 public static final String CONFIG_NAME_PROPERTY = "spring.config.name"; 130 131 /** 132 * The "config location" property name. 133 */ 134 public static final String CONFIG_LOCATION_PROPERTY = "spring.config.location"; 135 136 /** 137 * The "config additional location" property name. 138 */ 139 public static final String CONFIG_ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location"; 140 141 /** 142 * The default order for the processor. 143 */ 144 public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 10; 145 146 private final DeferredLog logger = new DeferredLog(); 147 148 private String searchLocations; 149 150 private String names; 151 152 private int order = DEFAULT_ORDER; 153 154 @Override 155 public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) { 156 return ApplicationEnvironmentPreparedEvent.class.isAssignableFrom(eventType) 157 || ApplicationPreparedEvent.class.isAssignableFrom(eventType); 158 } 159 160 @Override 161 public void onApplicationEvent(ApplicationEvent event) { 162 if (event instanceof ApplicationEnvironmentPreparedEvent) { 163 onApplicationEnvironmentPreparedEvent( 164 (ApplicationEnvironmentPreparedEvent) event); 165 } 166 if (event instanceof ApplicationPreparedEvent) { 167 onApplicationPreparedEvent(event); 168 } 169 } 170 171 private void onApplicationEnvironmentPreparedEvent( 172 ApplicationEnvironmentPreparedEvent event) { 173 List<EnvironmentPostProcessor> postProcessors = loadPostProcessors(); 174 postProcessors.add(this); 175 AnnotationAwareOrderComparator.sort(postProcessors); 176 for (EnvironmentPostProcessor postProcessor : postProcessors) { 177 postProcessor.postProcessEnvironment(event.getEnvironment(), 178 event.getSpringApplication()); 179 } 180 } 181 182 List<EnvironmentPostProcessor> loadPostProcessors() { 183 return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, 184 getClass().getClassLoader()); 185 } 186 187 @Override 188 public void postProcessEnvironment(ConfigurableEnvironment environment, 189 SpringApplication application) { 190 addPropertySources(environment, application.getResourceLoader()); 191 } 192 193 private void onApplicationPreparedEvent(ApplicationEvent event) { 194 this.logger.switchTo(ConfigFileApplicationListener.class); 195 addPostProcessors(((ApplicationPreparedEvent) event).getApplicationContext()); 196 } 197 198 /** 199 * Add config file property sources to the specified environment. 200 * @param environment the environment to add source to 201 * @param resourceLoader the resource loader 202 * @see #addPostProcessors(ConfigurableApplicationContext) 203 */ 204 protected void addPropertySources(ConfigurableEnvironment environment, 205 ResourceLoader resourceLoader) { 206 RandomValuePropertySource.addToEnvironment(environment); 207 new Loader(environment, resourceLoader).load(); 208 } 209 210 /** 211 * Add appropriate post-processors to post-configure the property-sources. 212 * @param context the context to configure 213 */ 214 protected void addPostProcessors(ConfigurableApplicationContext context) { 215 context.addBeanFactoryPostProcessor( 216 new PropertySourceOrderingPostProcessor(context)); 217 } 218 219 public void setOrder(int order) { 220 this.order = order; 221 } 222 223 @Override 224 public int getOrder() { 225 return this.order; 226 } 227 228 /** 229 * Set the search locations that will be considered as a comma-separated list. Each 230 * search location should be a directory path (ending in "/") and it will be prefixed 231 * by the file names constructed from {@link #setSearchNames(String) search names} and 232 * profiles (if any) plus file extensions supported by the properties loaders. 233 * Locations are considered in the order specified, with later items taking precedence 234 * (like a map merge). 235 * @param locations the search locations 236 */ 237 public void setSearchLocations(String locations) { 238 Assert.hasLength(locations, "Locations must not be empty"); 239 this.searchLocations = locations; 240 } 241 242 /** 243 * Sets the names of the files that should be loaded (excluding file extension) as a 244 * comma-separated list. 245 * @param names the names to load 246 */ 247 public void setSearchNames(String names) { 248 Assert.hasLength(names, "Names must not be empty"); 249 this.names = names; 250 } 251 252 /** 253 * {@link BeanFactoryPostProcessor} to re-order our property sources below any 254 * {@code @PropertySource} items added by the {@link ConfigurationClassPostProcessor}. 255 */ 256 private class PropertySourceOrderingPostProcessor 257 implements BeanFactoryPostProcessor, Ordered { 258 259 private ConfigurableApplicationContext context; 260 261 PropertySourceOrderingPostProcessor(ConfigurableApplicationContext context) { 262 this.context = context; 263 } 264 265 @Override 266 public int getOrder() { 267 return Ordered.HIGHEST_PRECEDENCE; 268 } 269 270 @Override 271 public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) 272 throws BeansException { 273 reorderSources(this.context.getEnvironment()); 274 } 275 276 private void reorderSources(ConfigurableEnvironment environment) { 277 PropertySource<?> defaultProperties = environment.getPropertySources() 278 .remove(DEFAULT_PROPERTIES); 279 if (defaultProperties != null) { 280 environment.getPropertySources().addLast(defaultProperties); 281 } 282 } 283 284 } 285 286 /** 287 * Loads candidate property sources and configures the active profiles. 288 */ 289 private class Loader { 290 291 private final Log logger = ConfigFileApplicationListener.this.logger; 292 293 private final ConfigurableEnvironment environment; 294 295 private final PropertySourcesPlaceholdersResolver placeholdersResolver; 296 297 private final ResourceLoader resourceLoader; 298 299 private final List<PropertySourceLoader> propertySourceLoaders; 300 301 private Deque<Profile> profiles; 302 303 private List<Profile> processedProfiles; 304 305 private boolean activatedProfiles; 306 307 private Map<Profile, MutablePropertySources> loaded; 308 309 private Map<DocumentsCacheKey, List<Document>> loadDocumentsCache = new HashMap<>(); 310 311 Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { 312 this.environment = environment; 313 this.placeholdersResolver = new PropertySourcesPlaceholdersResolver( 314 this.environment); 315 this.resourceLoader = (resourceLoader != null) ? resourceLoader 316 : new DefaultResourceLoader(); 317 this.propertySourceLoaders = SpringFactoriesLoader.loadFactories( 318 PropertySourceLoader.class, getClass().getClassLoader()); 319 } 320 321 public void load() { 322 this.profiles = new LinkedList<>(); 323 this.processedProfiles = new LinkedList<>(); 324 this.activatedProfiles = false; 325 this.loaded = new LinkedHashMap<>(); 326 initializeProfiles(); 327 while (!this.profiles.isEmpty()) { 328 Profile profile = this.profiles.poll(); 329 if (profile != null && !profile.isDefaultProfile()) { 330 addProfileToEnvironment(profile.getName()); 331 } 332 load(profile, this::getPositiveProfileFilter, 333 addToLoaded(MutablePropertySources::addLast, false)); 334 this.processedProfiles.add(profile); 335 } 336 resetEnvironmentProfiles(this.processedProfiles); 337 load(null, this::getNegativeProfileFilter, 338 addToLoaded(MutablePropertySources::addFirst, true)); 339 addLoadedPropertySources(); 340 } 341 342 /** 343 * Initialize profile information from both the {@link Environment} active 344 * profiles and any {@code spring.profiles.active}/{@code spring.profiles.include} 345 * properties that are already set. 346 */ 347 private void initializeProfiles() { 348 // The default profile for these purposes is represented as null. We add it 349 // first so that it is processed first and has lowest priority. 350 this.profiles.add(null); 351 Set<Profile> activatedViaProperty = getProfilesActivatedViaProperty(); 352 this.profiles.addAll(getOtherActiveProfiles(activatedViaProperty)); 353 // Any pre-existing active profiles set via property sources (e.g. 354 // System properties) take precedence over those added in config files. 355 addActiveProfiles(activatedViaProperty); 356 if (this.profiles.size() == 1) { // only has null profile 357 for (String defaultProfileName : this.environment.getDefaultProfiles()) { 358 Profile defaultProfile = new Profile(defaultProfileName, true); 359 this.profiles.add(defaultProfile); 360 } 361 } 362 } 363 364 private Set<Profile> getProfilesActivatedViaProperty() { 365 if (!this.environment.containsProperty(ACTIVE_PROFILES_PROPERTY) 366 && !this.environment.containsProperty(INCLUDE_PROFILES_PROPERTY)) { 367 return Collections.emptySet(); 368 } 369 Binder binder = Binder.get(this.environment); 370 Set<Profile> activeProfiles = new LinkedHashSet<>(); 371 activeProfiles.addAll(getProfiles(binder, INCLUDE_PROFILES_PROPERTY)); 372 activeProfiles.addAll(getProfiles(binder, ACTIVE_PROFILES_PROPERTY)); 373 return activeProfiles; 374 } 375 376 private List<Profile> getOtherActiveProfiles(Set<Profile> activatedViaProperty) { 377 return Arrays.stream(this.environment.getActiveProfiles()).map(Profile::new) 378 .filter((profile) -> !activatedViaProperty.contains(profile)) 379 .collect(Collectors.toList()); 380 } 381 382 void addActiveProfiles(Set<Profile> profiles) { 383 if (profiles.isEmpty()) { 384 return; 385 } 386 if (this.activatedProfiles) { 387 if (this.logger.isDebugEnabled()) { 388 this.logger.debug("Profiles already activated, '" + profiles 389 + "' will not be applied"); 390 } 391 return; 392 } 393 this.profiles.addAll(profiles); 394 if (this.logger.isDebugEnabled()) { 395 this.logger.debug("Activated activeProfiles " 396 + StringUtils.collectionToCommaDelimitedString(profiles)); 397 } 398 this.activatedProfiles = true; 399 removeUnprocessedDefaultProfiles(); 400 } 401 402 private void removeUnprocessedDefaultProfiles() { 403 this.profiles.removeIf( 404 (profile) -> (profile != null && profile.isDefaultProfile())); 405 } 406 407 private DocumentFilter getPositiveProfileFilter(Profile profile) { 408 return (Document document) -> { 409 if (profile == null) { 410 return ObjectUtils.isEmpty(document.getProfiles()); 411 } 412 return ObjectUtils.containsElement(document.getProfiles(), 413 profile.getName()) 414 && this.environment 415 .acceptsProfiles(Profiles.of(document.getProfiles())); 416 }; 417 } 418 419 private DocumentFilter getNegativeProfileFilter(Profile profile) { 420 return (Document document) -> (profile == null 421 && !ObjectUtils.isEmpty(document.getProfiles()) && this.environment 422 .acceptsProfiles(Profiles.of(document.getProfiles()))); 423 } 424 425 private DocumentConsumer addToLoaded( 426 BiConsumer<MutablePropertySources, PropertySource<?>> addMethod, 427 boolean checkForExisting) { 428 return (profile, document) -> { 429 if (checkForExisting) { 430 for (MutablePropertySources merged : this.loaded.values()) { 431 if (merged.contains(document.getPropertySource().getName())) { 432 return; 433 } 434 } 435 } 436 MutablePropertySources merged = this.loaded.computeIfAbsent(profile, 437 (k) -> new MutablePropertySources()); 438 addMethod.accept(merged, document.getPropertySource()); 439 }; 440 } 441 442 private void load(Profile profile, DocumentFilterFactory filterFactory, 443 DocumentConsumer consumer) { 444 getSearchLocations().forEach((location) -> { 445 boolean isFolder = location.endsWith("/"); 446 Set<String> names = isFolder ? getSearchNames() : NO_SEARCH_NAMES; 447 names.forEach( 448 (name) -> load(location, name, profile, filterFactory, consumer)); 449 }); 450 } 451 452 private void load(String location, String name, Profile profile, 453 DocumentFilterFactory filterFactory, DocumentConsumer consumer) { 454 if (!StringUtils.hasText(name)) { 455 for (PropertySourceLoader loader : this.propertySourceLoaders) { 456 if (canLoadFileExtension(loader, location)) { 457 load(loader, location, profile, 458 filterFactory.getDocumentFilter(profile), consumer); 459 return; 460 } 461 } 462 } 463 Set<String> processed = new HashSet<>(); 464 for (PropertySourceLoader loader : this.propertySourceLoaders) { 465 for (String fileExtension : loader.getFileExtensions()) { 466 if (processed.add(fileExtension)) { 467 loadForFileExtension(loader, location + name, "." + fileExtension, 468 profile, filterFactory, consumer); 469 } 470 } 471 } 472 } 473 474 private boolean canLoadFileExtension(PropertySourceLoader loader, String name) { 475 return Arrays.stream(loader.getFileExtensions()) 476 .anyMatch((fileExtension) -> StringUtils.endsWithIgnoreCase(name, 477 fileExtension)); 478 } 479 480 private void loadForFileExtension(PropertySourceLoader loader, String prefix, 481 String fileExtension, Profile profile, 482 DocumentFilterFactory filterFactory, DocumentConsumer consumer) { 483 DocumentFilter defaultFilter = filterFactory.getDocumentFilter(null); 484 DocumentFilter profileFilter = filterFactory.getDocumentFilter(profile); 485 if (profile != null) { 486 // Try profile-specific file & profile section in profile file (gh-340) 487 String profileSpecificFile = prefix + "-" + profile + fileExtension; 488 load(loader, profileSpecificFile, profile, defaultFilter, consumer); 489 load(loader, profileSpecificFile, profile, profileFilter, consumer); 490 // Try profile specific sections in files we've already processed 491 for (Profile processedProfile : this.processedProfiles) { 492 if (processedProfile != null) { 493 String previouslyLoaded = prefix + "-" + processedProfile 494 + fileExtension; 495 load(loader, previouslyLoaded, profile, profileFilter, consumer); 496 } 497 } 498 } 499 // Also try the profile-specific section (if any) of the normal file 500 load(loader, prefix + fileExtension, profile, profileFilter, consumer); 501 } 502 503 private void load(PropertySourceLoader loader, String location, Profile profile, 504 DocumentFilter filter, DocumentConsumer consumer) { 505 try { 506 Resource resource = this.resourceLoader.getResource(location); 507 if (resource == null || !resource.exists()) { 508 if (this.logger.isTraceEnabled()) { 509 StringBuilder description = getDescription( 510 "Skipped missing config ", location, resource, profile); 511 this.logger.trace(description); 512 } 513 return; 514 } 515 if (!StringUtils.hasText( 516 StringUtils.getFilenameExtension(resource.getFilename()))) { 517 if (this.logger.isTraceEnabled()) { 518 StringBuilder description = getDescription( 519 "Skipped empty config extension ", location, resource, 520 profile); 521 this.logger.trace(description); 522 } 523 return; 524 } 525 String name = "applicationConfig: [" + location + "]"; 526 List<Document> documents = loadDocuments(loader, name, resource); 527 if (CollectionUtils.isEmpty(documents)) { 528 if (this.logger.isTraceEnabled()) { 529 StringBuilder description = getDescription( 530 "Skipped unloaded config ", location, resource, profile); 531 this.logger.trace(description); 532 } 533 return; 534 } 535 List<Document> loaded = new ArrayList<>(); 536 for (Document document : documents) { 537 if (filter.match(document)) { 538 addActiveProfiles(document.getActiveProfiles()); 539 addIncludedProfiles(document.getIncludeProfiles()); 540 loaded.add(document); 541 } 542 } 543 Collections.reverse(loaded); 544 if (!loaded.isEmpty()) { 545 loaded.forEach((document) -> consumer.accept(profile, document)); 546 if (this.logger.isDebugEnabled()) { 547 StringBuilder description = getDescription("Loaded config file ", 548 location, resource, profile); 549 this.logger.debug(description); 550 } 551 } 552 } 553 catch (Exception ex) { 554 throw new IllegalStateException("Failed to load property " 555 + "source from location '" + location + "'", ex); 556 } 557 } 558 559 private void addIncludedProfiles(Set<Profile> includeProfiles) { 560 LinkedList<Profile> existingProfiles = new LinkedList<>(this.profiles); 561 this.profiles.clear(); 562 this.profiles.addAll(includeProfiles); 563 this.profiles.removeAll(this.processedProfiles); 564 this.profiles.addAll(existingProfiles); 565 } 566 567 private List<Document> loadDocuments(PropertySourceLoader loader, String name, 568 Resource resource) throws IOException { 569 DocumentsCacheKey cacheKey = new DocumentsCacheKey(loader, resource); 570 List<Document> documents = this.loadDocumentsCache.get(cacheKey); 571 if (documents == null) { 572 List<PropertySource<?>> loaded = loader.load(name, resource); 573 documents = asDocuments(loaded); 574 this.loadDocumentsCache.put(cacheKey, documents); 575 } 576 return documents; 577 } 578 579 private List<Document> asDocuments(List<PropertySource<?>> loaded) { 580 if (loaded == null) { 581 return Collections.emptyList(); 582 } 583 return loaded.stream().map((propertySource) -> { 584 Binder binder = new Binder( 585 ConfigurationPropertySources.from(propertySource), 586 this.placeholdersResolver); 587 return new Document(propertySource, 588 binder.bind("spring.profiles", STRING_ARRAY).orElse(null), 589 getProfiles(binder, ACTIVE_PROFILES_PROPERTY), 590 getProfiles(binder, INCLUDE_PROFILES_PROPERTY)); 591 }).collect(Collectors.toList()); 592 } 593 594 private StringBuilder getDescription(String prefix, String location, 595 Resource resource, Profile profile) { 596 StringBuilder result = new StringBuilder(prefix); 597 try { 598 if (resource != null) { 599 String uri = resource.getURI().toASCIIString(); 600 result.append("'"); 601 result.append(uri); 602 result.append("' ("); 603 result.append(location); 604 result.append(")"); 605 } 606 } 607 catch (IOException ex) { 608 result.append(location); 609 } 610 if (profile != null) { 611 result.append(" for profile "); 612 result.append(profile); 613 } 614 return result; 615 } 616 617 private Set<Profile> getProfiles(Binder binder, String name) { 618 return binder.bind(name, STRING_ARRAY).map(this::asProfileSet) 619 .orElse(Collections.emptySet()); 620 } 621 622 private Set<Profile> asProfileSet(String[] profileNames) { 623 List<Profile> profiles = new ArrayList<>(); 624 for (String profileName : profileNames) { 625 profiles.add(new Profile(profileName)); 626 } 627 return new LinkedHashSet<>(profiles); 628 } 629 630 private void addProfileToEnvironment(String profile) { 631 for (String activeProfile : this.environment.getActiveProfiles()) { 632 if (activeProfile.equals(profile)) { 633 return; 634 } 635 } 636 this.environment.addActiveProfile(profile); 637 } 638 639 private Set<String> getSearchLocations() { 640 if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) { 641 return getSearchLocations(CONFIG_LOCATION_PROPERTY); 642 } 643 Set<String> locations = getSearchLocations( 644 CONFIG_ADDITIONAL_LOCATION_PROPERTY); 645 locations.addAll( 646 asResolvedSet(ConfigFileApplicationListener.this.searchLocations, 647 DEFAULT_SEARCH_LOCATIONS)); 648 return locations; 649 } 650 651 private Set<String> getSearchLocations(String propertyName) { 652 Set<String> locations = new LinkedHashSet<>(); 653 if (this.environment.containsProperty(propertyName)) { 654 for (String path : asResolvedSet( 655 this.environment.getProperty(propertyName), null)) { 656 if (!path.contains("$")) { 657 path = StringUtils.cleanPath(path); 658 if (!ResourceUtils.isUrl(path)) { 659 path = ResourceUtils.FILE_URL_PREFIX + path; 660 } 661 } 662 locations.add(path); 663 } 664 } 665 return locations; 666 } 667 668 private Set<String> getSearchNames() { 669 if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) { 670 String property = this.environment.getProperty(CONFIG_NAME_PROPERTY); 671 return asResolvedSet(property, null); 672 } 673 return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES); 674 } 675 676 private Set<String> asResolvedSet(String value, String fallback) { 677 List<String> list = Arrays.asList(StringUtils.trimArrayElements( 678 StringUtils.commaDelimitedListToStringArray((value != null) 679 ? this.environment.resolvePlaceholders(value) : fallback))); 680 Collections.reverse(list); 681 return new LinkedHashSet<>(list); 682 } 683 684 /** 685 * This ensures that the order of active profiles in the {@link Environment} 686 * matches the order in which the profiles were processed. 687 * @param processedProfiles the processed profiles 688 */ 689 private void resetEnvironmentProfiles(List<Profile> processedProfiles) { 690 String[] names = processedProfiles.stream() 691 .filter((profile) -> profile != null && !profile.isDefaultProfile()) 692 .map(Profile::getName).toArray(String[]::new); 693 this.environment.setActiveProfiles(names); 694 } 695 696 private void addLoadedPropertySources() { 697 MutablePropertySources destination = this.environment.getPropertySources(); 698 List<MutablePropertySources> loaded = new ArrayList<>(this.loaded.values()); 699 Collections.reverse(loaded); 700 String lastAdded = null; 701 Set<String> added = new HashSet<>(); 702 for (MutablePropertySources sources : loaded) { 703 for (PropertySource<?> source : sources) { 704 if (added.add(source.getName())) { 705 addLoadedPropertySource(destination, lastAdded, source); 706 lastAdded = source.getName(); 707 } 708 } 709 } 710 } 711 712 private void addLoadedPropertySource(MutablePropertySources destination, 713 String lastAdded, PropertySource<?> source) { 714 if (lastAdded == null) { 715 if (destination.contains(DEFAULT_PROPERTIES)) { 716 destination.addBefore(DEFAULT_PROPERTIES, source); 717 } 718 else { 719 destination.addLast(source); 720 } 721 } 722 else { 723 destination.addAfter(lastAdded, source); 724 } 725 } 726 727 } 728 729 /** 730 * A Spring Profile that can be loaded. 731 */ 732 private static class Profile { 733 734 private final String name; 735 736 private final boolean defaultProfile; 737 738 Profile(String name) { 739 this(name, false); 740 } 741 742 Profile(String name, boolean defaultProfile) { 743 Assert.notNull(name, "Name must not be null"); 744 this.name = name; 745 this.defaultProfile = defaultProfile; 746 } 747 748 public String getName() { 749 return this.name; 750 } 751 752 public boolean isDefaultProfile() { 753 return this.defaultProfile; 754 } 755 756 @Override 757 public boolean equals(Object obj) { 758 if (obj == this) { 759 return true; 760 } 761 if (obj == null || obj.getClass() != getClass()) { 762 return false; 763 } 764 return ((Profile) obj).name.equals(this.name); 765 } 766 767 @Override 768 public int hashCode() { 769 return this.name.hashCode(); 770 } 771 772 @Override 773 public String toString() { 774 return this.name; 775 } 776 777 } 778 779 /** 780 * Cache key used to save loading the same document multiple times. 781 */ 782 private static class DocumentsCacheKey { 783 784 private final PropertySourceLoader loader; 785 786 private final Resource resource; 787 788 DocumentsCacheKey(PropertySourceLoader loader, Resource resource) { 789 this.loader = loader; 790 this.resource = resource; 791 } 792 793 @Override 794 public boolean equals(Object obj) { 795 if (this == obj) { 796 return true; 797 } 798 if (obj == null || getClass() != obj.getClass()) { 799 return false; 800 } 801 DocumentsCacheKey other = (DocumentsCacheKey) obj; 802 return this.loader.equals(other.loader) 803 && this.resource.equals(other.resource); 804 } 805 806 @Override 807 public int hashCode() { 808 return this.loader.hashCode() * 31 + this.resource.hashCode(); 809 } 810 811 } 812 813 /** 814 * A single document loaded by a {@link PropertySourceLoader}. 815 */ 816 private static class Document { 817 818 private final PropertySource<?> propertySource; 819 820 private String[] profiles; 821 822 private final Set<Profile> activeProfiles; 823 824 private final Set<Profile> includeProfiles; 825 826 Document(PropertySource<?> propertySource, String[] profiles, 827 Set<Profile> activeProfiles, Set<Profile> includeProfiles) { 828 this.propertySource = propertySource; 829 this.profiles = profiles; 830 this.activeProfiles = activeProfiles; 831 this.includeProfiles = includeProfiles; 832 } 833 834 public PropertySource<?> getPropertySource() { 835 return this.propertySource; 836 } 837 838 public String[] getProfiles() { 839 return this.profiles; 840 } 841 842 public Set<Profile> getActiveProfiles() { 843 return this.activeProfiles; 844 } 845 846 public Set<Profile> getIncludeProfiles() { 847 return this.includeProfiles; 848 } 849 850 @Override 851 public String toString() { 852 return this.propertySource.toString(); 853 } 854 855 } 856 857 /** 858 * Factory used to create a {@link DocumentFilter}. 859 */ 860 @FunctionalInterface 861 private interface DocumentFilterFactory { 862 863 /** 864 * Create a filter for the given profile. 865 * @param profile the profile or {@code null} 866 * @return the filter 867 */ 868 DocumentFilter getDocumentFilter(Profile profile); 869 870 } 871 872 /** 873 * Filter used to restrict when a {@link Document} is loaded. 874 */ 875 @FunctionalInterface 876 private interface DocumentFilter { 877 878 boolean match(Document document); 879 880 } 881 882 /** 883 * Consumer used to handle a loaded {@link Document}. 884 */ 885 @FunctionalInterface 886 private interface DocumentConsumer { 887 888 void accept(Profile profile, Document document); 889 890 } 891 892}