/*
* 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:
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();
}
Create an new ConfigDataEnvironmentContributor
with bound ConfigDataProperties
. 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;
}
}
}