Merge branch '2.4.x'

Closes gh-25887
pull/30708/head
Phillip Webb 4 years ago
commit c6cd5117d4

@ -43,7 +43,7 @@ public final class ConfigData {
private final List<PropertySource<?>> propertySources; private final List<PropertySource<?>> propertySources;
private final Set<Option> options; private final PropertySourceOptions propertySourceOptions;
/** /**
* A {@link ConfigData} instance that contains no data. * A {@link ConfigData} instance that contains no data.
@ -51,17 +51,30 @@ public final class ConfigData {
public static final ConfigData EMPTY = new ConfigData(Collections.emptySet()); public static final ConfigData EMPTY = new ConfigData(Collections.emptySet());
/** /**
* Create a new {@link ConfigData} instance. * Create a new {@link ConfigData} instance with the same options applied to each
* source.
* @param propertySources the config data property sources in ascending priority * @param propertySources the config data property sources in ascending priority
* order. * order.
* @param options the config data options * @param options the config data options applied to each source
* @see #ConfigData(Collection, PropertySourceOptions)
*/ */
public ConfigData(Collection<? extends PropertySource<?>> propertySources, Option... options) { public ConfigData(Collection<? extends PropertySource<?>> propertySources, Option... options) {
this(propertySources, PropertySourceOptions.always(Options.of(options)));
}
/**
* Create a new {@link ConfigData} instance with specific property source options.
* @param propertySources the config data property sources in ascending priority
* order.
* @param propertySourceOptions the property source options
* @since 2.4.5
*/
public ConfigData(Collection<? extends PropertySource<?>> propertySources,
PropertySourceOptions propertySourceOptions) {
Assert.notNull(propertySources, "PropertySources must not be null"); Assert.notNull(propertySources, "PropertySources must not be null");
Assert.notNull(options, "Options must not be null"); Assert.notNull(propertySourceOptions, "PropertySourceOptions must not be null");
this.propertySources = Collections.unmodifiableList(new ArrayList<>(propertySources)); this.propertySources = Collections.unmodifiableList(new ArrayList<>(propertySources));
this.options = Collections.unmodifiableSet( this.propertySourceOptions = propertySourceOptions;
(options.length != 0) ? EnumSet.copyOf(Arrays.asList(options)) : EnumSet.noneOf(Option.class));
} }
/** /**
@ -77,18 +90,167 @@ public final class ConfigData {
/** /**
* Return a set of {@link Option config data options} for this source. * Return a set of {@link Option config data options} for this source.
* @return the config data options * @return the config data options
* @deprecated since 2.4.5 in favor of {@link #getOptions(PropertySource)}
*/ */
@Deprecated
public Set<Option> getOptions() { public Set<Option> getOptions() {
return this.options; Assert.state(this.propertySourceOptions instanceof AlwaysPropertySourceOptions, "No global options defined");
return this.propertySourceOptions.get(null).asSet();
}
/**
* Return the {@link Options config data options} that apply to the given source.
* @param propertySource the property source to check
* @return the options that apply
* @since 2.4.5
*/
public Options getOptions(PropertySource<?> propertySource) {
Options options = this.propertySourceOptions.get(propertySource);
return (options != null) ? options : Options.NONE;
}
/**
* Strategy interface used to supply {@link Options} for a given
* {@link PropertySource}.
*
* @since 2.4.5
*/
@FunctionalInterface
public interface PropertySourceOptions {
/**
* Return the options that should apply for the given property source.
* @param propertySource the property source
* @return the options to apply
*/
Options get(PropertySource<?> propertySource);
/**
* Create a new {@link PropertySourceOptions} instance that always returns the
* same options regardless of the property source.
* @param options the options to return
* @return a new {@link PropertySourceOptions} instance
*/
static PropertySourceOptions always(Option... options) {
return always(Options.of(options));
}
/**
* Create a new {@link PropertySourceOptions} instance that always returns the
* same options regardless of the property source.
* @param options the options to return
* @return a new {@link PropertySourceOptions} instance
*/
static PropertySourceOptions always(Options options) {
return new AlwaysPropertySourceOptions(options);
}
}
/**
* {@link PropertySourceOptions} that always returns the same result.
*/
private static class AlwaysPropertySourceOptions implements PropertySourceOptions {
private final Options options;
AlwaysPropertySourceOptions(Options options) {
this.options = options;
}
@Override
public Options get(PropertySource<?> propertySource) {
return this.options;
}
} }
/** /**
* Option flags that can be applied config data. * A set of {@link Option} flags.
*
* @since 2.4.5
*/
public static final class Options {
/**
* No options.
*/
public static final Options NONE = Options.of();
private final Set<Option> options;
private Options(Set<Option> options) {
this.options = Collections.unmodifiableSet(options);
}
Set<Option> asSet() {
return this.options;
}
/**
* Returns if the given option is contained in this set.
* @param option the option to check
* @return {@code true} of the option is present
*/
public boolean contains(Option option) {
return this.options.contains(option);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Options other = (Options) obj;
return this.options.equals(other.options);
}
@Override
public int hashCode() {
return this.options.hashCode();
}
@Override
public String toString() {
return this.options.toString();
}
/**
* Create a new {@link Options} instance that contains the options in this set
* excluding the given option.
* @param option the option to exclude
* @return a new {@link Options} instance
*/
Options without(Option option) {
EnumSet<Option> options = EnumSet.noneOf(Option.class);
options.addAll(this.options);
options.remove(option);
return new Options(options);
}
/**
* Create a new instance with the given {@link Option} values.
* @param options the options to include
* @return a new {@link Options} instance
*/
public static Options of(Option... options) {
Assert.notNull(options, "Options must not be null");
return new Options(
(options.length != 0) ? EnumSet.copyOf(Arrays.asList(options)) : EnumSet.noneOf(Option.class));
}
}
/**
* Option flags that can be applied.
*/ */
public enum Option { public enum Option {
/** /**
* Ignore all imports properties from the sources. * Ignore all imports properties from the source.
*/ */
IGNORE_IMPORTS, IGNORE_IMPORTS,
@ -96,7 +258,14 @@ public final class ConfigData {
* Ignore all profile activation and include properties. * Ignore all profile activation and include properties.
* @since 2.4.3 * @since 2.4.3
*/ */
IGNORE_PROFILES; IGNORE_PROFILES,
/**
* Indicates that the source is "profile specific" and should be included after
* profile specific sibling imports.
* @since 2.4.5
*/
PROFILE_SPECIFIC;
} }

@ -269,7 +269,8 @@ class ConfigDataEnvironment {
ConfigDataActivationContext activationContext) { ConfigDataActivationContext activationContext) {
this.logger.trace("Deducing profiles from current config data environment contributors"); this.logger.trace("Deducing profiles from current config data environment contributors");
Binder binder = contributors.getBinder(activationContext, Binder binder = contributors.getBinder(activationContext,
ConfigDataEnvironmentContributor::isNotIgnoringProfiles, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE); (contributor) -> !contributor.hasConfigDataOption(ConfigData.Option.IGNORE_PROFILES),
BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE);
try { try {
Set<String> additionalProfiles = new LinkedHashSet<>(this.additionalProfiles); Set<String> additionalProfiles = new LinkedHashSet<>(this.additionalProfiles);
additionalProfiles.addAll(getIncludedProfiles(contributors, activationContext)); additionalProfiles.addAll(getIncludedProfiles(contributors, activationContext));
@ -291,7 +292,7 @@ class ConfigDataEnvironment {
Set<String> result = new LinkedHashSet<>(); Set<String> result = new LinkedHashSet<>();
for (ConfigDataEnvironmentContributor contributor : contributors) { for (ConfigDataEnvironmentContributor contributor : contributors) {
ConfigurationPropertySource source = contributor.getConfigurationPropertySource(); ConfigurationPropertySource source = contributor.getConfigurationPropertySource();
if (source != null && contributor.isNotIgnoringProfiles()) { if (source != null && !contributor.hasConfigDataOption(ConfigData.Option.IGNORE_PROFILES)) {
Binder binder = new Binder(Collections.singleton(source), placeholdersResolver); Binder binder = new Binder(Collections.singleton(source), placeholdersResolver);
binder.bind(Profiles.INCLUDE_PROFILES, STRING_LIST).ifBound((includes) -> { binder.bind(Profiles.INCLUDE_PROFILES, STRING_LIST).ifBound((includes) -> {
if (!contributor.isActive(activationContext)) { if (!contributor.isActive(activationContext)) {

@ -23,7 +23,6 @@ import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.Set;
import java.util.stream.Stream; import java.util.stream.Stream;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
@ -31,6 +30,7 @@ import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource; import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySource;
import org.springframework.util.CollectionUtils;
/** /**
* A single element that may directly or indirectly contribute configuration data to the * A single element that may directly or indirectly contribute configuration data to the
@ -52,14 +52,14 @@ import org.springframework.core.env.PropertySource;
*/ */
class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironmentContributor> { class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironmentContributor> {
private static final Set<ConfigData.Option> EMPTY_LOCATION_OPTIONS = Collections private static final ConfigData.Options EMPTY_LOCATION_OPTIONS = ConfigData.Options
.unmodifiableSet(Collections.singleton(ConfigData.Option.IGNORE_IMPORTS)); .of(ConfigData.Option.IGNORE_IMPORTS);
private final ConfigDataLocation location; private final ConfigDataLocation location;
private final ConfigDataResource resource; private final ConfigDataResource resource;
private final boolean profileSpecific; private final boolean fromProfileSpecificImport;
private final PropertySource<?> propertySource; private final PropertySource<?> propertySource;
@ -67,7 +67,7 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment
private final ConfigDataProperties properties; private final ConfigDataProperties properties;
private final Set<ConfigData.Option> configDataOptions; private final ConfigData.Options configDataOptions;
private final Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children; private final Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children;
@ -78,7 +78,8 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment
* @param kind the contributor kind * @param kind the contributor kind
* @param location the location of this contributor * @param location the location of this contributor
* @param resource the resource that contributed the data or {@code null} * @param resource the resource that contributed the data or {@code null}
* @param profileSpecific if the contributor is from a profile specific import * @param fromProfileSpecificImport if the contributor is from a profile specific
* import
* @param propertySource the property source for 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 * @param configurationPropertySource the configuration property source for the data
* or {@code null} * or {@code null}
@ -87,18 +88,17 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment
* @param children the children of this contributor at each {@link ImportPhase} * @param children the children of this contributor at each {@link ImportPhase}
*/ */
ConfigDataEnvironmentContributor(Kind kind, ConfigDataLocation location, ConfigDataResource resource, ConfigDataEnvironmentContributor(Kind kind, ConfigDataLocation location, ConfigDataResource resource,
boolean profileSpecific, PropertySource<?> propertySource, boolean fromProfileSpecificImport, PropertySource<?> propertySource,
ConfigurationPropertySource configurationPropertySource, ConfigDataProperties properties, ConfigurationPropertySource configurationPropertySource, ConfigDataProperties properties,
Set<ConfigData.Option> configDataOptions, ConfigData.Options configDataOptions, Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children) {
Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children) {
this.kind = kind; this.kind = kind;
this.location = location; this.location = location;
this.resource = resource; this.resource = resource;
this.profileSpecific = profileSpecific; this.fromProfileSpecificImport = fromProfileSpecificImport;
this.properties = properties; this.properties = properties;
this.propertySource = propertySource; this.propertySource = propertySource;
this.configurationPropertySource = configurationPropertySource; this.configurationPropertySource = configurationPropertySource;
this.configDataOptions = (configDataOptions != null) ? configDataOptions : Collections.emptySet(); this.configDataOptions = (configDataOptions != null) ? configDataOptions : ConfigData.Options.NONE;
this.children = (children != null) ? children : Collections.emptyMap(); this.children = (children != null) ? children : Collections.emptyMap();
} }
@ -135,8 +135,8 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment
* Return if the contributor is from a profile specific import. * Return if the contributor is from a profile specific import.
* @return if the contributor is profile specific * @return if the contributor is profile specific
*/ */
boolean isProfileSpecific() { boolean isFromProfileSpecificImport() {
return this.profileSpecific; return this.fromProfileSpecificImport;
} }
/** /**
@ -156,12 +156,18 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment
} }
/** /**
* Returns {@code true} if this contributor is not ignoring profile properties. * Return if the contributor has a specific config data option.
* @return if the contributor is not ignoring profiles * @param option the option to check
* @see ConfigData.Option#IGNORE_PROFILES * @return {@code true} if the option is present
*/ */
boolean isNotIgnoringProfiles() { boolean hasConfigDataOption(ConfigData.Option option) {
return !this.configDataOptions.contains(ConfigData.Option.IGNORE_PROFILES); return this.configDataOptions.contains(option);
}
ConfigDataEnvironmentContributor withoutConfigDataOption(ConfigData.Option option) {
return new ConfigDataEnvironmentContributor(this.kind, this.location, this.resource,
this.fromProfileSpecificImport, this.propertySource, this.configurationPropertySource, this.properties,
this.configDataOptions.without(option), this.children);
} }
/** /**
@ -227,7 +233,7 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment
properties = properties.withoutImports(); properties = properties.withoutImports();
} }
return new ConfigDataEnvironmentContributor(Kind.BOUND_IMPORT, this.location, this.resource, return new ConfigDataEnvironmentContributor(Kind.BOUND_IMPORT, this.location, this.resource,
this.profileSpecific, this.propertySource, this.configurationPropertySource, properties, this.fromProfileSpecificImport, this.propertySource, this.configurationPropertySource, properties,
this.configDataOptions, null); this.configDataOptions, null);
} }
@ -242,9 +248,60 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment
List<ConfigDataEnvironmentContributor> children) { List<ConfigDataEnvironmentContributor> children) {
Map<ImportPhase, List<ConfigDataEnvironmentContributor>> updatedChildren = new LinkedHashMap<>(this.children); Map<ImportPhase, List<ConfigDataEnvironmentContributor>> updatedChildren = new LinkedHashMap<>(this.children);
updatedChildren.put(importPhase, children); updatedChildren.put(importPhase, children);
return new ConfigDataEnvironmentContributor(this.kind, this.location, this.resource, this.profileSpecific, if (importPhase == ImportPhase.AFTER_PROFILE_ACTIVATION) {
this.propertySource, this.configurationPropertySource, this.properties, this.configDataOptions, moveProfileSpecific(updatedChildren);
updatedChildren); }
return new ConfigDataEnvironmentContributor(this.kind, this.location, this.resource,
this.fromProfileSpecificImport, this.propertySource, this.configurationPropertySource, this.properties,
this.configDataOptions, updatedChildren);
}
private void moveProfileSpecific(Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children) {
List<ConfigDataEnvironmentContributor> before = children.get(ImportPhase.BEFORE_PROFILE_ACTIVATION);
if (!hasAnyProfileSpecificChildren(before)) {
return;
}
List<ConfigDataEnvironmentContributor> updatedBefore = new ArrayList<>(before.size());
List<ConfigDataEnvironmentContributor> updatedAfter = new ArrayList<>();
for (ConfigDataEnvironmentContributor contributor : before) {
updatedBefore.add(moveProfileSpecificChildren(contributor, updatedAfter));
}
updatedAfter.addAll(children.getOrDefault(ImportPhase.AFTER_PROFILE_ACTIVATION, Collections.emptyList()));
children.put(ImportPhase.BEFORE_PROFILE_ACTIVATION, updatedBefore);
children.put(ImportPhase.AFTER_PROFILE_ACTIVATION, updatedAfter);
}
private ConfigDataEnvironmentContributor moveProfileSpecificChildren(ConfigDataEnvironmentContributor contributor,
List<ConfigDataEnvironmentContributor> removed) {
for (ImportPhase importPhase : ImportPhase.values()) {
List<ConfigDataEnvironmentContributor> children = contributor.getChildren(importPhase);
List<ConfigDataEnvironmentContributor> updatedChildren = new ArrayList<>(children.size());
for (ConfigDataEnvironmentContributor child : children) {
if (child.hasConfigDataOption(ConfigData.Option.PROFILE_SPECIFIC)) {
removed.add(child.withoutConfigDataOption(ConfigData.Option.PROFILE_SPECIFIC));
}
else {
updatedChildren.add(child);
}
}
contributor = contributor.withChildren(importPhase, updatedChildren);
}
return contributor;
}
private boolean hasAnyProfileSpecificChildren(List<ConfigDataEnvironmentContributor> contributors) {
if (CollectionUtils.isEmpty(contributors)) {
return false;
}
for (ConfigDataEnvironmentContributor contributor : contributors) {
for (ImportPhase importPhase : ImportPhase.values()) {
if (contributor.getChildren(importPhase).stream()
.anyMatch((child) -> child.hasConfigDataOption(ConfigData.Option.PROFILE_SPECIFIC))) {
return true;
}
}
}
return false;
} }
/** /**
@ -268,9 +325,36 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment
} }
updatedChildren.put(importPhase, Collections.unmodifiableList(updatedContributors)); updatedChildren.put(importPhase, Collections.unmodifiableList(updatedContributors));
}); });
return new ConfigDataEnvironmentContributor(this.kind, this.location, this.resource, this.profileSpecific, return new ConfigDataEnvironmentContributor(this.kind, this.location, this.resource,
this.propertySource, this.configurationPropertySource, this.properties, this.configDataOptions, this.fromProfileSpecificImport, this.propertySource, this.configurationPropertySource, this.properties,
updatedChildren); this.configDataOptions, updatedChildren);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
buildToString("", builder);
return builder.toString();
}
private void buildToString(String prefix, StringBuilder builder) {
builder.append(prefix);
builder.append(this.kind);
builder.append(" ");
builder.append(this.location);
builder.append(" ");
builder.append(this.resource);
builder.append(" ");
builder.append(this.configDataOptions);
builder.append("\n");
for (ConfigDataEnvironmentContributor child : this.children.getOrDefault(ImportPhase.BEFORE_PROFILE_ACTIVATION,
Collections.emptyList())) {
child.buildToString(prefix + " ", builder);
}
for (ConfigDataEnvironmentContributor child : this.children.getOrDefault(ImportPhase.AFTER_PROFILE_ACTIVATION,
Collections.emptyList())) {
child.buildToString(prefix + " ", builder);
}
} }
/** /**
@ -324,9 +408,10 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment
static ConfigDataEnvironmentContributor ofUnboundImport(ConfigDataLocation location, ConfigDataResource resource, static ConfigDataEnvironmentContributor ofUnboundImport(ConfigDataLocation location, ConfigDataResource resource,
boolean profileSpecific, ConfigData configData, int propertySourceIndex) { boolean profileSpecific, ConfigData configData, int propertySourceIndex) {
PropertySource<?> propertySource = configData.getPropertySources().get(propertySourceIndex); PropertySource<?> propertySource = configData.getPropertySources().get(propertySourceIndex);
ConfigData.Options options = configData.getOptions(propertySource);
ConfigurationPropertySource configurationPropertySource = ConfigurationPropertySource.from(propertySource); ConfigurationPropertySource configurationPropertySource = ConfigurationPropertySource.from(propertySource);
return new ConfigDataEnvironmentContributor(Kind.UNBOUND_IMPORT, location, resource, profileSpecific, return new ConfigDataEnvironmentContributor(Kind.UNBOUND_IMPORT, location, resource, profileSpecific,
propertySource, configurationPropertySource, null, configData.getOptions(), null); propertySource, configurationPropertySource, null, options, null);
} }
/** /**

@ -117,7 +117,8 @@ public class InvalidConfigDataPropertyException extends ConfigDataException {
logger.warn(getMessage(property, false, replacement, contributor.getResource())); logger.warn(getMessage(property, false, replacement, contributor.getResource()));
} }
}); });
if (contributor.isProfileSpecific() && contributor.isNotIgnoringProfiles()) { if (contributor.isFromProfileSpecificImport()
&& !contributor.hasConfigDataOption(ConfigData.Option.IGNORE_PROFILES)) {
PROFILE_SPECIFIC_ERRORS.forEach((name) -> { PROFILE_SPECIFIC_ERRORS.forEach((name) -> {
ConfigurationProperty property = propertySource.getConfigurationProperty(name); ConfigurationProperty property = propertySource.getConfigurationProperty(name);
if (property != null) { if (property != null) {

@ -27,6 +27,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.cloud.CloudPlatform; import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.boot.context.config.ConfigData.Option; import org.springframework.boot.context.config.ConfigData.Option;
import org.springframework.boot.context.config.ConfigData.PropertySourceOptions;
import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.ImportPhase; import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.ImportPhase;
import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.Kind; import org.springframework.boot.context.config.ConfigDataEnvironmentContributor.Kind;
import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.context.properties.bind.Binder;
@ -224,6 +225,21 @@ class ConfigDataEnvironmentContributorTests {
assertThat(withChildren.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION)).containsExactly(child); assertThat(withChildren.getChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION)).containsExactly(child);
} }
@Test
void withChildrenAfterProfileActivationMovesProfileSpecificChildren() {
ConfigDataEnvironmentContributor root = createBoundContributor("root");
ConfigDataEnvironmentContributor child1 = createBoundContributor("child1");
ConfigDataEnvironmentContributor grandchild = createBoundContributor(new TestResource("grandchild"),
new ConfigData(Collections.singleton(new MockPropertySource()),
PropertySourceOptions.always(Option.PROFILE_SPECIFIC)),
0);
child1 = child1.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION, Collections.singletonList(grandchild));
root = root.withChildren(ImportPhase.BEFORE_PROFILE_ACTIVATION, Collections.singletonList(child1));
ConfigDataEnvironmentContributor child2 = createBoundContributor("child2");
root = root.withChildren(ImportPhase.AFTER_PROFILE_ACTIVATION, Collections.singletonList(child2));
assertThat(asLocationsList(root.iterator())).containsExactly("grandchild", "child2", "child1", "root");
}
@Test @Test
void withReplacementReplacesChild() { void withReplacementReplacesChild() {
ConfigDataEnvironmentContributor root = createBoundContributor("root"); ConfigDataEnvironmentContributor root = createBoundContributor("root");

@ -0,0 +1,132 @@
/*
* Copyright 2012-2021 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.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.context.config.ConfigData.Option;
import org.springframework.boot.context.config.ConfigData.Options;
import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessorIntegrationTests.Config;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link ConfigDataEnvironmentPostProcessor} config data imports
* that are combined with profile-specific files.
*
* @author Phillip Webb
*/
class ConfigDataEnvironmentPostProcessorImportCombinedWithProfileSpecificIntegrationTests {
private SpringApplication application;
@TempDir
public File temp;
@BeforeEach
void setup() {
this.application = new SpringApplication(Config.class);
this.application.setWebApplicationType(WebApplicationType.NONE);
}
@Test
void testWithoutProfile() {
ConfigurableApplicationContext context = this.application
.run("--spring.config.name=configimportwithprofilespecific");
String value = context.getEnvironment().getProperty("prop");
assertThat(value).isEqualTo("fromicwps1");
}
@Test
void testWithProfile() {
ConfigurableApplicationContext context = this.application
.run("--spring.config.name=configimportwithprofilespecific", "--spring.profiles.active=prod");
String value = context.getEnvironment().getProperty("prop");
assertThat(value).isEqualTo("fromicwps2");
}
static class LocationResolver implements ConfigDataLocationResolver<Resource> {
@Override
public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location) {
return location.hasPrefix("icwps:");
}
@Override
public List<Resource> resolve(ConfigDataLocationResolverContext context, ConfigDataLocation location)
throws ConfigDataLocationNotFoundException, ConfigDataResourceNotFoundException {
return Collections.emptyList();
}
@Override
public List<Resource> resolveProfileSpecific(ConfigDataLocationResolverContext context,
ConfigDataLocation location, Profiles profiles) throws ConfigDataLocationNotFoundException {
return Collections.singletonList(new Resource(profiles));
}
}
static class Loader implements ConfigDataLoader<Resource> {
@Override
public ConfigData load(ConfigDataLoaderContext context, Resource resource)
throws IOException, ConfigDataResourceNotFoundException {
List<PropertySource<?>> propertySources = new ArrayList<>();
Map<PropertySource<?>, Options> propertySourceOptions = new HashMap<>();
propertySources.add(new MapPropertySource("icwps1", Collections.singletonMap("prop", "fromicwps1")));
if (resource.profiles.isAccepted("prod")) {
MapPropertySource profileSpecificPropertySource = new MapPropertySource("icwps2",
Collections.singletonMap("prop", "fromicwps2"));
propertySources.add(profileSpecificPropertySource);
propertySourceOptions.put(profileSpecificPropertySource, Options.of(Option.PROFILE_SPECIFIC));
}
return new ConfigData(propertySources, propertySourceOptions::get);
}
}
private static class Resource extends ConfigDataResource {
private final Profiles profiles;
Resource(Profiles profiles) {
this.profiles = profiles;
}
@Override
public String toString() {
return "icwps:";
}
}
}

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,16 +17,22 @@
package org.springframework.boot.context.config; package org.springframework.boot.context.config;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.context.config.ConfigData.Option; import org.springframework.boot.context.config.ConfigData.Option;
import org.springframework.boot.context.config.ConfigData.Options;
import org.springframework.boot.context.config.ConfigData.PropertySourceOptions;
import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.mockito.Mockito.mock;
/** /**
* Tests for {@link ConfigData}. * Tests for {@link ConfigData}.
@ -58,7 +64,8 @@ class ConfigDataTests {
} }
@Test @Test
void getOptionsReturnsCopyOfOptions() { @Deprecated
void getDeprecatedOptionsReturnsCopyOfOptions() {
MapPropertySource source = new MapPropertySource("test", Collections.emptyMap()); MapPropertySource source = new MapPropertySource("test", Collections.emptyMap());
Option[] options = { Option.IGNORE_IMPORTS }; Option[] options = { Option.IGNORE_IMPORTS };
ConfigData configData = new ConfigData(Collections.singleton(source), options); ConfigData configData = new ConfigData(Collections.singleton(source), options);
@ -66,4 +73,67 @@ class ConfigDataTests {
assertThat(configData.getOptions()).containsExactly(Option.IGNORE_IMPORTS); assertThat(configData.getOptions()).containsExactly(Option.IGNORE_IMPORTS);
} }
@Test
@Deprecated
void getDeprecatedOptionsWhenUsingPropertySourceOptionsThrowsException() {
MapPropertySource source = new MapPropertySource("test", Collections.emptyMap());
PropertySourceOptions propertySourceOptions = (propertySource) -> Options.NONE;
ConfigData configData = new ConfigData(Collections.singleton(source), propertySourceOptions);
assertThatIllegalStateException().isThrownBy(() -> configData.getOptions())
.withMessage("No global options defined");
}
@Test
void getOptionsWhenOptionsSetAtConstructionAlwaysReturnsSameOptions() {
MapPropertySource source = new MapPropertySource("test", Collections.emptyMap());
ConfigData configData = new ConfigData(Collections.singleton(source), Option.IGNORE_IMPORTS);
assertThat(configData.getOptions(source).asSet()).containsExactly(Option.IGNORE_IMPORTS);
}
@Test
void getOptionsReturnsOptionsFromPropertySourceOptions() {
MapPropertySource source1 = new MapPropertySource("test", Collections.emptyMap());
MapPropertySource source2 = new MapPropertySource("test", Collections.emptyMap());
Options options1 = Options.of(Option.IGNORE_IMPORTS);
Options options2 = Options.of(Option.IGNORE_PROFILES);
PropertySourceOptions propertySourceOptions = (source) -> source == source1 ? options1 : options2;
ConfigData configData = new ConfigData(Arrays.asList(source1, source2), propertySourceOptions);
assertThat(configData.getOptions(source1)).isEqualTo(options1);
assertThat(configData.getOptions(source2)).isEqualTo(options2);
}
@Test
void getOptionsWhenPropertySourceOptionsReturnsNullReturnsNone() {
MapPropertySource source = new MapPropertySource("test", Collections.emptyMap());
PropertySourceOptions propertySourceOptions = (propertySource) -> null;
ConfigData configData = new ConfigData(Collections.singleton(source), propertySourceOptions);
assertThat(configData.getOptions(source)).isEqualTo(Options.NONE);
}
@Test
void optionsOfCreatesOptions() {
Options options = Options.of(Option.IGNORE_IMPORTS, Option.IGNORE_PROFILES);
assertThat(options.asSet()).containsExactly(Option.IGNORE_IMPORTS, Option.IGNORE_PROFILES);
}
@Test
void optionsOfUsesCopyOfOptions() {
Option[] array = { Option.IGNORE_IMPORTS, Option.IGNORE_PROFILES };
Options options = Options.of(array);
array[0] = Option.PROFILE_SPECIFIC;
assertThat(options.asSet()).containsExactly(Option.IGNORE_IMPORTS, Option.IGNORE_PROFILES);
}
@Test
void optionsNoneReturnsEmptyOptions() {
assertThat(Options.NONE.asSet()).isEmpty();
}
@Test
void propertySourceOptionsAlwaysReturnsSameOptionsEachTime() {
PropertySourceOptions options = PropertySourceOptions.always(Option.IGNORE_IMPORTS, Option.IGNORE_PROFILES);
assertThat(options.get(mock(PropertySource.class)).asSet()).containsExactly(Option.IGNORE_IMPORTS,
Option.IGNORE_PROFILES);
}
} }

@ -16,9 +16,6 @@
package org.springframework.boot.context.config; package org.springframework.boot.context.config;
import java.util.Arrays;
import java.util.HashSet;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -148,7 +145,7 @@ class InvalidConfigDataPropertyExceptionTests {
propertySource.setProperty(name, "a"); propertySource.setProperty(name, "a");
ConfigDataEnvironmentContributor contributor = new ConfigDataEnvironmentContributor(Kind.BOUND_IMPORT, null, ConfigDataEnvironmentContributor contributor = new ConfigDataEnvironmentContributor(Kind.BOUND_IMPORT, null,
null, true, propertySource, ConfigurationPropertySource.from(propertySource), null, null, true, propertySource, ConfigurationPropertySource.from(propertySource), null,
new HashSet<>(Arrays.asList(configDataOptions)), null); ConfigData.Options.of(configDataOptions), null);
return contributor; return contributor;
} }

@ -3,7 +3,9 @@ org.springframework.boot.context.config.TestPropertySourceLoader1,\
org.springframework.boot.context.config.TestPropertySourceLoader2 org.springframework.boot.context.config.TestPropertySourceLoader2
org.springframework.boot.context.config.ConfigDataLocationResolver=\ org.springframework.boot.context.config.ConfigDataLocationResolver=\
org.springframework.boot.context.config.TestConfigDataBootstrap.LocationResolver org.springframework.boot.context.config.TestConfigDataBootstrap.LocationResolver,\
org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessorImportCombinedWithProfileSpecificIntegrationTests.LocationResolver
org.springframework.boot.context.config.ConfigDataLoader=\ org.springframework.boot.context.config.ConfigDataLoader=\
org.springframework.boot.context.config.TestConfigDataBootstrap.Loader org.springframework.boot.context.config.TestConfigDataBootstrap.Loader,\
org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessorImportCombinedWithProfileSpecificIntegrationTests.Loader

Loading…
Cancel
Save