package org.springframework.boot.context.config;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.logging.Log;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.ImportPhase;
import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.Kind;
import org.springframework.boot.context.properties.bind.BindContext;
import org.springframework.boot.context.properties.bind.BindHandler;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.bind.PlaceholdersResolver;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.log.LogMessage;
import org.springframework.util.ObjectUtils;
class ConfigDataEnvironmentContributors implements Iterable<ConfigDataEnvironmentContributor> {
private final Log logger;
private final ConfigDataEnvironmentContributor root;
private final ConfigurableBootstrapContext bootstrapContext;
ConfigDataEnvironmentContributors(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
List<ConfigDataEnvironmentContributor> contributors) {
this.logger = logFactory.getLog(getClass());
this.bootstrapContext = bootstrapContext;
this.root = ConfigDataEnvironmentContributor.of(contributors);
}
private ConfigDataEnvironmentContributors(Log logger, ConfigurableBootstrapContext bootstrapContext,
ConfigDataEnvironmentContributor root) {
this.logger = logger;
this.bootstrapContext = bootstrapContext;
this.root = root;
}
ConfigDataEnvironmentContributors withProcessedImports(ConfigDataImporter importer,
ConfigDataActivationContext activationContext) {
ImportPhase importPhase = ImportPhase.get(activationContext);
this.logger.trace(LogMessage.format("Processing imports for phase %s. %s", importPhase,
(activationContext != null) ? activationContext : "no activation context"));
ConfigDataEnvironmentContributors result = this;
int processed = 0;
while (true) {
ConfigDataEnvironmentContributor contributor = getNextToProcess(result, activationContext, importPhase);
if (contributor == null) {
this.logger.trace(LogMessage.format("Processed imports for of %d contributors", processed));
return result;
}
if (contributor.getKind() == Kind.UNBOUND_IMPORT) {
Iterable<ConfigurationPropertySource> sources = Collections
.singleton(contributor.getConfigurationPropertySource());
PlaceholdersResolver placeholdersResolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(
result, activationContext, true);
Binder binder = new Binder(sources, placeholdersResolver, null, null, null);
ConfigDataEnvironmentContributor bound = contributor.withBoundProperties(binder);
result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext,
result.getRoot().withReplacement(contributor, bound));
continue;
}
ConfigDataLocationResolverContext locationResolverContext = new ContributorConfigDataLocationResolverContext(
result, contributor, activationContext);
ConfigDataLoaderContext loaderContext = new ContributorDataLoaderContext(this);
List<ConfigDataLocation> imports = contributor.getImports();
this.logger.trace(LogMessage.format("Processing imports %s", imports));
Map<ConfigDataResolutionResult, ConfigData> imported = importer.resolveAndLoad(activationContext,
locationResolverContext, loaderContext, imports);
this.logger.trace(LogMessage.of(() -> getImportedMessage(imported.keySet())));
ConfigDataEnvironmentContributor contributorAndChildren = contributor.withChildren(importPhase,
asContributors(imported));
result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext,
result.getRoot().withReplacement(contributor, contributorAndChildren));
processed++;
}
}
private CharSequence getImportedMessage(Set<ConfigDataResolutionResult> results) {
if (results.isEmpty()) {
return "Nothing imported";
}
StringBuilder message = new StringBuilder();
message.append("Imported " + results.size() + " resource" + ((results.size() != 1) ? "s " : " "));
message.append(results.stream().map(ConfigDataResolutionResult::getResource).collect(Collectors.toList()));
return message;
}
protected final ConfigurableBootstrapContext getBootstrapContext() {
return this.bootstrapContext;
}
private ConfigDataEnvironmentContributor getNextToProcess(ConfigDataEnvironmentContributors contributors,
ConfigDataActivationContext activationContext, ImportPhase importPhase) {
for (ConfigDataEnvironmentContributor contributor : contributors.getRoot()) {
if (contributor.getKind() == Kind.UNBOUND_IMPORT
|| isActiveWithUnprocessedImports(activationContext, importPhase, contributor)) {
return contributor;
}
}
return null;
}
private boolean isActiveWithUnprocessedImports(ConfigDataActivationContext activationContext,
ImportPhase importPhase, ConfigDataEnvironmentContributor contributor) {
return contributor.isActive(activationContext) && contributor.hasUnprocessedImports(importPhase);
}
private List<ConfigDataEnvironmentContributor> asContributors(
Map<ConfigDataResolutionResult, ConfigData> imported) {
List<ConfigDataEnvironmentContributor> contributors = new ArrayList<>(imported.size() * 5);
imported.forEach((resolutionResult, data) -> {
for (int i = data.getPropertySources().size() - 1; i >= 0; i--) {
ConfigDataLocation location = resolutionResult.getLocation();
ConfigDataResource resource = resolutionResult.getResource();
contributors.add(ConfigDataEnvironmentContributor.ofUnboundImport(location, resource, data, i));
}
});
return Collections.unmodifiableList(contributors);
}
ConfigDataEnvironmentContributor getRoot() {
return this.root;
}
Binder getBinder(ConfigDataActivationContext activationContext, BinderOption... options) {
return getBinder(activationContext, asBinderOptionsSet(options));
}
private Set<BinderOption> asBinderOptionsSet(BinderOption... options) {
return ObjectUtils.isEmpty(options) ? EnumSet.noneOf(BinderOption.class)
: EnumSet.copyOf(Arrays.asList(options));
}
private Binder getBinder(ConfigDataActivationContext activationContext, Set<BinderOption> options) {
boolean failOnInactiveSource = options.contains(BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE);
Iterable<ConfigurationPropertySource> sources = () -> getBinderSources(activationContext,
!options.contains(BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE));
PlaceholdersResolver placeholdersResolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(this.root,
activationContext, failOnInactiveSource);
BindHandler bindHandler = !failOnInactiveSource ? null : new InactiveSourceChecker(activationContext);
return new Binder(sources, placeholdersResolver, null, null, bindHandler);
}
private Iterator<ConfigurationPropertySource> getBinderSources(ConfigDataActivationContext activationContext,
boolean filterInactive) {
Stream<ConfigDataEnvironmentContributor> sources = this.root.stream()
.filter(this::hasConfigurationPropertySource);
if (filterInactive) {
sources = sources.filter((contributor) -> contributor.isActive(activationContext));
}
return sources.map(ConfigDataEnvironmentContributor::getConfigurationPropertySource).iterator();
}
private boolean hasConfigurationPropertySource(ConfigDataEnvironmentContributor contributor) {
return contributor.getConfigurationPropertySource() != null;
}
@Override
public Iterator<ConfigDataEnvironmentContributor> iterator() {
return this.root.iterator();
}
private static class ContributorDataLoaderContext implements ConfigDataLoaderContext {
private final ConfigDataEnvironmentContributors contributors;
ContributorDataLoaderContext(ConfigDataEnvironmentContributors contributors) {
this.contributors = contributors;
}
@Override
public ConfigurableBootstrapContext getBootstrapContext() {
return this.contributors.getBootstrapContext();
}
}
private static class ContributorConfigDataLocationResolverContext implements ConfigDataLocationResolverContext {
private final ConfigDataEnvironmentContributors contributors;
private final ConfigDataEnvironmentContributor contributor;
private final ConfigDataActivationContext activationContext;
private volatile Binder binder;
ContributorConfigDataLocationResolverContext(ConfigDataEnvironmentContributors contributors,
ConfigDataEnvironmentContributor contributor, ConfigDataActivationContext activationContext) {
this.contributors = contributors;
this.contributor = contributor;
this.activationContext = activationContext;
}
@Override
public Binder getBinder() {
Binder binder = this.binder;
if (binder == null) {
binder = this.contributors.getBinder(this.activationContext);
this.binder = binder;
}
return binder;
}
@Override
public ConfigDataResource getParent() {
return this.contributor.getResource();
}
@Override
public ConfigurableBootstrapContext getBootstrapContext() {
return this.contributors.getBootstrapContext();
}
}
private class InactiveSourceChecker implements BindHandler {
private final ConfigDataActivationContext activationContext;
InactiveSourceChecker(ConfigDataActivationContext activationContext) {
this.activationContext = activationContext;
}
@Override
public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target, BindContext context,
Object result) {
for (ConfigDataEnvironmentContributor contributor : ConfigDataEnvironmentContributors.this) {
if (!contributor.isActive(this.activationContext)) {
InactiveConfigDataAccessException.throwIfPropertyFound(contributor, name);
}
}
return result;
}
}
enum BinderOption {
FAIL_ON_BIND_TO_INACTIVE_SOURCE;
}
}