/*
 * Copyright 2012-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.context.config;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;

A single element that may directly or indirectly contribute configuration data to the Environment. There are several different kinds of contributor, all are immutable and will be replaced with new versions as imports are processed.

Contributors may provide a set of imports that should be processed and ultimately turned into children. There are two distinct import phases:

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