Commit 1cf9fc10 authored by Phillip Webb's avatar Phillip Webb

Improve ConfigData processing code

Refactor `ConfigData` processing code to make it less awkward to
follow.

Prior to this commit the `ConfigDataLocationResolver` would take a
String location and return a `ConfigDataLocation` instance. This was
a little confusing since sometimes we would refer to `location` as the
String value, and sometimes it would be the typed instance. We also
had nowhere sensible to put the `optional:` prefix logic and we needed
to pass a `boolean` parameter to a number of methods. The recently
introduced `Orgin` support also didn't have a good home.

To solve this, `ConfigDataLocation` has been renamed to
`ConfigDataResource`. This frees up `ConfigDataLocation` to be used
as a richer `location` type that holds the String value, the `Orgin`
and provides a home for the `optional:` logic.

This commit also cleans up a few other areas of the code, including
renaming `ResourceConfigData...` to `StandardConfigData...`. It also
introduces a new exception hierarchy for `ConfigDataNotFoundExceptions`.

Closes gh-23711
parent f89b99bd
...@@ -663,7 +663,7 @@ You can use this prefix with the `spring.config.location` and `spring.config.add ...@@ -663,7 +663,7 @@ You can use this prefix with the `spring.config.location` and `spring.config.add
For example, a `spring.config.import` value of `optional:file:./myconfig.properties` allows your application to start, even if the `myconfig.properties` file is missing. For example, a `spring.config.import` value of `optional:file:./myconfig.properties` allows your application to start, even if the `myconfig.properties` file is missing.
If you want to ignore all `ConfigDataLocationNotFoundExceptions` and always continue to start your application, you can use the `spring.config.on-location-not-found` property. If you want to ignore all `ConfigDataLocationNotFoundExceptions` and always continue to start your application, you can use the `spring.config.on-not-found` property.
Set the value to `ignore` using `SpringApplication.setDefaultProperties(...)` or with a system/environment variable. Set the value to `ignore` using `SpringApplication.setDefaultProperties(...)` or with a system/environment variable.
......
...@@ -29,9 +29,9 @@ import org.springframework.core.env.PropertySource; ...@@ -29,9 +29,9 @@ import org.springframework.core.env.PropertySource;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
* Configuration data that has been loaded from an external {@link ConfigDataLocation * Configuration data that has been loaded from a {@link ConfigDataResource} and may
* location} and may ultimately contribute {@link PropertySource property sources} to * ultimately contribute {@link PropertySource property sources} to Spring's
* Spring's {@link Environment}. * {@link Environment}.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Madhura Bhave * @author Madhura Bhave
......
...@@ -48,10 +48,10 @@ import org.springframework.util.StringUtils; ...@@ -48,10 +48,10 @@ import org.springframework.util.StringUtils;
* Wrapper around a {@link ConfigurableEnvironment} that can be used to import and apply * Wrapper around a {@link ConfigurableEnvironment} that can be used to import and apply
* {@link ConfigData}. Configures the initial set of * {@link ConfigData}. Configures the initial set of
* {@link ConfigDataEnvironmentContributors} by wrapping property sources from the Spring * {@link ConfigDataEnvironmentContributors} by wrapping property sources from the Spring
* {@link Environment} and adding the initial set of imports. * {@link Environment} and adding the initial set of locations.
* <p> * <p>
* The initial imports can be influenced via the {@link #LOCATION_PROPERTY}, * The initial locations can be influenced via the {@link #LOCATION_PROPERTY},
* {@value #ADDITIONAL_LOCATION_PROPERTY} and {@value #IMPORT_PROPERTY} properties. If not * {@value #ADDITIONAL_LOCATION_PROPERTY} and {@value #IMPORT_PROPERTY} properties. If no
* explicit properties are set, the {@link #DEFAULT_SEARCH_LOCATIONS} will be used. * explicit properties are set, the {@link #DEFAULT_SEARCH_LOCATIONS} will be used.
* *
* @author Phillip Webb * @author Phillip Webb
...@@ -76,28 +76,41 @@ class ConfigDataEnvironment { ...@@ -76,28 +76,41 @@ class ConfigDataEnvironment {
/** /**
* Property used to determine what action to take when a * Property used to determine what action to take when a
* {@code ConfigDataLocationNotFoundException} is thrown. * {@code ConfigDataNotFoundAction} is thrown.
* @see ConfigDataLocationNotFoundAction * @see ConfigDataNotFoundAction
*/ */
static final String ON_LOCATION_NOT_FOUND_PROPERTY = "spring.config.on-location-not-found"; static final String ON_NOT_FOUND_PROPERTY = "spring.config.on-not-found";
/** /**
* Default search locations used if not {@link #LOCATION_PROPERTY} is found. * Default search locations used if not {@link #LOCATION_PROPERTY} is found.
*/ */
static final String[] DEFAULT_SEARCH_LOCATIONS = { "optional:classpath:/", "optional:classpath:/config/", static final ConfigDataLocation[] DEFAULT_SEARCH_LOCATIONS;
"optional:file:./", "optional:file:./config/*/", "optional:file:./config/" }; static {
List<ConfigDataLocation> locations = new ArrayList<>();
private static final String[] EMPTY_LOCATIONS = new String[0]; locations.add(ConfigDataLocation.of("optional:classpath:/"));
locations.add(ConfigDataLocation.of("optional:classpath:/config/"));
locations.add(ConfigDataLocation.of("optional:file:./"));
locations.add(ConfigDataLocation.of("optional:file:./config/*/"));
locations.add(ConfigDataLocation.of("optional:file:./config/"));
DEFAULT_SEARCH_LOCATIONS = locations.toArray(new ConfigDataLocation[0]);
};
private static final ConfigDataLocation[] EMPTY_LOCATIONS = new ConfigDataLocation[0];
private static final ConfigurationPropertyName INCLUDE_PROFILES = ConfigurationPropertyName private static final ConfigurationPropertyName INCLUDE_PROFILES = ConfigurationPropertyName
.of(Profiles.INCLUDE_PROFILES_PROPERTY_NAME); .of(Profiles.INCLUDE_PROFILES_PROPERTY_NAME);
private static final Bindable<ConfigDataLocation[]> CONFIG_DATA_LOCATION_ARRAY = Bindable
.of(ConfigDataLocation[].class);
private static final Bindable<List<String>> STRING_LIST = Bindable.listOf(String.class); private static final Bindable<List<String>> STRING_LIST = Bindable.listOf(String.class);
private final DeferredLogFactory logFactory; private final DeferredLogFactory logFactory;
private final Log logger; private final Log logger;
private final ConfigDataNotFoundAction notFoundAction;
private final ConfigurableBootstrapContext bootstrapContext; private final ConfigurableBootstrapContext bootstrapContext;
private final ConfigurableEnvironment environment; private final ConfigurableEnvironment environment;
...@@ -122,25 +135,21 @@ class ConfigDataEnvironment { ...@@ -122,25 +135,21 @@ class ConfigDataEnvironment {
ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection<String> additionalProfiles) { ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection<String> additionalProfiles) {
Binder binder = Binder.get(environment); Binder binder = Binder.get(environment);
UseLegacyConfigProcessingException.throwIfRequested(binder); UseLegacyConfigProcessingException.throwIfRequested(binder);
ConfigDataLocationNotFoundAction locationNotFoundAction = binder
.bind(ON_LOCATION_NOT_FOUND_PROPERTY, ConfigDataLocationNotFoundAction.class)
.orElse(ConfigDataLocationNotFoundAction.FAIL);
this.logFactory = logFactory; this.logFactory = logFactory;
this.logger = logFactory.getLog(getClass()); this.logger = logFactory.getLog(getClass());
this.notFoundAction = binder.bind(ON_NOT_FOUND_PROPERTY, ConfigDataNotFoundAction.class)
.orElse(ConfigDataNotFoundAction.FAIL);
this.bootstrapContext = bootstrapContext; this.bootstrapContext = bootstrapContext;
this.environment = environment; this.environment = environment;
this.resolvers = createConfigDataLocationResolvers(logFactory, bootstrapContext, locationNotFoundAction, binder, this.resolvers = createConfigDataLocationResolvers(logFactory, bootstrapContext, binder, resourceLoader);
resourceLoader);
this.additionalProfiles = additionalProfiles; this.additionalProfiles = additionalProfiles;
this.loaders = new ConfigDataLoaders(logFactory, bootstrapContext, locationNotFoundAction); this.loaders = new ConfigDataLoaders(logFactory, bootstrapContext);
this.contributors = createContributors(binder); this.contributors = createContributors(binder);
} }
protected ConfigDataLocationResolvers createConfigDataLocationResolvers(DeferredLogFactory logFactory, protected ConfigDataLocationResolvers createConfigDataLocationResolvers(DeferredLogFactory logFactory,
ConfigurableBootstrapContext bootstrapContext, ConfigDataLocationNotFoundAction locationNotFoundAction, ConfigurableBootstrapContext bootstrapContext, Binder binder, ResourceLoader resourceLoader) {
Binder binder, ResourceLoader resourceLoader) { return new ConfigDataLocationResolvers(logFactory, bootstrapContext, binder, resourceLoader);
return new ConfigDataLocationResolvers(logFactory, bootstrapContext, locationNotFoundAction, binder,
resourceLoader);
} }
private ConfigDataEnvironmentContributors createContributors(Binder binder) { private ConfigDataEnvironmentContributors createContributors(Binder binder) {
...@@ -172,23 +181,26 @@ class ConfigDataEnvironment { ...@@ -172,23 +181,26 @@ class ConfigDataEnvironment {
private List<ConfigDataEnvironmentContributor> getInitialImportContributors(Binder binder) { private List<ConfigDataEnvironmentContributor> getInitialImportContributors(Binder binder) {
List<ConfigDataEnvironmentContributor> initialContributors = new ArrayList<>(); List<ConfigDataEnvironmentContributor> initialContributors = new ArrayList<>();
addInitialImportContributors(initialContributors, bindLocations(binder, IMPORT_PROPERTY, EMPTY_LOCATIONS));
addInitialImportContributors(initialContributors, addInitialImportContributors(initialContributors,
binder.bind(IMPORT_PROPERTY, String[].class).orElse(EMPTY_LOCATIONS)); bindLocations(binder, ADDITIONAL_LOCATION_PROPERTY, EMPTY_LOCATIONS));
addInitialImportContributors(initialContributors, addInitialImportContributors(initialContributors,
binder.bind(ADDITIONAL_LOCATION_PROPERTY, String[].class).orElse(EMPTY_LOCATIONS)); bindLocations(binder, LOCATION_PROPERTY, DEFAULT_SEARCH_LOCATIONS));
addInitialImportContributors(initialContributors,
binder.bind(LOCATION_PROPERTY, String[].class).orElse(DEFAULT_SEARCH_LOCATIONS));
return initialContributors; return initialContributors;
} }
private ConfigDataLocation[] bindLocations(Binder binder, String propertyName, ConfigDataLocation[] other) {
return binder.bind(propertyName, CONFIG_DATA_LOCATION_ARRAY).orElse(other);
}
private void addInitialImportContributors(List<ConfigDataEnvironmentContributor> initialContributors, private void addInitialImportContributors(List<ConfigDataEnvironmentContributor> initialContributors,
String[] locations) { ConfigDataLocation[] locations) {
for (int i = locations.length - 1; i >= 0; i--) { for (int i = locations.length - 1; i >= 0; i--) {
initialContributors.add(createInitialImportContributor(locations[i])); initialContributors.add(createInitialImportContributor(locations[i]));
} }
} }
private ConfigDataEnvironmentContributor createInitialImportContributor(String location) { private ConfigDataEnvironmentContributor createInitialImportContributor(ConfigDataLocation location) {
this.logger.trace(LogMessage.format("Adding initial config data import from location '%s'", location)); this.logger.trace(LogMessage.format("Adding initial config data import from location '%s'", location));
return ConfigDataEnvironmentContributor.ofInitialImport(location); return ConfigDataEnvironmentContributor.ofInitialImport(location);
} }
...@@ -198,7 +210,8 @@ class ConfigDataEnvironment { ...@@ -198,7 +210,8 @@ class ConfigDataEnvironment {
* {@link Environment}. * {@link Environment}.
*/ */
void processAndApply() { void processAndApply() {
ConfigDataImporter importer = new ConfigDataImporter(this.resolvers, this.loaders); ConfigDataImporter importer = new ConfigDataImporter(this.logFactory, this.notFoundAction, this.resolvers,
this.loaders);
this.bootstrapContext.register(Binder.class, InstanceSupplier this.bootstrapContext.register(Binder.class, InstanceSupplier
.from(() -> this.contributors.getBinder(null, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE))); .from(() -> this.contributors.getBinder(null, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE)));
ConfigDataEnvironmentContributors contributors = processInitial(this.contributors, importer); ConfigDataEnvironmentContributors contributors = processInitial(this.contributors, importer);
......
...@@ -28,7 +28,6 @@ import java.util.stream.StreamSupport; ...@@ -28,7 +28,6 @@ import java.util.stream.StreamSupport;
import org.springframework.boot.context.properties.bind.Binder; 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.boot.origin.Origin;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySource;
...@@ -52,7 +51,7 @@ import org.springframework.core.env.PropertySource; ...@@ -52,7 +51,7 @@ import org.springframework.core.env.PropertySource;
*/ */
class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironmentContributor> { class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironmentContributor> {
private final ConfigDataLocation location; private final ConfigDataResource resource;
private final PropertySource<?> propertySource; private final PropertySource<?> propertySource;
...@@ -69,7 +68,7 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment ...@@ -69,7 +68,7 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment
/** /**
* Create a new {@link ConfigDataEnvironmentContributor} instance. * Create a new {@link ConfigDataEnvironmentContributor} instance.
* @param kind the contributor kind * @param kind the contributor kind
* @param location the location that contributed the data or {@code null} * @param resource the resource that contributed the data or {@code null}
* @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}
...@@ -77,11 +76,11 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment ...@@ -77,11 +76,11 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment
* @param ignoreImports if import properties should be ignored * @param ignoreImports if import properties should be ignored
* @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, PropertySource<?> propertySource, ConfigDataEnvironmentContributor(Kind kind, ConfigDataResource resource, PropertySource<?> propertySource,
ConfigurationPropertySource configurationPropertySource, ConfigDataProperties properties, ConfigurationPropertySource configurationPropertySource, ConfigDataProperties properties,
boolean ignoreImports, Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children) { boolean ignoreImports, Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children) {
this.kind = kind; this.kind = kind;
this.location = location; this.resource = resource;
this.properties = properties; this.properties = properties;
this.propertySource = propertySource; this.propertySource = propertySource;
this.configurationPropertySource = configurationPropertySource; this.configurationPropertySource = configurationPropertySource;
...@@ -107,11 +106,11 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment ...@@ -107,11 +106,11 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment
} }
/** /**
* Return the location that contributed this instance. * Return the resource that contributed this instance.
* @return the location or {@code null} * @return the resource or {@code null}
*/ */
ConfigDataLocation getLocation() { ConfigDataResource getResource() {
return this.location; return this.resource;
} }
/** /**
...@@ -134,14 +133,10 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment ...@@ -134,14 +133,10 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment
* Return any imports requested by this contributor. * Return any imports requested by this contributor.
* @return the imports * @return the imports
*/ */
List<String> getImports() { List<ConfigDataLocation> getImports() {
return (this.properties != null) ? this.properties.getImports() : Collections.emptyList(); return (this.properties != null) ? this.properties.getImports() : Collections.emptyList();
} }
Origin getImportOrigin(String importLocation) {
return (this.properties != null) ? this.properties.getImportOrigin(importLocation) : null;
}
/** /**
* Return true if this contributor has imports that have not yet been processed in the * Return true if this contributor has imports that have not yet been processed in the
* given phase. * given phase.
...@@ -184,13 +179,19 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment ...@@ -184,13 +179,19 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment
return new ContributorIterator(); return new ContributorIterator();
} }
/**
* Create an new {@link ConfigDataEnvironmentContributor} with bound
* {@link ConfigDataProperties}.
* @param binder the binder to use
* @return a new contributor instance
*/
ConfigDataEnvironmentContributor withBoundProperties(Binder binder) { ConfigDataEnvironmentContributor withBoundProperties(Binder binder) {
UseLegacyConfigProcessingException.throwIfRequested(binder); UseLegacyConfigProcessingException.throwIfRequested(binder);
ConfigDataProperties properties = ConfigDataProperties.get(binder); ConfigDataProperties properties = ConfigDataProperties.get(binder);
if (this.ignoreImports) { if (this.ignoreImports) {
properties = properties.withoutImports(); properties = properties.withoutImports();
} }
return new ConfigDataEnvironmentContributor(Kind.BOUND_IMPORT, this.location, this.propertySource, return new ConfigDataEnvironmentContributor(Kind.BOUND_IMPORT, this.resource, this.propertySource,
this.configurationPropertySource, properties, this.ignoreImports, null); this.configurationPropertySource, properties, this.ignoreImports, null);
} }
...@@ -205,7 +206,7 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment ...@@ -205,7 +206,7 @@ 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.propertySource, return new ConfigDataEnvironmentContributor(this.kind, this.resource, this.propertySource,
this.configurationPropertySource, this.properties, this.ignoreImports, updatedChildren); this.configurationPropertySource, this.properties, this.ignoreImports, updatedChildren);
} }
...@@ -230,7 +231,7 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment ...@@ -230,7 +231,7 @@ 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.propertySource, return new ConfigDataEnvironmentContributor(this.kind, this.resource, this.propertySource,
this.configurationPropertySource, this.properties, this.ignoreImports, updatedChildren); this.configurationPropertySource, this.properties, this.ignoreImports, updatedChildren);
} }
...@@ -249,11 +250,11 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment ...@@ -249,11 +250,11 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment
* Factory method to create a {@link Kind#INITIAL_IMPORT initial import} contributor. * Factory method to create a {@link Kind#INITIAL_IMPORT initial import} contributor.
* This contributor is used to trigger initial imports of additional contributors. It * This contributor is used to trigger initial imports of additional contributors. It
* does not contribute any properties itself. * does not contribute any properties itself.
* @param importLocation the initial import location (with placeholder resolved) * @param initialImport the initial import location (with placeholders resolved)
* @return a new {@link ConfigDataEnvironmentContributor} instance * @return a new {@link ConfigDataEnvironmentContributor} instance
*/ */
static ConfigDataEnvironmentContributor ofInitialImport(String importLocation) { static ConfigDataEnvironmentContributor ofInitialImport(ConfigDataLocation initialImport) {
List<String> imports = Collections.singletonList(importLocation); List<ConfigDataLocation> imports = Collections.singletonList(initialImport);
ConfigDataProperties properties = new ConfigDataProperties(imports, null); ConfigDataProperties properties = new ConfigDataProperties(imports, null);
return new ConfigDataEnvironmentContributor(Kind.INITIAL_IMPORT, null, null, null, properties, false, null); return new ConfigDataEnvironmentContributor(Kind.INITIAL_IMPORT, null, null, null, properties, false, null);
} }
...@@ -274,17 +275,17 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment ...@@ -274,17 +275,17 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment
* Factory method to create an {@link Kind#UNBOUND_IMPORT unbound import} contributor. * Factory method to create an {@link Kind#UNBOUND_IMPORT unbound import} contributor.
* This contributor has been actively imported from another contributor and may itself * This contributor has been actively imported from another contributor and may itself
* import further contributors later. * import further contributors later.
* @param location the location of imported config data * @param resource the condig data resource
* @param configData the config data * @param configData the config data
* @param propertySourceIndex the index of the property source that should be used * @param propertySourceIndex the index of the property source that should be used
* @return a new {@link ConfigDataEnvironmentContributor} instance * @return a new {@link ConfigDataEnvironmentContributor} instance
*/ */
static ConfigDataEnvironmentContributor ofUnboundImport(ConfigDataLocation location, ConfigData configData, static ConfigDataEnvironmentContributor ofUnboundImport(ConfigDataResource resource, ConfigData configData,
int propertySourceIndex) { int propertySourceIndex) {
PropertySource<?> propertySource = configData.getPropertySources().get(propertySourceIndex); PropertySource<?> propertySource = configData.getPropertySources().get(propertySourceIndex);
ConfigurationPropertySource configurationPropertySource = ConfigurationPropertySource.from(propertySource); ConfigurationPropertySource configurationPropertySource = ConfigurationPropertySource.from(propertySource);
boolean ignoreImports = configData.getOptions().contains(ConfigData.Option.IGNORE_IMPORTS); boolean ignoreImports = configData.getOptions().contains(ConfigData.Option.IGNORE_IMPORTS);
return new ConfigDataEnvironmentContributor(Kind.UNBOUND_IMPORT, location, propertySource, return new ConfigDataEnvironmentContributor(Kind.UNBOUND_IMPORT, resource, propertySource,
configurationPropertySource, null, ignoreImports, null); configurationPropertySource, null, ignoreImports, null);
} }
......
...@@ -64,9 +64,9 @@ class ConfigDataEnvironmentContributorPlaceholdersResolver implements Placeholde ...@@ -64,9 +64,9 @@ class ConfigDataEnvironmentContributorPlaceholdersResolver implements Placeholde
Object value = (propertySource != null) ? propertySource.getProperty(placeholder) : null; Object value = (propertySource != null) ? propertySource.getProperty(placeholder) : null;
if (value != null && !contributor.isActive(this.activationContext)) { if (value != null && !contributor.isActive(this.activationContext)) {
if (this.failOnResolveFromInactiveContributor) { if (this.failOnResolveFromInactiveContributor) {
ConfigDataResource resource = contributor.getResource();
Origin origin = OriginLookup.getOrigin(propertySource, placeholder); Origin origin = OriginLookup.getOrigin(propertySource, placeholder);
throw new InactiveConfigDataAccessException(propertySource, contributor.getLocation(), placeholder, throw new InactiveConfigDataAccessException(propertySource, resource, placeholder, origin);
origin);
} }
value = null; value = null;
} }
......
...@@ -39,7 +39,6 @@ import org.springframework.boot.context.properties.bind.PlaceholdersResolver; ...@@ -39,7 +39,6 @@ import org.springframework.boot.context.properties.bind.PlaceholdersResolver;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource; import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.boot.origin.Origin;
import org.springframework.core.log.LogMessage; import org.springframework.core.log.LogMessage;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
...@@ -114,12 +113,12 @@ class ConfigDataEnvironmentContributors implements Iterable<ConfigDataEnvironmen ...@@ -114,12 +113,12 @@ class ConfigDataEnvironmentContributors implements Iterable<ConfigDataEnvironmen
ConfigDataLocationResolverContext locationResolverContext = new ContributorConfigDataLocationResolverContext( ConfigDataLocationResolverContext locationResolverContext = new ContributorConfigDataLocationResolverContext(
result, contributor, activationContext); result, contributor, activationContext);
ConfigDataLoaderContext loaderContext = new ContributorDataLoaderContext(this); ConfigDataLoaderContext loaderContext = new ContributorDataLoaderContext(this);
List<String> imports = contributor.getImports(); List<ConfigDataLocation> imports = contributor.getImports();
this.logger.trace(LogMessage.format("Processing imports %s", imports)); this.logger.trace(LogMessage.format("Processing imports %s", imports));
Map<ConfigDataLocation, ConfigData> imported = importer.resolveAndLoad(activationContext, Map<ConfigDataResource, ConfigData> imported = importer.resolveAndLoad(activationContext,
locationResolverContext, loaderContext, imports); locationResolverContext, loaderContext, imports);
this.logger.trace(LogMessage.of(() -> imported.isEmpty() ? "Nothing imported" : "Imported " this.logger.trace(LogMessage.of(() -> imported.isEmpty() ? "Nothing imported" : "Imported "
+ imported.size() + " location " + ((imported.size() != 1) ? "s" : "") + imported.keySet())); + imported.size() + " resource " + ((imported.size() != 1) ? "s" : "") + imported.keySet()));
ConfigDataEnvironmentContributor contributorAndChildren = contributor.withChildren(importPhase, ConfigDataEnvironmentContributor contributorAndChildren = contributor.withChildren(importPhase,
asContributors(imported)); asContributors(imported));
result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext, result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext,
...@@ -148,7 +147,7 @@ class ConfigDataEnvironmentContributors implements Iterable<ConfigDataEnvironmen ...@@ -148,7 +147,7 @@ class ConfigDataEnvironmentContributors implements Iterable<ConfigDataEnvironmen
return contributor.isActive(activationContext) && contributor.hasUnprocessedImports(importPhase); return contributor.isActive(activationContext) && contributor.hasUnprocessedImports(importPhase);
} }
private List<ConfigDataEnvironmentContributor> asContributors(Map<ConfigDataLocation, ConfigData> imported) { private List<ConfigDataEnvironmentContributor> asContributors(Map<ConfigDataResource, ConfigData> imported) {
List<ConfigDataEnvironmentContributor> contributors = new ArrayList<>(imported.size() * 5); List<ConfigDataEnvironmentContributor> contributors = new ArrayList<>(imported.size() * 5);
imported.forEach((location, data) -> { imported.forEach((location, data) -> {
for (int i = data.getPropertySources().size() - 1; i >= 0; i--) { for (int i = data.getPropertySources().size() - 1; i >= 0; i--) {
...@@ -259,8 +258,8 @@ class ConfigDataEnvironmentContributors implements Iterable<ConfigDataEnvironmen ...@@ -259,8 +258,8 @@ class ConfigDataEnvironmentContributors implements Iterable<ConfigDataEnvironmen
} }
@Override @Override
public ConfigDataLocation getParent() { public ConfigDataResource getParent() {
return this.contributor.getLocation(); return this.contributor.getResource();
} }
@Override @Override
...@@ -268,11 +267,6 @@ class ConfigDataEnvironmentContributors implements Iterable<ConfigDataEnvironmen ...@@ -268,11 +267,6 @@ class ConfigDataEnvironmentContributors implements Iterable<ConfigDataEnvironmen
return this.contributors.getBootstrapContext(); return this.contributors.getBootstrapContext();
} }
@Override
public Origin getLocationOrigin(String location) {
return this.contributor.getImportOrigin(location);
}
} }
private class InactiveSourceChecker implements BindHandler { private class InactiveSourceChecker implements BindHandler {
......
...@@ -53,9 +53,9 @@ public class ConfigDataEnvironmentPostProcessor implements EnvironmentPostProces ...@@ -53,9 +53,9 @@ public class ConfigDataEnvironmentPostProcessor implements EnvironmentPostProces
/** /**
* Property used to determine what action to take when a * Property used to determine what action to take when a
* {@code ConfigDataLocationNotFoundException} is thrown. * {@code ConfigDataLocationNotFoundException} is thrown.
* @see ConfigDataLocationNotFoundAction * @see ConfigDataNotFoundAction
*/ */
public static final String ON_LOCATION_NOT_FOUND_PROPERTY = ConfigDataEnvironment.ON_LOCATION_NOT_FOUND_PROPERTY; public static final String ON_LOCATION_NOT_FOUND_PROPERTY = ConfigDataEnvironment.ON_NOT_FOUND_PROPERTY;
private final DeferredLogFactory logFactory; private final DeferredLogFactory logFactory;
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
package org.springframework.boot.context.config; package org.springframework.boot.context.config;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
...@@ -24,9 +25,13 @@ import java.util.List; ...@@ -24,9 +25,13 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.apache.commons.logging.Log;
import org.springframework.boot.logging.DeferredLogFactory;
/** /**
* Imports {@link ConfigData} by {@link ConfigDataLocationResolver resolving} and * Imports {@link ConfigData} by {@link ConfigDataLocationResolver resolving} and
* {@link ConfigDataLoader loading} imports. {@link ConfigDataLocation locations} are * {@link ConfigDataLoader loading} locations. {@link ConfigDataResource resources} are
* tracked to ensure that they are not imported multiple times. * tracked to ensure that they are not imported multiple times.
* *
* @author Phillip Webb * @author Phillip Webb
...@@ -34,20 +39,29 @@ import java.util.Set; ...@@ -34,20 +39,29 @@ import java.util.Set;
*/ */
class ConfigDataImporter { class ConfigDataImporter {
private final Log logger;
private final ConfigDataLocationResolvers resolvers; private final ConfigDataLocationResolvers resolvers;
private final ConfigDataLoaders loaders; private final ConfigDataLoaders loaders;
private final Set<ConfigDataLocation> loadedLocations = new HashSet<>(); private final ConfigDataNotFoundAction notFoundAction;
private final Set<ConfigDataResource> loaded = new HashSet<>();
/** /**
* Create a new {@link ConfigDataImporter} instance. * Create a new {@link ConfigDataImporter} instance.
* @param logFactory the log factory
* @param notFoundAction the action to take when a location cannot be found
* @param resolvers the config data location resolvers * @param resolvers the config data location resolvers
* @param loaders the config data loaders * @param loaders the config data loaders
*/ */
ConfigDataImporter(ConfigDataLocationResolvers resolvers, ConfigDataLoaders loaders) { ConfigDataImporter(DeferredLogFactory logFactory, ConfigDataNotFoundAction notFoundAction,
ConfigDataLocationResolvers resolvers, ConfigDataLoaders loaders) {
this.logger = logFactory.getLog(getClass());
this.resolvers = resolvers; this.resolvers = resolvers;
this.loaders = loaders; this.loaders = loaders;
this.notFoundAction = notFoundAction;
} }
/** /**
...@@ -59,31 +73,70 @@ class ConfigDataImporter { ...@@ -59,31 +73,70 @@ class ConfigDataImporter {
* @param locations the locations to resolve * @param locations the locations to resolve
* @return a map of the loaded locations and data * @return a map of the loaded locations and data
*/ */
Map<ConfigDataLocation, ConfigData> resolveAndLoad(ConfigDataActivationContext activationContext, Map<ConfigDataResource, ConfigData> resolveAndLoad(ConfigDataActivationContext activationContext,
ConfigDataLocationResolverContext locationResolverContext, ConfigDataLoaderContext loaderContext, ConfigDataLocationResolverContext locationResolverContext, ConfigDataLoaderContext loaderContext,
List<String> locations) { List<ConfigDataLocation> locations) {
try { try {
Profiles profiles = (activationContext != null) ? activationContext.getProfiles() : null; Profiles profiles = (activationContext != null) ? activationContext.getProfiles() : null;
return load(loaderContext, this.resolvers.resolveAll(locationResolverContext, locations, profiles)); List<ConfigDataResolutionResult> resolved = resolve(locationResolverContext, profiles, locations);
return load(loaderContext, resolved);
} }
catch (IOException ex) { catch (IOException ex) {
throw new IllegalStateException("IO error on loading imports from " + locations, ex); throw new IllegalStateException("IO error on loading imports from " + locations, ex);
} }
} }
private Map<ConfigDataLocation, ConfigData> load(ConfigDataLoaderContext loaderContext, private List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolverContext locationResolverContext,
List<ConfigDataLocation> locations) throws IOException { Profiles profiles, List<ConfigDataLocation> locations) {
Map<ConfigDataLocation, ConfigData> result = new LinkedHashMap<>(); List<ConfigDataResolutionResult> resolved = new ArrayList<>(locations.size());
for (int i = locations.size() - 1; i >= 0; i--) { for (ConfigDataLocation location : locations) {
ConfigDataLocation location = locations.get(i); resolved.addAll(resolve(locationResolverContext, profiles, location));
if (this.loadedLocations.add(location)) { }
ConfigData loaded = this.loaders.load(loaderContext, location); return Collections.unmodifiableList(resolved);
if (loaded != null) { }
result.put(location, loaded);
private List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolverContext locationResolverContext,
Profiles profiles, ConfigDataLocation location) {
try {
return this.resolvers.resolve(locationResolverContext, location, profiles);
}
catch (ConfigDataNotFoundException ex) {
handle(ex, location);
return Collections.emptyList();
}
}
private Map<ConfigDataResource, ConfigData> load(ConfigDataLoaderContext loaderContext,
List<ConfigDataResolutionResult> candidates) throws IOException {
Map<ConfigDataResource, ConfigData> result = new LinkedHashMap<>();
for (int i = candidates.size() - 1; i >= 0; i--) {
ConfigDataResolutionResult candidate = candidates.get(i);
ConfigDataLocation location = candidate.getLocation();
ConfigDataResource resource = candidate.getResource();
if (this.loaded.add(resource)) {
try {
ConfigData loaded = this.loaders.load(loaderContext, resource);
if (loaded != null) {
result.put(resource, loaded);
}
}
catch (ConfigDataNotFoundException ex) {
handle(ex, location);
} }
} }
} }
return Collections.unmodifiableMap(result); return Collections.unmodifiableMap(result);
} }
private void handle(ConfigDataNotFoundException ex, ConfigDataLocation location) {
if (ex instanceof ConfigDataResourceNotFoundException) {
ex = ((ConfigDataResourceNotFoundException) ex).withLocation(location);
}
getNotFoundAction(location).handle(this.logger, ex);
}
private ConfigDataNotFoundAction getNotFoundAction(ConfigDataLocation location) {
return (!location.isOptional()) ? this.notFoundAction : ConfigDataNotFoundAction.IGNORE;
}
} }
...@@ -25,8 +25,8 @@ import org.springframework.boot.BootstrapRegistry; ...@@ -25,8 +25,8 @@ import org.springframework.boot.BootstrapRegistry;
import org.springframework.boot.ConfigurableBootstrapContext; import org.springframework.boot.ConfigurableBootstrapContext;
/** /**
* Strategy class that can be used used to load {@link ConfigData} instances from a * Strategy class that can be used used to load {@link ConfigData} for a given
* {@link ConfigDataLocation location}. Implementations should be added as a * {@link ConfigDataResource}. Implementations should be added as a
* {@code spring.factories} entries. The following constructor parameter types are * {@code spring.factories} entries. The following constructor parameter types are
* supported: * supported:
* <ul> * <ul>
...@@ -36,34 +36,34 @@ import org.springframework.boot.ConfigurableBootstrapContext; ...@@ -36,34 +36,34 @@ import org.springframework.boot.ConfigurableBootstrapContext;
* ({@link BootstrapContext} or {@link BootstrapRegistry} may also be used).</li> * ({@link BootstrapContext} or {@link BootstrapRegistry} may also be used).</li>
* </ul> * </ul>
* <p> * <p>
* Multiple loaders cannot claim the same location. * Multiple loaders cannot claim the same resource.
* *
* @param <L> the location type * @param <R> the resource type
* @author Phillip Webb * @author Phillip Webb
* @author Madhura Bhave * @author Madhura Bhave
* @since 2.4.0 * @since 2.4.0
*/ */
public interface ConfigDataLoader<L extends ConfigDataLocation> { public interface ConfigDataLoader<R extends ConfigDataResource> {
/** /**
* Returns if the specified location can be loaded by this instance. * Returns if the specified resource can be loaded by this instance.
* @param context the loader context * @param context the loader context
* @param location the location to check. * @param resource the resource to check.
* @return if the location is supported by this loader * @return if the resource is supported by this loader
*/ */
default boolean isLoadable(ConfigDataLoaderContext context, L location) { default boolean isLoadable(ConfigDataLoaderContext context, R resource) {
return true; return true;
} }
/** /**
* Load {@link ConfigData} for the given location. * Load {@link ConfigData} for the given resource.
* @param context the loader context * @param context the loader context
* @param location the location to load * @param resource the resource to load
* @return the loaded config data or {@code null} if the location should be skipped * @return the loaded config data or {@code null} if the location should be skipped
* @throws IOException on IO error * @throws IOException on IO error
* @throws ConfigDataLocationNotFoundException if the location cannot be found * @throws ConfigDataResourceNotFoundException if the resource cannot be found
*/ */
ConfigData load(ConfigDataLoaderContext context, L location) ConfigData load(ConfigDataLoaderContext context, R resource)
throws IOException, ConfigDataLocationNotFoundException; throws IOException, ConfigDataResourceNotFoundException;
} }
...@@ -43,37 +43,28 @@ class ConfigDataLoaders { ...@@ -43,37 +43,28 @@ class ConfigDataLoaders {
private final Log logger; private final Log logger;
private final ConfigDataLocationNotFoundAction locationNotFoundAction;
private final List<ConfigDataLoader<?>> loaders; private final List<ConfigDataLoader<?>> loaders;
private final List<Class<?>> locationTypes; private final List<Class<?>> resourceTypes;
/** /**
* Create a new {@link ConfigDataLoaders} instance. * Create a new {@link ConfigDataLoaders} instance.
* @param logFactory the deferred log factory * @param logFactory the deferred log factory
* @param bootstrapContext the bootstrap context * @param bootstrapContext the bootstrap context
* @param locationNotFoundAction the action to take if a
* {@link ConfigDataLocationNotFoundException} is thrown
*/ */
ConfigDataLoaders(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext, ConfigDataLoaders(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext) {
ConfigDataLocationNotFoundAction locationNotFoundAction) { this(logFactory, bootstrapContext, SpringFactoriesLoader.loadFactoryNames(ConfigDataLoader.class, null));
this(logFactory, bootstrapContext, locationNotFoundAction,
SpringFactoriesLoader.loadFactoryNames(ConfigDataLoader.class, null));
} }
/** /**
* Create a new {@link ConfigDataLoaders} instance. * Create a new {@link ConfigDataLoaders} instance.
* @param logFactory the deferred log factory * @param logFactory the deferred log factory
* @param bootstrapContext the bootstrap context * @param bootstrapContext the bootstrap context
* @param locationNotFoundAction the action to take if a
* {@link ConfigDataLocationNotFoundException} is thrown
* @param names the {@link ConfigDataLoader} class names instantiate * @param names the {@link ConfigDataLoader} class names instantiate
*/ */
ConfigDataLoaders(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext, ConfigDataLoaders(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
ConfigDataLocationNotFoundAction locationNotFoundAction, List<String> names) { List<String> names) {
this.logger = logFactory.getLog(getClass()); this.logger = logFactory.getLog(getClass());
this.locationNotFoundAction = locationNotFoundAction;
Instantiator<ConfigDataLoader<?>> instantiator = new Instantiator<>(ConfigDataLoader.class, Instantiator<ConfigDataLoader<?>> instantiator = new Instantiator<>(ConfigDataLoader.class,
(availableParameters) -> { (availableParameters) -> {
availableParameters.add(Log.class, logFactory::getLog); availableParameters.add(Log.class, logFactory::getLog);
...@@ -82,69 +73,52 @@ class ConfigDataLoaders { ...@@ -82,69 +73,52 @@ class ConfigDataLoaders {
availableParameters.add(BootstrapRegistry.class, bootstrapContext); availableParameters.add(BootstrapRegistry.class, bootstrapContext);
}); });
this.loaders = instantiator.instantiate(names); this.loaders = instantiator.instantiate(names);
this.locationTypes = getLocationTypes(this.loaders); this.resourceTypes = getResourceTypes(this.loaders);
} }
private List<Class<?>> getLocationTypes(List<ConfigDataLoader<?>> loaders) { private List<Class<?>> getResourceTypes(List<ConfigDataLoader<?>> loaders) {
List<Class<?>> locationTypes = new ArrayList<>(loaders.size()); List<Class<?>> resourceTypes = new ArrayList<>(loaders.size());
for (ConfigDataLoader<?> loader : loaders) { for (ConfigDataLoader<?> loader : loaders) {
locationTypes.add(getLocationType(loader)); resourceTypes.add(getResourceType(loader));
} }
return Collections.unmodifiableList(locationTypes); return Collections.unmodifiableList(resourceTypes);
} }
private Class<?> getLocationType(ConfigDataLoader<?> loader) { private Class<?> getResourceType(ConfigDataLoader<?> loader) {
return ResolvableType.forClass(loader.getClass()).as(ConfigDataLoader.class).resolveGeneric(); return ResolvableType.forClass(loader.getClass()).as(ConfigDataLoader.class).resolveGeneric();
} }
/** /**
* Load {@link ConfigData} using the first appropriate {@link ConfigDataLoader}. * Load {@link ConfigData} using the first appropriate {@link ConfigDataLoader}.
* @param <L> the config data location type * @param <R> the resource type
* @param context the loader context * @param context the loader context
* @param location the location to load * @param resource the resource to load
* @return the loaded {@link ConfigData} * @return the loaded {@link ConfigData}
* @throws IOException on IO error * @throws IOException on IO error
*/ */
<L extends ConfigDataLocation> ConfigData load(ConfigDataLoaderContext context, L location) throws IOException { <R extends ConfigDataResource> ConfigData load(ConfigDataLoaderContext context, R resource) throws IOException {
boolean optional = location instanceof OptionalConfigDataLocation; ConfigDataLoader<R> loader = getLoader(context, resource);
location = (!optional) ? location : OptionalConfigDataLocation.unwrap(location); this.logger.trace(LogMessage.of(() -> "Loading " + resource + " using loader " + loader.getClass().getName()));
return load(context, optional, location); return loader.load(context, resource);
}
private <L extends ConfigDataLocation> ConfigData load(ConfigDataLoaderContext context, boolean optional,
L location) throws IOException {
ConfigDataLoader<L> loader = getLoader(context, location);
this.logger.trace(LogMessage.of(() -> "Loading " + location + " using loader " + loader.getClass().getName()));
try {
return loader.load(context, location);
}
catch (ConfigDataLocationNotFoundException ex) {
if (optional) {
this.logger.trace(LogMessage.format("Skipping missing resource from optional location %s", location));
return null;
}
this.locationNotFoundAction.handle(this.logger, location, ex);
return null;
}
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private <L extends ConfigDataLocation> ConfigDataLoader<L> getLoader(ConfigDataLoaderContext context, L location) { private <R extends ConfigDataResource> ConfigDataLoader<R> getLoader(ConfigDataLoaderContext context, R resource) {
ConfigDataLoader<L> result = null; ConfigDataLoader<R> result = null;
for (int i = 0; i < this.loaders.size(); i++) { for (int i = 0; i < this.loaders.size(); i++) {
ConfigDataLoader<?> candidate = this.loaders.get(i); ConfigDataLoader<?> candidate = this.loaders.get(i);
if (this.locationTypes.get(i).isInstance(location)) { if (this.resourceTypes.get(i).isInstance(resource)) {
ConfigDataLoader<L> loader = (ConfigDataLoader<L>) candidate; ConfigDataLoader<R> loader = (ConfigDataLoader<R>) candidate;
if (loader.isLoadable(context, location)) { if (loader.isLoadable(context, resource)) {
if (result != null) { if (result != null) {
throw new IllegalStateException("Multiple loaders found for location " + location + " [" throw new IllegalStateException("Multiple loaders found for resource '" + resource + "' ["
+ candidate.getClass().getName() + "," + result.getClass().getName() + "]"); + candidate.getClass().getName() + "," + result.getClass().getName() + "]");
} }
result = loader; result = loader;
} }
} }
} }
Assert.state(result != null, () -> "No loader found for location '" + location + "'"); Assert.state(result != null, () -> "No loader found for resource '" + resource + "'");
return result; return result;
} }
......
...@@ -16,20 +16,131 @@ ...@@ -16,20 +16,131 @@
package org.springframework.boot.context.config; package org.springframework.boot.context.config;
import org.springframework.boot.origin.Origin;
import org.springframework.boot.origin.OriginProvider;
import org.springframework.util.StringUtils;
/** /**
* A location from which {@link ConfigData} can be loaded. Implementations must implement * A user specified location that can be {@link ConfigDataLocationResolver resolved} to
* a valid {@link #equals(Object) equals}, {@link #hashCode() hashCode} and * one or {@link ConfigDataResource config data resources}. A {@link ConfigDataLocation}
* {@link #toString() toString} methods. * is a simple wrapper around a {@link String} value. The exact format of the value will
* depend on the underlying technology, but is usually a URL like syntax consisting of a
* prefix and path. For example, {@code crypt:somehost/somepath}.
* <p>
* Locations can be mandatory or {@link #isOptional() optional}. Optional locations are
* prefixed with {@code optional:}.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Madhura Bhave
* @since 2.4.0 * @since 2.4.0
*/ */
public abstract class ConfigDataLocation { public final class ConfigDataLocation implements OriginProvider {
/** /**
* Prefix used to indicate that a {@link ConfigDataLocation} is optional. * Prefix used to indicate that a {@link ConfigDataResource} is optional.
*/ */
public static final String OPTIONAL_PREFIX = "optional:"; public static final String OPTIONAL_PREFIX = "optional:";
private final boolean optional;
private final String value;
private final Origin origin;
private ConfigDataLocation(boolean optional, String value, Origin origin) {
this.value = value;
this.optional = optional;
this.origin = origin;
}
/**
* Return the the location is optional and should ignore
* {@link ConfigDataNotFoundException}.
* @return if the location is optional
*/
public boolean isOptional() {
return this.optional;
}
/**
* Return the value of the location (always excluding any user specified
* {@code optional:} prefix.
* @return the location value
*/
public String getValue() {
return this.value;
}
/**
* Return if {@link #getValue()} has the specified prefix.
* @param prefix the prefix to check
* @return if the value has the prefix
*/
public boolean hasPrefix(String prefix) {
return this.value.startsWith(prefix);
}
/**
* Return {@link #getValue()} with the specified prefix removed. If the location does
* not have the given prefix then the {@link #getValue()} is returned unchanged.
* @param prefix the prefix to check
* @return the value with the prefix removed
*/
public String getNonPrefixedValue(String prefix) {
if (hasPrefix(prefix)) {
return this.value.substring(prefix.length());
}
return this.value;
}
@Override
public Origin getOrigin() {
return this.origin;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
ConfigDataLocation other = (ConfigDataLocation) obj;
return this.value.equals(other.value);
}
@Override
public int hashCode() {
return this.value.hashCode();
}
@Override
public String toString() {
return (!this.optional) ? this.value : OPTIONAL_PREFIX + this.value;
}
/**
* Create a new {@link ConfigDataLocation} with a specific {@link Origin}.
* @param origin the orgin to set
* @return a new {@link ConfigDataLocation} instance.
*/
ConfigDataLocation withOrigin(Origin origin) {
return new ConfigDataLocation(this.optional, this.value, origin);
}
/**
* Factory method to create a new {@link ConfigDataLocation} from a string.
* @param location the location string
* @return a {@link ConfigDataLocation} instance or {@code null} if no location was
* provided
*/
public static ConfigDataLocation of(String location) {
boolean optional = location != null && location.startsWith(OPTIONAL_PREFIX);
String value = (!optional) ? location : location.substring(OPTIONAL_PREFIX.length());
if (!StringUtils.hasText(value)) {
return null;
}
return new ConfigDataLocation(optional, value, null);
}
} }
/*
* 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.List;
import org.springframework.boot.context.properties.bind.AbstractBindHandler;
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.source.ConfigurationPropertyName;
import org.springframework.boot.origin.Origin;
/**
* {@link BindHandler} to set the {@link Origin} of bound {@link ConfigDataLocation}
* objects.
*
* @author Phillip Webb
*/
class ConfigDataLocationBindHandler extends AbstractBindHandler {
@Override
@SuppressWarnings("unchecked")
public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target, BindContext context, Object result) {
if (result instanceof ConfigDataLocation) {
return withOrigin(context, (ConfigDataLocation) result);
}
if (result instanceof List) {
List<Object> list = (List<Object>) result;
for (int i = 0; i < list.size(); i++) {
Object element = list.get(i);
if (element instanceof ConfigDataLocation) {
list.set(i, withOrigin(context, (ConfigDataLocation) element));
}
}
}
if (result instanceof ConfigDataLocation[]) {
ConfigDataLocation[] locations = (ConfigDataLocation[]) result;
for (int i = 0; i < locations.length; i++) {
locations[i] = withOrigin(context, locations[i]);
}
}
return result;
}
private ConfigDataLocation withOrigin(BindContext context, ConfigDataLocation result) {
if (result.getOrigin() != null) {
return result;
}
Origin origin = Origin.from(context.getConfigurationProperty());
return result.withOrigin(origin);
}
}
...@@ -16,25 +16,23 @@ ...@@ -16,25 +16,23 @@
package org.springframework.boot.context.config; package org.springframework.boot.context.config;
import java.io.File; import org.springframework.boot.origin.Origin;
import java.nio.file.Files; import org.springframework.util.Assert;
import java.nio.file.Path;
import org.springframework.core.io.Resource;
/** /**
* Exception thrown when a config data location cannot be found. * {@link ConfigDataNotFoundException} thrown when a {@link ConfigDataLocation} cannot be
* found.
* *
* @author Phillip Webb * @author Phillip Webb
* @since 2.4.0 * @since 2.4.0
*/ */
public class ConfigDataLocationNotFoundException extends ConfigDataException { public class ConfigDataLocationNotFoundException extends ConfigDataNotFoundException {
private final ConfigDataLocation location; private final ConfigDataLocation location;
/** /**
* Create a new {@link ConfigDataLocationNotFoundException} instance. * Create a new {@link ConfigDataLocationNotFoundException} instance.
* @param location the location that was not found * @param location the location that could not be found
*/ */
public ConfigDataLocationNotFoundException(ConfigDataLocation location) { public ConfigDataLocationNotFoundException(ConfigDataLocation location) {
this(location, null); this(location, null);
...@@ -42,79 +40,39 @@ public class ConfigDataLocationNotFoundException extends ConfigDataException { ...@@ -42,79 +40,39 @@ public class ConfigDataLocationNotFoundException extends ConfigDataException {
/** /**
* Create a new {@link ConfigDataLocationNotFoundException} instance. * Create a new {@link ConfigDataLocationNotFoundException} instance.
* @param location the location that was not found * @param location the location that could not be found
* @param cause the cause of the exception * @param cause the exception cause
*/ */
public ConfigDataLocationNotFoundException(ConfigDataLocation location, Throwable cause) { public ConfigDataLocationNotFoundException(ConfigDataLocation location, Throwable cause) {
this(getMessage(location), location, cause); super(getMessage(location), cause);
} Assert.notNull(location, "Location must not be null");
/**
* Create a new {@link ConfigDataLocationNotFoundException} instance.
* @param message the exception message
* @param location the location that was not found
*/
public ConfigDataLocationNotFoundException(String message, ConfigDataLocation location) {
this(message, location, null);
}
/**
* Create a new {@link ConfigDataLocationNotFoundException} instance.
* @param message the exception message
* @param location the location that was not found
* @param cause the cause of the exception
*/
public ConfigDataLocationNotFoundException(String message, ConfigDataLocation location, Throwable cause) {
super(message, cause);
this.location = location; this.location = location;
} }
/** /**
* Return the location that could not be found. * Return the location that could not be found.
* @return the location that could not be found. * @return the location
*/ */
public ConfigDataLocation getLocation() { public ConfigDataLocation getLocation() {
return this.location; return this.location;
} }
private static String getMessage(ConfigDataLocation location) { @Override
return "Config data location '" + location + "' does not exist"; public Origin getOrigin() {
} return Origin.from(this.location);
/**
* Throw a {@link ConfigDataLocationNotFoundException} if the specified {@link Path}
* does not exist.
* @param location the location being checked
* @param path the path to check
*/
public static void throwIfDoesNotExist(ConfigDataLocation location, Path path) {
throwIfDoesNotExist(location, Files.exists(path));
} }
/** @Override
* Throw a {@link ConfigDataLocationNotFoundException} if the specified {@link File} public String getReferenceDescription() {
* does not exist. return getReferenceDescription(this.location);
* @param location the location being checked
* @param file the file to check
*/
public static void throwIfDoesNotExist(ConfigDataLocation location, File file) {
throwIfDoesNotExist(location, file.exists());
} }
/** private static String getMessage(ConfigDataLocation location) {
* Throw a {@link ConfigDataLocationNotFoundException} if the specified return String.format("Config data %s cannot be found", getReferenceDescription(location));
* {@link Resource} does not exist.
* @param location the location being checked
* @param resource the resource to check
*/
public static void throwIfDoesNotExist(ConfigDataLocation location, Resource resource) {
throwIfDoesNotExist(location, resource.exists());
} }
private static void throwIfDoesNotExist(ConfigDataLocation location, boolean exists) { private static String getReferenceDescription(ConfigDataLocation location) {
if (!exists) { return String.format("location '%s'", location);
throw new ConfigDataLocationNotFoundException(location);
}
} }
} }
...@@ -31,9 +31,10 @@ import org.springframework.core.env.Environment; ...@@ -31,9 +31,10 @@ import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
/** /**
* Strategy interface used to resolve {@link ConfigDataLocation locations} from a String * Strategy interface used to resolve {@link ConfigDataLocation locations} into one or
* based location address. Implementations should be added as a {@code spring.factories} * more {@link ConfigDataResource resources}. Implementations should be added as a
* entries. The following constructor parameter types are supported: * {@code spring.factories} entries. The following constructor parameter types are
* supported:
* <ul> * <ul>
* <li>{@link Log} - if the resolver needs deferred logging</li> * <li>{@link Log} - if the resolver needs deferred logging</li>
* <li>{@link Binder} - if the resolver needs to obtain values from the initial * <li>{@link Binder} - if the resolver needs to obtain values from the initial
...@@ -47,12 +48,12 @@ import org.springframework.core.io.ResourceLoader; ...@@ -47,12 +48,12 @@ import org.springframework.core.io.ResourceLoader;
* Resolvers may implement {@link Ordered} or use the {@link Order @Order} annotation. The * Resolvers may implement {@link Ordered} or use the {@link Order @Order} annotation. The
* first resolver that supports the given location will be used. * first resolver that supports the given location will be used.
* *
* @param <L> the location type * @param <R> the location type
* @author Phillip Webb * @author Phillip Webb
* @author Madhura Bhave * @author Madhura Bhave
* @since 2.4.0 * @since 2.4.0
*/ */
public interface ConfigDataLocationResolver<L extends ConfigDataLocation> { public interface ConfigDataLocationResolver<R extends ConfigDataResource> {
/** /**
* Returns if the specified location address can be resolved by this resolver. * Returns if the specified location address can be resolved by this resolver.
...@@ -60,35 +61,34 @@ public interface ConfigDataLocationResolver<L extends ConfigDataLocation> { ...@@ -60,35 +61,34 @@ public interface ConfigDataLocationResolver<L extends ConfigDataLocation> {
* @param location the location to check. * @param location the location to check.
* @return if the location is supported by this resolver * @return if the location is supported by this resolver
*/ */
boolean isResolvable(ConfigDataLocationResolverContext context, String location); boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location);
/** /**
* Resolve a location string into one or more {@link ConfigDataLocation} instances. * Resolve a {@link ConfigDataLocation} into one or more {@link ConfigDataResource}
* instances.
* @param context the location resolver context * @param context the location resolver context
* @param location the location that should be resolved * @param location the location that should be resolved
* @param optional if the location is optional * @return a list of {@link ConfigDataResource resources} in ascending priority order.
* @return a list of resolved locations in ascending priority order. If the same key
* is contained in more than one of the location, then the later source will win.
* @throws ConfigDataLocationNotFoundException on a non-optional location that cannot * @throws ConfigDataLocationNotFoundException on a non-optional location that cannot
* be found * be found
* @throws ConfigDataResourceNotFoundException if a resolved resource cannot be found
*/ */
List<L> resolve(ConfigDataLocationResolverContext context, String location, boolean optional) List<R> resolve(ConfigDataLocationResolverContext context, ConfigDataLocation location)
throws ConfigDataLocationNotFoundException; throws ConfigDataLocationNotFoundException, ConfigDataResourceNotFoundException;
/** /**
* Resolve a location string into one or more {@link ConfigDataLocation} instances * Resolve a {@link ConfigDataLocation} into one or more {@link ConfigDataResource}
* based on available profiles. This method is called once profiles have been deduced * instances based on available profiles. This method is called once profiles have
* from the contributed values. By default this method returns an empty list. * been deduced from the contributed values. By default this method returns an empty
* list.
* @param context the location resolver context * @param context the location resolver context
* @param location the location that should be resolved * @param location the location that should be resolved
* @param optional if the location is optional
* @param profiles profile information * @param profiles profile information
* @return a list of resolved locations in ascending priority order.If the same key is * @return a list of resolved locations in ascending priority order.
* contained in more than one of the location, then the later source will win.
* @throws ConfigDataLocationNotFoundException on a non-optional location that cannot * @throws ConfigDataLocationNotFoundException on a non-optional location that cannot
* be found * be found
*/ */
default List<L> resolveProfileSpecific(ConfigDataLocationResolverContext context, String location, boolean optional, default List<R> resolveProfileSpecific(ConfigDataLocationResolverContext context, ConfigDataLocation location,
Profiles profiles) throws ConfigDataLocationNotFoundException { Profiles profiles) throws ConfigDataLocationNotFoundException {
return Collections.emptyList(); return Collections.emptyList();
} }
......
...@@ -19,7 +19,6 @@ package org.springframework.boot.context.config; ...@@ -19,7 +19,6 @@ package org.springframework.boot.context.config;
import org.springframework.boot.ConfigurableBootstrapContext; import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.origin.Origin;
/** /**
* Context provided to {@link ConfigDataLocationResolver} methods. * Context provided to {@link ConfigDataLocationResolver} methods.
...@@ -38,11 +37,11 @@ public interface ConfigDataLocationResolverContext { ...@@ -38,11 +37,11 @@ public interface ConfigDataLocationResolverContext {
Binder getBinder(); Binder getBinder();
/** /**
* Provides access to the parent location that triggered the resolve or {@code null} * Provides access to the parent {@link ConfigDataResource} that triggered the resolve
* if there is no available parent. * or {@code null} if there is no available parent.
* @return the parent location * @return the parent location
*/ */
ConfigDataLocation getParent(); ConfigDataResource getParent();
/** /**
* Provides access to the {@link ConfigurableBootstrapContext} shared across all * Provides access to the {@link ConfigurableBootstrapContext} shared across all
...@@ -51,11 +50,4 @@ public interface ConfigDataLocationResolverContext { ...@@ -51,11 +50,4 @@ public interface ConfigDataLocationResolverContext {
*/ */
ConfigurableBootstrapContext getBootstrapContext(); ConfigurableBootstrapContext getBootstrapContext();
/**
* Return the {@link Origin} of a location that's being resolved.
* @param location the location being resolved
* @return the {@link Origin} of the location or {@code null}
*/
Origin getLocationOrigin(String location);
} }
...@@ -32,8 +32,6 @@ import org.springframework.boot.util.Instantiator; ...@@ -32,8 +32,6 @@ import org.springframework.boot.util.Instantiator;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.log.LogMessage;
import org.springframework.util.StringUtils;
/** /**
* A collection of {@link ConfigDataLocationResolver} instances loaded via * A collection of {@link ConfigDataLocationResolver} instances loaded via
...@@ -44,24 +42,18 @@ import org.springframework.util.StringUtils; ...@@ -44,24 +42,18 @@ import org.springframework.util.StringUtils;
*/ */
class ConfigDataLocationResolvers { class ConfigDataLocationResolvers {
private final Log logger;
private final ConfigDataLocationNotFoundAction locationNotFoundAction;
private final List<ConfigDataLocationResolver<?>> resolvers; private final List<ConfigDataLocationResolver<?>> resolvers;
/** /**
* Create a new {@link ConfigDataLocationResolvers} instance. * Create a new {@link ConfigDataLocationResolvers} instance.
* @param logFactory a {@link DeferredLogFactory} used to inject {@link Log} instances * @param logFactory a {@link DeferredLogFactory} used to inject {@link Log} instances
* @param bootstrapContext the bootstrap context * @param bootstrapContext the bootstrap context
* @param locationNotFoundAction the action to take if a
* {@link ConfigDataLocationNotFoundException} is thrown
* @param binder a binder providing values from the initial {@link Environment} * @param binder a binder providing values from the initial {@link Environment}
* @param resourceLoader {@link ResourceLoader} to load resource locations * @param resourceLoader {@link ResourceLoader} to load resource locations
*/ */
ConfigDataLocationResolvers(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext, ConfigDataLocationResolvers(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
ConfigDataLocationNotFoundAction locationNotFoundAction, Binder binder, ResourceLoader resourceLoader) { Binder binder, ResourceLoader resourceLoader) {
this(logFactory, bootstrapContext, locationNotFoundAction, binder, resourceLoader, this(logFactory, bootstrapContext, binder, resourceLoader,
SpringFactoriesLoader.loadFactoryNames(ConfigDataLocationResolver.class, null)); SpringFactoriesLoader.loadFactoryNames(ConfigDataLocationResolver.class, null));
} }
...@@ -69,17 +61,12 @@ class ConfigDataLocationResolvers { ...@@ -69,17 +61,12 @@ class ConfigDataLocationResolvers {
* Create a new {@link ConfigDataLocationResolvers} instance. * Create a new {@link ConfigDataLocationResolvers} instance.
* @param logFactory a {@link DeferredLogFactory} used to inject {@link Log} instances * @param logFactory a {@link DeferredLogFactory} used to inject {@link Log} instances
* @param bootstrapContext the bootstrap context * @param bootstrapContext the bootstrap context
* @param locationNotFoundAction the action to take if a
* {@link ConfigDataLocationNotFoundException} is thrown
* @param binder {@link Binder} providing values from the initial {@link Environment} * @param binder {@link Binder} providing values from the initial {@link Environment}
* @param resourceLoader {@link ResourceLoader} to load resource locations * @param resourceLoader {@link ResourceLoader} to load resource locations
* @param names the {@link ConfigDataLocationResolver} class names * @param names the {@link ConfigDataLocationResolver} class names
*/ */
ConfigDataLocationResolvers(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext, ConfigDataLocationResolvers(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
ConfigDataLocationNotFoundAction locationNotFoundAction, Binder binder, ResourceLoader resourceLoader, Binder binder, ResourceLoader resourceLoader, List<String> names) {
List<String> names) {
this.logger = logFactory.getLog(getClass());
this.locationNotFoundAction = locationNotFoundAction;
Instantiator<ConfigDataLocationResolver<?>> instantiator = new Instantiator<>(ConfigDataLocationResolver.class, Instantiator<ConfigDataLocationResolver<?>> instantiator = new Instantiator<>(ConfigDataLocationResolver.class,
(availableParameters) -> { (availableParameters) -> {
availableParameters.add(Log.class, logFactory::getLog); availableParameters.add(Log.class, logFactory::getLog);
...@@ -94,10 +81,10 @@ class ConfigDataLocationResolvers { ...@@ -94,10 +81,10 @@ class ConfigDataLocationResolvers {
private List<ConfigDataLocationResolver<?>> reorder(List<ConfigDataLocationResolver<?>> resolvers) { private List<ConfigDataLocationResolver<?>> reorder(List<ConfigDataLocationResolver<?>> resolvers) {
List<ConfigDataLocationResolver<?>> reordered = new ArrayList<>(resolvers.size()); List<ConfigDataLocationResolver<?>> reordered = new ArrayList<>(resolvers.size());
ResourceConfigDataLocationResolver resourceResolver = null; StandardConfigDataLocationResolver resourceResolver = null;
for (ConfigDataLocationResolver<?> resolver : resolvers) { for (ConfigDataLocationResolver<?> resolver : resolvers) {
if (resolver instanceof ResourceConfigDataLocationResolver) { if (resolver instanceof StandardConfigDataLocationResolver) {
resourceResolver = (ResourceConfigDataLocationResolver) resolver; resourceResolver = (StandardConfigDataLocationResolver) resolver;
} }
else { else {
reordered.add(resolver); reordered.add(resolver);
...@@ -109,67 +96,38 @@ class ConfigDataLocationResolvers { ...@@ -109,67 +96,38 @@ class ConfigDataLocationResolvers {
return Collections.unmodifiableList(reordered); return Collections.unmodifiableList(reordered);
} }
/** List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolverContext context, ConfigDataLocation location,
* Resolve all location strings using the most appropriate
* {@link ConfigDataLocationResolver}.
* @param context the location resolver context
* @param locations the locations to resolve
* @param profiles the current profiles or {@code null}
* @return the resolved locations
*/
List<ConfigDataLocation> resolveAll(ConfigDataLocationResolverContext context, List<String> locations,
Profiles profiles) { Profiles profiles) {
List<ConfigDataLocation> resolved = new ArrayList<>(locations.size()); if (location == null) {
for (String location : locations) {
resolved.addAll(resolveAll(context, location, profiles));
}
return resolved;
}
private List<ConfigDataLocation> resolveAll(ConfigDataLocationResolverContext context, String location,
Profiles profiles) {
boolean optional = location != null && location.startsWith(ConfigDataLocation.OPTIONAL_PREFIX);
location = (!optional) ? location : location.substring(ConfigDataLocation.OPTIONAL_PREFIX.length());
if (!StringUtils.hasText(location)) {
return Collections.emptyList(); return Collections.emptyList();
} }
for (ConfigDataLocationResolver<?> resolver : getResolvers()) { for (ConfigDataLocationResolver<?> resolver : getResolvers()) {
if (resolver.isResolvable(context, location)) { if (resolver.isResolvable(context, location)) {
return resolve(resolver, context, optional, location, profiles); return resolve(resolver, context, location, profiles);
} }
} }
throw new UnsupportedConfigDataLocationException(location); throw new UnsupportedConfigDataLocationException(location);
} }
private List<ConfigDataLocation> resolve(ConfigDataLocationResolver<?> resolver, private List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolver<?> resolver,
ConfigDataLocationResolverContext context, boolean optional, String location, Profiles profiles) { ConfigDataLocationResolverContext context, ConfigDataLocation location, Profiles profiles) {
List<ConfigDataLocation> resolved = resolve(location, optional, List<ConfigDataResolutionResult> resolved = resolve(location, () -> resolver.resolve(context, location));
() -> resolver.resolve(context, location, optional));
if (profiles == null) { if (profiles == null) {
return resolved; return resolved;
} }
List<ConfigDataLocation> profileSpecific = resolve(location, optional, List<ConfigDataResolutionResult> profileSpecific = resolve(location,
() -> resolver.resolveProfileSpecific(context, location, optional, profiles)); () -> resolver.resolveProfileSpecific(context, location, profiles));
return merge(resolved, profileSpecific); return merge(resolved, profileSpecific);
} }
private List<ConfigDataLocation> resolve(String location, boolean optional, private List<ConfigDataResolutionResult> resolve(ConfigDataLocation location,
Supplier<List<? extends ConfigDataLocation>> resolveAction) { Supplier<List<? extends ConfigDataResource>> resolveAction) {
try { List<ConfigDataResource> resources = nonNullList(resolveAction.get());
List<ConfigDataLocation> resolved = nonNullList(resolveAction.get()); List<ConfigDataResolutionResult> resolved = new ArrayList<>(resources.size());
if (!resolved.isEmpty() && optional) { for (ConfigDataResource resource : resources) {
resolved = OptionalConfigDataLocation.wrapAll(resolved); resolved.add(new ConfigDataResolutionResult(location, resource));
}
return resolved;
}
catch (ConfigDataLocationNotFoundException ex) {
if (optional) {
this.logger.trace(LogMessage.format("Skipping missing resource from optional location %s", location));
return Collections.emptyList();
}
this.locationNotFoundAction.handle(this.logger, location, ex);
return Collections.emptyList();
} }
return resolved;
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
......
...@@ -21,12 +21,12 @@ import org.apache.commons.logging.Log; ...@@ -21,12 +21,12 @@ import org.apache.commons.logging.Log;
import org.springframework.core.log.LogMessage; import org.springframework.core.log.LogMessage;
/** /**
* Action to take when an uncaught {@link ConfigDataLocationNotFoundException} is thrown. * Action to take when an uncaught {@link ConfigDataNotFoundException} is thrown.
* *
* @author Phillip Webb * @author Phillip Webb
* @since 2.4.0 * @since 2.4.0
*/ */
public enum ConfigDataLocationNotFoundAction { public enum ConfigDataNotFoundAction {
/** /**
* Throw the exception to fail startup. * Throw the exception to fail startup.
...@@ -34,7 +34,7 @@ public enum ConfigDataLocationNotFoundAction { ...@@ -34,7 +34,7 @@ public enum ConfigDataLocationNotFoundAction {
FAIL { FAIL {
@Override @Override
void handle(Log logger, Object location, ConfigDataLocationNotFoundException ex) { void handle(Log logger, ConfigDataNotFoundException ex) {
throw ex; throw ex;
} }
...@@ -46,19 +46,17 @@ public enum ConfigDataLocationNotFoundAction { ...@@ -46,19 +46,17 @@ public enum ConfigDataLocationNotFoundAction {
IGNORE { IGNORE {
@Override @Override
void handle(Log logger, Object location, ConfigDataLocationNotFoundException ex) { void handle(Log logger, ConfigDataNotFoundException ex) {
logger.trace(LogMessage.format("Ignoring missing resource from location %s", location)); logger.trace(LogMessage.format("Ignoring missing config data %s", ex.getReferenceDescription()));
} }
}; };
/** /**
* Handle the given exception. * Handle the given exception.
* @param logger the logger used for output * @param logger the logger used for output {@code ConfigDataLocation})
* @param location the location being checked (a {@link ConfigDataLocation} or
* {@code String})
* @param ex the exception to handle * @param ex the exception to handle
*/ */
abstract void handle(Log logger, Object location, ConfigDataLocationNotFoundException ex); abstract void handle(Log logger, ConfigDataNotFoundException ex);
} }
/*
* 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 org.springframework.boot.origin.OriginProvider;
/**
* {@link ConfigDataNotFoundException} thrown when a {@link ConfigData} cannot be found.
*
* @author Phillip Webb
* @since 2.4.0
*/
public abstract class ConfigDataNotFoundException extends ConfigDataException implements OriginProvider {
/**
* Create a new {@link ConfigDataNotFoundException} instance.
* @param message the exception message
* @param cause the exception cause
*/
ConfigDataNotFoundException(String message, Throwable cause) {
super(message, cause);
}
/**
* Return a description of actual referenced item that could not be found.
* @return a description of the referenced items
*/
public abstract String getReferenceDescription();
}
...@@ -16,11 +16,8 @@ ...@@ -16,11 +16,8 @@
package org.springframework.boot.context.config; package org.springframework.boot.context.config;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.function.Predicate; import java.util.function.Predicate;
import org.springframework.boot.cloud.CloudPlatform; import org.springframework.boot.cloud.CloudPlatform;
...@@ -28,11 +25,9 @@ import org.springframework.boot.context.properties.bind.BindContext; ...@@ -28,11 +25,9 @@ import org.springframework.boot.context.properties.bind.BindContext;
import org.springframework.boot.context.properties.bind.BindHandler; import org.springframework.boot.context.properties.bind.BindHandler;
import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.bind.BoundPropertiesTrackingBindHandler;
import org.springframework.boot.context.properties.bind.Name; import org.springframework.boot.context.properties.bind.Name;
import org.springframework.boot.context.properties.source.ConfigurationProperty; import org.springframework.boot.context.properties.source.ConfigurationProperty;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.origin.Origin;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
/** /**
...@@ -45,8 +40,6 @@ class ConfigDataProperties { ...@@ -45,8 +40,6 @@ class ConfigDataProperties {
private static final ConfigurationPropertyName NAME = ConfigurationPropertyName.of("spring.config"); private static final ConfigurationPropertyName NAME = ConfigurationPropertyName.of("spring.config");
private static final ConfigurationPropertyName IMPORT_NAME = ConfigurationPropertyName.of("spring.config.import");
private static final ConfigurationPropertyName LEGACY_PROFILES_NAME = ConfigurationPropertyName private static final ConfigurationPropertyName LEGACY_PROFILES_NAME = ConfigurationPropertyName
.of("spring.profiles"); .of("spring.profiles");
...@@ -54,57 +47,33 @@ class ConfigDataProperties { ...@@ -54,57 +47,33 @@ class ConfigDataProperties {
private static final Bindable<String[]> BINDABLE_STRING_ARRAY = Bindable.of(String[].class); private static final Bindable<String[]> BINDABLE_STRING_ARRAY = Bindable.of(String[].class);
private final List<String> imports; private final List<ConfigDataLocation> imports;
private final Activate activate; private final Activate activate;
private final Map<ConfigurationPropertyName, ConfigurationProperty> boundProperties;
/** /**
* Create a new {@link ConfigDataProperties} instance. * Create a new {@link ConfigDataProperties} instance.
* @param imports the imports requested * @param imports the imports requested
* @param activate the activate properties * @param activate the activate properties
*/ */
ConfigDataProperties(@Name("import") List<String> imports, Activate activate) { ConfigDataProperties(@Name("import") List<ConfigDataLocation> imports, Activate activate) {
this(imports, activate, Collections.emptyList()); this(imports, activate, Collections.emptyList());
} }
private ConfigDataProperties(List<String> imports, Activate activate, List<ConfigurationProperty> boundProperties) { private ConfigDataProperties(List<ConfigDataLocation> imports, Activate activate,
List<ConfigurationProperty> boundProperties) {
this.imports = (imports != null) ? imports : Collections.emptyList(); this.imports = (imports != null) ? imports : Collections.emptyList();
this.activate = activate; this.activate = activate;
this.boundProperties = mapByName(boundProperties);
}
private Map<ConfigurationPropertyName, ConfigurationProperty> mapByName(
List<ConfigurationProperty> boundProperties) {
Map<ConfigurationPropertyName, ConfigurationProperty> result = new LinkedHashMap<>();
boundProperties.forEach((property) -> result.put(property.getName(), property));
return Collections.unmodifiableMap(result);
} }
/** /**
* Return any additional imports requested. * Return any additional imports requested.
* @return the requested imports * @return the requested imports
*/ */
List<String> getImports() { List<ConfigDataLocation> getImports() {
return this.imports; return this.imports;
} }
/**
* Return the {@link Origin} of a given import location.
* @param importLocation the import location to check
* @return the origin of the import or {@code null}
*/
Origin getImportOrigin(String importLocation) {
int index = this.imports.indexOf(importLocation);
if (index == -1) {
return null;
}
ConfigurationProperty bound = this.boundProperties.get(IMPORT_NAME.append("[" + index + "]"));
bound = (bound != null) ? bound : this.boundProperties.get(IMPORT_NAME);
return (bound != null) ? bound.getOrigin() : null;
}
/** /**
* Return {@code true} if the properties indicate that the config data property source * Return {@code true} if the properties indicate that the config data property source
* is active for the given activation context. * is active for the given activation context.
...@@ -130,10 +99,6 @@ class ConfigDataProperties { ...@@ -130,10 +99,6 @@ class ConfigDataProperties {
return new ConfigDataProperties(this.imports, new Activate(this.activate.onCloudPlatform, legacyProfiles)); return new ConfigDataProperties(this.imports, new Activate(this.activate.onCloudPlatform, legacyProfiles));
} }
ConfigDataProperties withBoundProperties(List<ConfigurationProperty> boundProperties) {
return new ConfigDataProperties(this.imports, this.activate, boundProperties);
}
/** /**
* Factory method used to create {@link ConfigDataProperties} from the given * Factory method used to create {@link ConfigDataProperties} from the given
* {@link Binder}. * {@link Binder}.
...@@ -144,16 +109,14 @@ class ConfigDataProperties { ...@@ -144,16 +109,14 @@ class ConfigDataProperties {
LegacyProfilesBindHandler legacyProfilesBindHandler = new LegacyProfilesBindHandler(); LegacyProfilesBindHandler legacyProfilesBindHandler = new LegacyProfilesBindHandler();
String[] legacyProfiles = binder.bind(LEGACY_PROFILES_NAME, BINDABLE_STRING_ARRAY, legacyProfilesBindHandler) String[] legacyProfiles = binder.bind(LEGACY_PROFILES_NAME, BINDABLE_STRING_ARRAY, legacyProfilesBindHandler)
.orElse(null); .orElse(null);
List<ConfigurationProperty> boundProperties = new ArrayList<>(); ConfigDataProperties properties = binder.bind(NAME, BINDABLE_PROPERTIES, new ConfigDataLocationBindHandler())
ConfigDataProperties properties = binder
.bind(NAME, BINDABLE_PROPERTIES, new BoundPropertiesTrackingBindHandler(boundProperties::add))
.orElse(null); .orElse(null);
if (!ObjectUtils.isEmpty(legacyProfiles)) { if (!ObjectUtils.isEmpty(legacyProfiles)) {
properties = (properties != null) properties = (properties != null)
? properties.withLegacyProfiles(legacyProfiles, legacyProfilesBindHandler.getProperty()) ? properties.withLegacyProfiles(legacyProfiles, legacyProfilesBindHandler.getProperty())
: new ConfigDataProperties(null, new Activate(null, legacyProfiles)); : new ConfigDataProperties(null, new Activate(null, legacyProfiles));
} }
return (properties != null) ? properties.withBoundProperties(boundProperties) : null; return properties;
} }
/** /**
......
...@@ -16,57 +16,29 @@ ...@@ -16,57 +16,29 @@
package org.springframework.boot.context.config; package org.springframework.boot.context.config;
import java.util.ArrayList;
import java.util.List;
/** /**
* {@link ConfigDataLocation} wrapper used to indicate that it's optional. * Result returned from {@link ConfigDataLocationResolvers} containing both the
* {@link ConfigDataResource} and the original {@link ConfigDataLocation}.
* *
* @author Phillip Webb * @author Phillip Webb
*/ */
class OptionalConfigDataLocation extends ConfigDataLocation { class ConfigDataResolutionResult {
private final ConfigDataLocation location;
private ConfigDataLocation location; private final ConfigDataResource resource;
OptionalConfigDataLocation(ConfigDataLocation location) { ConfigDataResolutionResult(ConfigDataLocation location, ConfigDataResource resource) {
this.location = location; this.location = location;
this.resource = resource;
} }
ConfigDataLocation getLocation() { ConfigDataLocation getLocation() {
return this.location; return this.location;
} }
@Override ConfigDataResource getResource() {
public boolean equals(Object obj) { return this.resource;
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
OptionalConfigDataLocation other = (OptionalConfigDataLocation) obj;
return this.location.equals(other.location);
}
@Override
public int hashCode() {
return this.location.hashCode();
}
@Override
public String toString() {
return this.location.toString();
}
static List<ConfigDataLocation> wrapAll(List<ConfigDataLocation> locations) {
List<ConfigDataLocation> wrapped = new ArrayList<>(locations.size());
locations.forEach((location) -> wrapped.add(new OptionalConfigDataLocation(location)));
return wrapped;
}
@SuppressWarnings("unchecked")
static <L extends ConfigDataLocation> L unwrap(ConfigDataLocation wrapped) {
return (L) ((OptionalConfigDataLocation) wrapped).getLocation();
} }
} }
...@@ -16,23 +16,15 @@ ...@@ -16,23 +16,15 @@
package org.springframework.boot.context.config; package org.springframework.boot.context.config;
import java.io.IOException;
import org.springframework.core.io.Resource;
/** /**
* {@link ConfigDataLoader} for {@link Resource} backed locations. * A single resource from which {@link ConfigData} can be loaded. Implementations must
* implement a valid {@link #equals(Object) equals}, {@link #hashCode() hashCode} and
* {@link #toString() toString} methods.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Madhura Bhave * @author Madhura Bhave
* @since 2.4.0 * @since 2.4.0
*/ */
public class ResourceConfigDataLoader implements ConfigDataLoader<ResourceConfigDataLocation> { public abstract class ConfigDataResource {
@Override
public ConfigData load(ConfigDataLoaderContext context, ResourceConfigDataLocation location) throws IOException {
ConfigDataLocationNotFoundException.throwIfDoesNotExist(location, location.getResource());
return new ConfigData(location.load());
}
} }
/*
* 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.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import org.springframework.boot.origin.Origin;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
/**
* {@link ConfigDataNotFoundException} thrown when a {@link ConfigDataResource} cannot be
* found.
*
* @author Phillip Webb
* @since 2.4.0
*/
public class ConfigDataResourceNotFoundException extends ConfigDataNotFoundException {
private final ConfigDataResource resource;
private final ConfigDataLocation location;
/**
* Create a new {@link ConfigDataResourceNotFoundException} instance.
* @param resource the resource that could not be found
*/
public ConfigDataResourceNotFoundException(ConfigDataResource resource) {
this(resource, null);
}
/**
* Create a new {@link ConfigDataResourceNotFoundException} instance.
* @param resource the resource that could not be found
* @param cause the exception cause
*/
public ConfigDataResourceNotFoundException(ConfigDataResource resource, Throwable cause) {
this(resource, null, cause);
}
private ConfigDataResourceNotFoundException(ConfigDataResource resource, ConfigDataLocation location,
Throwable cause) {
super(getMessage(resource, location), cause);
Assert.notNull(resource, "Resource must not be null");
this.resource = resource;
this.location = location;
}
/**
* Return the resource that could not be found.
* @return the resource
*/
public ConfigDataResource getResource() {
return this.resource;
}
/**
* Return the original location that was resolved to determine the resource.
* @return the location or {@code null} if no location is availble
*/
public ConfigDataLocation getLocation() {
return this.location;
}
@Override
public Origin getOrigin() {
return Origin.from(this.location);
}
@Override
public String getReferenceDescription() {
return getReferenceDescription(this.resource, this.location);
}
/**
* Create a new {@link ConfigDataResourceNotFoundException} instance with a location.
* @param location the location to set
* @return a new {@link ConfigDataResourceNotFoundException} instance
*/
ConfigDataResourceNotFoundException withLocation(ConfigDataLocation location) {
return new ConfigDataResourceNotFoundException(this.resource, location, getCause());
}
private static String getMessage(ConfigDataResource resource, ConfigDataLocation location) {
return String.format("Config data %s cannot be found", getReferenceDescription(resource, location));
}
private static String getReferenceDescription(ConfigDataResource resource, ConfigDataLocation location) {
String description = String.format("resource '%s'", resource);
if (location != null) {
description += String.format(" via location '%s'", location);
}
return description;
}
/**
* Throw a {@link ConfigDataNotFoundException} if the specified {@link Path} does not
* exist.
* @param resource the config data resource
* @param pathToCheck the path to check
*/
public static void throwIfDoesNotExist(ConfigDataResource resource, Path pathToCheck) {
throwIfDoesNotExist(resource, Files.exists(pathToCheck));
}
/**
* Throw a {@link ConfigDataNotFoundException} if the specified {@link File} does not
* exist.
* @param resource the config data resource
* @param fileToCheck the file to check
*/
public static void throwIfDoesNotExist(ConfigDataResource resource, File fileToCheck) {
throwIfDoesNotExist(resource, fileToCheck.exists());
}
/**
* Throw a {@link ConfigDataNotFoundException} if the specified {@link Resource} does
* not exist.
* @param resource the config data resource
* @param resourceToCheck the resource to check
*/
public static void throwIfDoesNotExist(ConfigDataResource resource, Resource resourceToCheck) {
throwIfDoesNotExist(resource, resourceToCheck.exists());
}
private static void throwIfDoesNotExist(ConfigDataResource resource, boolean exists) {
if (!exists) {
throw new ConfigDataResourceNotFoundException(resource);
}
}
}
...@@ -426,25 +426,13 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor, ...@@ -426,25 +426,13 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) { private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
getSearchLocations().forEach((location) -> { getSearchLocations().forEach((location) -> {
String nonOptionalLocation = ConfigDataLocation.of(location).getValue();
boolean isDirectory = location.endsWith("/"); boolean isDirectory = location.endsWith("/");
Set<String> names = isDirectory ? getSearchNames() : NO_SEARCH_NAMES; Set<String> names = isDirectory ? getSearchNames() : NO_SEARCH_NAMES;
names.forEach((name) -> load(stripOptionalPrefix(location), name, profile, filterFactory, consumer)); names.forEach((name) -> load(nonOptionalLocation, name, profile, filterFactory, consumer));
}); });
} }
/**
* Strip the optional prefix from the location. When using the legacy method, all
* locations are optional.
* @param location the location to strip
* @return the stripped location
*/
private String stripOptionalPrefix(String location) {
if (location != null && location.startsWith(ConfigDataLocation.OPTIONAL_PREFIX)) {
return location.substring(ConfigDataLocation.OPTIONAL_PREFIX.length());
}
return location;
}
private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory, private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
DocumentConsumer consumer) { DocumentConsumer consumer) {
if (!StringUtils.hasText(name)) { if (!StringUtils.hasText(name)) {
...@@ -552,9 +540,9 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor, ...@@ -552,9 +540,9 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
} }
} }
private String getLocationName(String location, Resource resource) { private String getLocationName(String locationReference, Resource resource) {
if (!location.contains("*")) { if (!locationReference.contains("*")) {
return location; return locationReference;
} }
if (resource instanceof FileSystemResource) { if (resource instanceof FileSystemResource) {
return ((FileSystemResource) resource).getPath(); return ((FileSystemResource) resource).getPath();
...@@ -562,24 +550,24 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor, ...@@ -562,24 +550,24 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
return resource.getDescription(); return resource.getDescription();
} }
private Resource[] getResources(String location) { private Resource[] getResources(String locationReference) {
try { try {
if (location.contains("*")) { if (locationReference.contains("*")) {
return getResourcesFromPatternLocation(location); return getResourcesFromPatternLocationReference(locationReference);
} }
return new Resource[] { this.resourceLoader.getResource(location) }; return new Resource[] { this.resourceLoader.getResource(locationReference) };
} }
catch (Exception ex) { catch (Exception ex) {
return EMPTY_RESOURCES; return EMPTY_RESOURCES;
} }
} }
private Resource[] getResourcesFromPatternLocation(String location) throws IOException { private Resource[] getResourcesFromPatternLocationReference(String locationReference) throws IOException {
String directoryPath = location.substring(0, location.indexOf("*/")); String directoryPath = locationReference.substring(0, locationReference.indexOf("*/"));
Resource resource = this.resourceLoader.getResource(directoryPath); Resource resource = this.resourceLoader.getResource(directoryPath);
File[] files = resource.getFile().listFiles(File::isDirectory); File[] files = resource.getFile().listFiles(File::isDirectory);
if (files != null) { if (files != null) {
String fileName = location.substring(location.lastIndexOf("/") + 1); String fileName = locationReference.substring(locationReference.lastIndexOf("/") + 1);
Arrays.sort(files, FILE_COMPARATOR); Arrays.sort(files, FILE_COMPARATOR);
return Arrays.stream(files).map((file) -> file.listFiles((dir, name) -> name.equals(fileName))) return Arrays.stream(files).map((file) -> file.listFiles((dir, name) -> name.equals(fileName)))
.filter(Objects::nonNull).flatMap((Function<File[], Stream<File>>) Arrays::stream) .filter(Objects::nonNull).flatMap((Function<File[], Stream<File>>) Arrays::stream)
...@@ -622,7 +610,8 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor, ...@@ -622,7 +610,8 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
}).collect(Collectors.toList()); }).collect(Collectors.toList());
} }
private StringBuilder getDescription(String prefix, String location, Resource resource, Profile profile) { private StringBuilder getDescription(String prefix, String locationReference, Resource resource,
Profile profile) {
StringBuilder result = new StringBuilder(prefix); StringBuilder result = new StringBuilder(prefix);
try { try {
if (resource != null) { if (resource != null) {
...@@ -630,12 +619,12 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor, ...@@ -630,12 +619,12 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
result.append("'"); result.append("'");
result.append(uri); result.append(uri);
result.append("' ("); result.append("' (");
result.append(location); result.append(locationReference);
result.append(")"); result.append(")");
} }
} }
catch (IOException ex) { catch (IOException ex) {
result.append(location); result.append(locationReference);
} }
if (profile != null) { if (profile != null) {
result.append(" for profile "); result.append(" for profile ");
......
...@@ -29,12 +29,13 @@ import org.springframework.boot.env.ConfigTreePropertySource; ...@@ -29,12 +29,13 @@ import org.springframework.boot.env.ConfigTreePropertySource;
* @author Phillip Webb * @author Phillip Webb
* @since 2.4.0 * @since 2.4.0
*/ */
public class ConfigTreeConfigDataLoader implements ConfigDataLoader<ConfigTreeConfigDataLocation> { public class ConfigTreeConfigDataLoader implements ConfigDataLoader<ConfigTreeConfigDataResource> {
@Override @Override
public ConfigData load(ConfigDataLoaderContext context, ConfigTreeConfigDataLocation location) throws IOException { public ConfigData load(ConfigDataLoaderContext context, ConfigTreeConfigDataResource resource)
ConfigDataLocationNotFoundException.throwIfDoesNotExist(location, location.getPath()); throws IOException, ConfigDataResourceNotFoundException {
Path path = location.getPath(); Path path = resource.getPath();
ConfigDataResourceNotFoundException.throwIfDoesNotExist(resource, path);
String name = "Config tree '" + path + "'"; String name = "Config tree '" + path + "'";
ConfigTreePropertySource source = new ConfigTreePropertySource(name, path); ConfigTreePropertySource source = new ConfigTreePropertySource(name, path);
return new ConfigData(Collections.singletonList(source)); return new ConfigData(Collections.singletonList(source));
......
...@@ -26,19 +26,19 @@ import java.util.List; ...@@ -26,19 +26,19 @@ import java.util.List;
* @author Phillip Webb * @author Phillip Webb
* @since 2.4.0 * @since 2.4.0
*/ */
public class ConfigTreeConfigDataLocationResolver implements ConfigDataLocationResolver<ConfigTreeConfigDataLocation> { public class ConfigTreeConfigDataLocationResolver implements ConfigDataLocationResolver<ConfigTreeConfigDataResource> {
private static final String PREFIX = "configtree:"; private static final String PREFIX = "configtree:";
@Override @Override
public boolean isResolvable(ConfigDataLocationResolverContext context, String location) { public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location) {
return location.startsWith(PREFIX); return location.hasPrefix(PREFIX);
} }
@Override @Override
public List<ConfigTreeConfigDataLocation> resolve(ConfigDataLocationResolverContext context, String location, public List<ConfigTreeConfigDataResource> resolve(ConfigDataLocationResolverContext context,
boolean optional) { ConfigDataLocation location) {
ConfigTreeConfigDataLocation resolved = new ConfigTreeConfigDataLocation(location.substring(PREFIX.length())); ConfigTreeConfigDataResource resolved = new ConfigTreeConfigDataResource(location.getNonPrefixedValue(PREFIX));
return Collections.singletonList(resolved); return Collections.singletonList(resolved);
} }
......
...@@ -24,18 +24,18 @@ import org.springframework.boot.env.ConfigTreePropertySource; ...@@ -24,18 +24,18 @@ import org.springframework.boot.env.ConfigTreePropertySource;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
* {@link ConfigDataLocation} backed by a config tree directory. * {@link ConfigDataResource} backed by a config tree directory.
* *
* @author Madhura Bhave * @author Madhura Bhave
* @author Phillip Webb * @author Phillip Webb
* @since 2.4.0 * @since 2.4.0
* @see ConfigTreePropertySource * @see ConfigTreePropertySource
*/ */
public class ConfigTreeConfigDataLocation extends ConfigDataLocation { public class ConfigTreeConfigDataResource extends ConfigDataResource {
private final Path path; private final Path path;
ConfigTreeConfigDataLocation(String path) { ConfigTreeConfigDataResource(String path) {
Assert.notNull(path, "Path must not be null"); Assert.notNull(path, "Path must not be null");
this.path = Paths.get(path).toAbsolutePath(); this.path = Paths.get(path).toAbsolutePath();
} }
...@@ -52,7 +52,7 @@ public class ConfigTreeConfigDataLocation extends ConfigDataLocation { ...@@ -52,7 +52,7 @@ public class ConfigTreeConfigDataLocation extends ConfigDataLocation {
if (obj == null || getClass() != obj.getClass()) { if (obj == null || getClass() != obj.getClass()) {
return false; return false;
} }
ConfigTreeConfigDataLocation other = (ConfigTreeConfigDataLocation) obj; ConfigTreeConfigDataResource other = (ConfigTreeConfigDataResource) obj;
return Objects.equals(this.path, other.path); return Objects.equals(this.path, other.path);
} }
......
...@@ -35,7 +35,7 @@ public class InactiveConfigDataAccessException extends ConfigDataException { ...@@ -35,7 +35,7 @@ public class InactiveConfigDataAccessException extends ConfigDataException {
private final PropertySource<?> propertySource; private final PropertySource<?> propertySource;
private final ConfigDataLocation location; private final ConfigDataResource location;
private final String propertyName; private final String propertyName;
...@@ -44,12 +44,12 @@ public class InactiveConfigDataAccessException extends ConfigDataException { ...@@ -44,12 +44,12 @@ public class InactiveConfigDataAccessException extends ConfigDataException {
/** /**
* Create a new {@link InactiveConfigDataAccessException} instance. * Create a new {@link InactiveConfigDataAccessException} instance.
* @param propertySource the inactive property source * @param propertySource the inactive property source
* @param location the {@link ConfigDataLocation} of the property source or * @param location the {@link ConfigDataResource} of the property source or
* {@code null} if the source was not loaded from {@link ConfigData}. * {@code null} if the source was not loaded from {@link ConfigData}.
* @param propertyName the name of the property * @param propertyName the name of the property
* @param origin the origin or the property or {@code null} * @param origin the origin or the property or {@code null}
*/ */
InactiveConfigDataAccessException(PropertySource<?> propertySource, ConfigDataLocation location, InactiveConfigDataAccessException(PropertySource<?> propertySource, ConfigDataResource location,
String propertyName, Origin origin) { String propertyName, Origin origin) {
super(getMessage(propertySource, location, propertyName, origin), null); super(getMessage(propertySource, location, propertyName, origin), null);
this.propertySource = propertySource; this.propertySource = propertySource;
...@@ -58,7 +58,7 @@ public class InactiveConfigDataAccessException extends ConfigDataException { ...@@ -58,7 +58,7 @@ public class InactiveConfigDataAccessException extends ConfigDataException {
this.origin = origin; this.origin = origin;
} }
private static String getMessage(PropertySource<?> propertySource, ConfigDataLocation location, String propertyName, private static String getMessage(PropertySource<?> propertySource, ConfigDataResource location, String propertyName,
Origin origin) { Origin origin) {
StringBuilder message = new StringBuilder("Inactive property source '"); StringBuilder message = new StringBuilder("Inactive property source '");
message.append(propertySource.getName()); message.append(propertySource.getName());
...@@ -86,11 +86,11 @@ public class InactiveConfigDataAccessException extends ConfigDataException { ...@@ -86,11 +86,11 @@ public class InactiveConfigDataAccessException extends ConfigDataException {
} }
/** /**
* Return the {@link ConfigDataLocation} of the property source or {@code null} if the * Return the {@link ConfigDataResource} of the property source or {@code null} if the
* source was not loaded from {@link ConfigData}. * source was not loaded from {@link ConfigData}.
* @return the config data location or {@code null} * @return the config data location or {@code null}
*/ */
public ConfigDataLocation getLocation() { public ConfigDataResource getLocation() {
return this.location; return this.location;
} }
...@@ -121,7 +121,7 @@ public class InactiveConfigDataAccessException extends ConfigDataException { ...@@ -121,7 +121,7 @@ public class InactiveConfigDataAccessException extends ConfigDataException {
ConfigurationProperty property = (source != null) ? source.getConfigurationProperty(name) : null; ConfigurationProperty property = (source != null) ? source.getConfigurationProperty(name) : null;
if (property != null) { if (property != null) {
PropertySource<?> propertySource = contributor.getPropertySource(); PropertySource<?> propertySource = contributor.getPropertySource();
ConfigDataLocation location = contributor.getLocation(); ConfigDataResource location = contributor.getResource();
throw new InactiveConfigDataAccessException(propertySource, location, name.toString(), throw new InactiveConfigDataAccessException(propertySource, location, name.toString(),
property.getOrigin()); property.getOrigin());
} }
......
...@@ -47,10 +47,10 @@ public class InvalidConfigDataPropertyException extends ConfigDataException { ...@@ -47,10 +47,10 @@ public class InvalidConfigDataPropertyException extends ConfigDataException {
private final ConfigurationPropertyName replacement; private final ConfigurationPropertyName replacement;
private final ConfigDataLocation location; private final ConfigDataResource location;
InvalidConfigDataPropertyException(ConfigurationProperty property, ConfigurationPropertyName replacement, InvalidConfigDataPropertyException(ConfigurationProperty property, ConfigurationPropertyName replacement,
ConfigDataLocation location) { ConfigDataResource location) {
super(getMessage(property, replacement, location), null); super(getMessage(property, replacement, location), null);
this.property = property; this.property = property;
this.replacement = replacement; this.replacement = replacement;
...@@ -66,11 +66,11 @@ public class InvalidConfigDataPropertyException extends ConfigDataException { ...@@ -66,11 +66,11 @@ public class InvalidConfigDataPropertyException extends ConfigDataException {
} }
/** /**
* Return the {@link ConfigDataLocation} of the invalid property or {@code null} if * Return the {@link ConfigDataResource} of the invalid property or {@code null} if
* the source was not loaded from {@link ConfigData}. * the source was not loaded from {@link ConfigData}.
* @return the config data location or {@code null} * @return the config data location or {@code null}
*/ */
public ConfigDataLocation getLocation() { public ConfigDataResource getLocation() {
return this.location; return this.location;
} }
...@@ -97,14 +97,14 @@ public class InvalidConfigDataPropertyException extends ConfigDataException { ...@@ -97,14 +97,14 @@ public class InvalidConfigDataPropertyException extends ConfigDataException {
WARNING.forEach((invalid, replacement) -> { WARNING.forEach((invalid, replacement) -> {
ConfigurationProperty property = propertySource.getConfigurationProperty(invalid); ConfigurationProperty property = propertySource.getConfigurationProperty(invalid);
if (property != null) { if (property != null) {
logger.warn(getMessage(property, replacement, contributor.getLocation())); logger.warn(getMessage(property, replacement, contributor.getResource()));
} }
}); });
} }
} }
private static String getMessage(ConfigurationProperty property, ConfigurationPropertyName replacement, private static String getMessage(ConfigurationProperty property, ConfigurationPropertyName replacement,
ConfigDataLocation location) { ConfigDataResource location) {
StringBuilder message = new StringBuilder("Property '"); StringBuilder message = new StringBuilder("Property '");
message.append(property.getName()); message.append(property.getName());
if (location != null) { if (location != null) {
......
/*
* 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.io.IOException;
import java.util.List;
import org.springframework.boot.origin.Origin;
import org.springframework.boot.origin.OriginTrackedResource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
/**
* {@link ConfigDataLoader} for {@link Resource} backed locations.
*
* @author Phillip Webb
* @author Madhura Bhave
* @since 2.4.0
*/
public class StandardConfigDataLoader implements ConfigDataLoader<StandardConfigDataResource> {
@Override
public ConfigData load(ConfigDataLoaderContext context, StandardConfigDataResource resource)
throws IOException, ConfigDataNotFoundException {
ConfigDataResourceNotFoundException.throwIfDoesNotExist(resource, resource.getResource());
StandardConfigDataReference reference = resource.getReference();
Resource originTrackedResource = OriginTrackedResource.of(resource.getResource(),
Origin.from(reference.getConfigDataLocation()));
String name = String.format("Config resource '%s' via location '%s'", reference.getResourceLocation(),
reference.getConfigDataLocation());
List<PropertySource<?>> propertySources = reference.getPropertySourceLoader().load(name, originTrackedResource);
return new ConfigData(propertySources);
}
}
...@@ -32,7 +32,6 @@ import org.apache.commons.logging.Log; ...@@ -32,7 +32,6 @@ import org.apache.commons.logging.Log;
import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.env.PropertySourceLoader; import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.boot.origin.Origin;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.FileSystemResource;
...@@ -52,8 +51,8 @@ import org.springframework.util.StringUtils; ...@@ -52,8 +51,8 @@ import org.springframework.util.StringUtils;
* @author Phillip Webb * @author Phillip Webb
* @since 2.4.0 * @since 2.4.0
*/ */
public class ResourceConfigDataLocationResolver public class StandardConfigDataLocationResolver
implements ConfigDataLocationResolver<ResourceConfigDataLocation>, Ordered { implements ConfigDataLocationResolver<StandardConfigDataResource>, Ordered {
private static final String PREFIX = "resource:"; private static final String PREFIX = "resource:";
...@@ -80,12 +79,12 @@ public class ResourceConfigDataLocationResolver ...@@ -80,12 +79,12 @@ public class ResourceConfigDataLocationResolver
private final ResourceLoader resourceLoader; private final ResourceLoader resourceLoader;
/** /**
* Create a new {@link ResourceConfigDataLocationResolver} instance. * Create a new {@link StandardConfigDataLocationResolver} instance.
* @param logger the logger to use * @param logger the logger to use
* @param binder a binder backed by the initial {@link Environment} * @param binder a binder backed by the initial {@link Environment}
* @param resourceLoader a {@link ResourceLoader} used to load resources * @param resourceLoader a {@link ResourceLoader} used to load resources
*/ */
public ResourceConfigDataLocationResolver(Log logger, Binder binder, ResourceLoader resourceLoader) { public StandardConfigDataLocationResolver(Log logger, Binder binder, ResourceLoader resourceLoader) {
this.logger = logger; this.logger = logger;
this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class, this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
getClass().getClassLoader()); getClass().getClassLoader());
...@@ -111,179 +110,180 @@ public class ResourceConfigDataLocationResolver ...@@ -111,179 +110,180 @@ public class ResourceConfigDataLocationResolver
} }
@Override @Override
public boolean isResolvable(ConfigDataLocationResolverContext context, String location) { public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location) {
return true; return true;
} }
@Override @Override
public List<ResourceConfigDataLocation> resolve(ConfigDataLocationResolverContext context, String location, public List<StandardConfigDataResource> resolve(ConfigDataLocationResolverContext context,
boolean optional) { ConfigDataLocation location) throws ConfigDataNotFoundException {
return resolve(location, getResolvables(context, location, optional)); return resolve(getReferences(context, location));
} }
@Override private Set<StandardConfigDataReference> getReferences(ConfigDataLocationResolverContext context,
public List<ResourceConfigDataLocation> resolveProfileSpecific(ConfigDataLocationResolverContext context, ConfigDataLocation configDataLocation) {
String location, boolean optional, Profiles profiles) { String resourceLocation = getResourceLocation(context, configDataLocation);
return resolve(location, getProfileSpecificResolvables(context, location, optional, profiles));
}
private Set<Resolvable> getResolvables(ConfigDataLocationResolverContext context, String location,
boolean optional) {
Origin origin = context.getLocationOrigin(location);
String resourceLocation = getResourceLocation(context, location);
try { try {
if (isDirectoryLocation(resourceLocation)) { if (isDirectory(resourceLocation)) {
return getResolvablesForDirectory(resourceLocation, optional, NO_PROFILE, origin); return getReferencesForDirectory(configDataLocation, resourceLocation, NO_PROFILE);
} }
return getResolvablesForFile(resourceLocation, optional, NO_PROFILE, origin); return getReferencesForFile(configDataLocation, resourceLocation, NO_PROFILE);
} }
catch (RuntimeException ex) { catch (RuntimeException ex) {
throw new IllegalStateException("Unable to load config data from '" + location + "'", ex); throw new IllegalStateException("Unable to load config data from '" + configDataLocation + "'", ex);
} }
} }
private Set<Resolvable> getProfileSpecificResolvables(ConfigDataLocationResolverContext context, String location, @Override
boolean optional, Profiles profiles) { public List<StandardConfigDataResource> resolveProfileSpecific(ConfigDataLocationResolverContext context,
Origin origin = context.getLocationOrigin(location); ConfigDataLocation location, Profiles profiles) {
Set<Resolvable> resolvables = new LinkedHashSet<>(); return resolve(getProfileSpecificReferences(context, location, profiles));
String resourceLocation = getResourceLocation(context, location); }
private Set<StandardConfigDataReference> getProfileSpecificReferences(ConfigDataLocationResolverContext context,
ConfigDataLocation configDataLocation, Profiles profiles) {
Set<StandardConfigDataReference> references = new LinkedHashSet<>();
String resourceLocation = getResourceLocation(context, configDataLocation);
for (String profile : profiles) { for (String profile : profiles) {
resolvables.addAll(getResolvables(resourceLocation, optional, profile, origin)); references.addAll(getReferences(configDataLocation, resourceLocation, profile));
} }
return resolvables; return references;
} }
private String getResourceLocation(ConfigDataLocationResolverContext context, String location) { private String getResourceLocation(ConfigDataLocationResolverContext context,
String resourceLocation = (location.startsWith(PREFIX)) ? location.substring(PREFIX.length()) : location; ConfigDataLocation configDataLocation) {
String resourceLocation = configDataLocation.getNonPrefixedValue(PREFIX);
boolean isAbsolute = resourceLocation.startsWith("/") || URL_PREFIX.matcher(resourceLocation).matches(); boolean isAbsolute = resourceLocation.startsWith("/") || URL_PREFIX.matcher(resourceLocation).matches();
if (isAbsolute) { if (isAbsolute) {
return resourceLocation; return resourceLocation;
} }
ConfigDataLocation parent = context.getParent(); ConfigDataResource parent = context.getParent();
if (parent instanceof ResourceConfigDataLocation) { if (parent instanceof StandardConfigDataResource) {
String parentLocation = ((ResourceConfigDataLocation) parent).getLocation(); String parentResourceLocation = ((StandardConfigDataResource) parent).getReference().getResourceLocation();
String parentDirectory = parentLocation.substring(0, parentLocation.lastIndexOf("/") + 1); String parentDirectory = parentResourceLocation.substring(0, parentResourceLocation.lastIndexOf("/") + 1);
return parentDirectory + resourceLocation; return parentDirectory + resourceLocation;
} }
return resourceLocation; return resourceLocation;
} }
private Set<Resolvable> getResolvables(String resourceLocation, boolean optional, String profile, Origin origin) { private Set<StandardConfigDataReference> getReferences(ConfigDataLocation configDataLocation,
if (isDirectoryLocation(resourceLocation)) { String resourceLocation, String profile) {
return getResolvablesForDirectory(resourceLocation, optional, profile, origin); if (isDirectory(resourceLocation)) {
return getReferencesForDirectory(configDataLocation, resourceLocation, profile);
} }
return getResolvablesForFile(resourceLocation, optional, profile, origin); return getReferencesForFile(configDataLocation, resourceLocation, profile);
} }
private Set<Resolvable> getResolvablesForDirectory(String directoryLocation, boolean optional, String profile, private Set<StandardConfigDataReference> getReferencesForDirectory(ConfigDataLocation configDataLocation,
Origin origin) { String directory, String profile) {
Set<Resolvable> resolvables = new LinkedHashSet<>(); Set<StandardConfigDataReference> references = new LinkedHashSet<>();
for (String name : this.configNames) { for (String name : this.configNames) {
String rootLocation = directoryLocation + name; for (PropertySourceLoader propertySourceLoader : this.propertySourceLoaders) {
for (PropertySourceLoader loader : this.propertySourceLoaders) { for (String extension : propertySourceLoader.getFileExtensions()) {
for (String extension : loader.getFileExtensions()) { StandardConfigDataReference reference = new StandardConfigDataReference(configDataLocation,
Resolvable resolvable = new Resolvable(directoryLocation, rootLocation, optional, profile, directory, directory + name, profile, extension, propertySourceLoader);
extension, origin, loader); references.add(reference);
resolvables.add(resolvable);
} }
} }
} }
return resolvables; return references;
} }
private Set<Resolvable> getResolvablesForFile(String fileLocation, boolean optional, String profile, private Set<StandardConfigDataReference> getReferencesForFile(ConfigDataLocation configDataLocation, String file,
Origin origin) { String profile) {
Matcher extensionHintMatcher = EXTENSION_HINT_PATTERN.matcher(fileLocation); Matcher extensionHintMatcher = EXTENSION_HINT_PATTERN.matcher(file);
boolean extensionHintLocation = extensionHintMatcher.matches(); boolean extensionHintLocation = extensionHintMatcher.matches();
if (extensionHintLocation) { if (extensionHintLocation) {
fileLocation = extensionHintMatcher.group(1) + extensionHintMatcher.group(2); file = extensionHintMatcher.group(1) + extensionHintMatcher.group(2);
} }
for (PropertySourceLoader loader : this.propertySourceLoaders) { for (PropertySourceLoader propertySourceLoader : this.propertySourceLoaders) {
String extension = getLoadableFileExtension(loader, fileLocation); String extension = getLoadableFileExtension(propertySourceLoader, file);
if (extension != null) { if (extension != null) {
String root = fileLocation.substring(0, fileLocation.length() - extension.length() - 1); String root = file.substring(0, file.length() - extension.length() - 1);
return Collections.singleton(new Resolvable(null, root, optional, profile, StandardConfigDataReference reference = new StandardConfigDataReference(configDataLocation, null, root,
(!extensionHintLocation) ? extension : null, origin, loader)); profile, (!extensionHintLocation) ? extension : null, propertySourceLoader);
return Collections.singleton(reference);
} }
} }
throw new IllegalStateException("File extension is not known to any PropertySourceLoader. " throw new IllegalStateException("File extension is not known to any PropertySourceLoader. "
+ "If the location is meant to reference a directory, it must end in '/'"); + "If the location is meant to reference a directory, it must end in '/'");
} }
private String getLoadableFileExtension(PropertySourceLoader loader, String resourceLocation) { private String getLoadableFileExtension(PropertySourceLoader loader, String file) {
for (String fileExtension : loader.getFileExtensions()) { for (String fileExtension : loader.getFileExtensions()) {
if (StringUtils.endsWithIgnoreCase(resourceLocation, fileExtension)) { if (StringUtils.endsWithIgnoreCase(file, fileExtension)) {
return fileExtension; return fileExtension;
} }
} }
return null; return null;
} }
private boolean isDirectoryLocation(String resourceLocation) { private boolean isDirectory(String resourceLocation) {
return resourceLocation.endsWith("/"); return resourceLocation.endsWith("/");
} }
private List<ResourceConfigDataLocation> resolve(String location, Set<Resolvable> resolvables) { private List<StandardConfigDataResource> resolve(Set<StandardConfigDataReference> references) {
List<ResourceConfigDataLocation> resolved = new ArrayList<>(); List<StandardConfigDataResource> resolved = new ArrayList<>();
for (Resolvable resolvable : resolvables) { for (StandardConfigDataReference reference : references) {
resolved.addAll(resolve(location, resolvable)); resolved.addAll(resolve(reference));
} }
if (resolved.isEmpty()) { if (resolved.isEmpty()) {
assertNonOptionalDirectories(location, resolvables); assertNonOptionalDirectories(references);
} }
return resolved; return resolved;
} }
private void assertNonOptionalDirectories(String location, Set<Resolvable> resolvables) { private void assertNonOptionalDirectories(Set<StandardConfigDataReference> references) {
for (Resolvable resolvable : resolvables) { for (StandardConfigDataReference reference : references) {
if (resolvable.isNonOptionalDirectory()) { if (reference.isNonOptionalDirectory()) {
Resource resource = loadResource(resolvable.getDirectory()); assertDirectoryExists(reference);
ResourceConfigDataLocation resourceLocation = createConfigResourceLocation(location, resolvable,
resource);
ConfigDataLocationNotFoundException.throwIfDoesNotExist(resourceLocation, resource);
} }
} }
} }
private List<ResourceConfigDataLocation> resolve(String location, Resolvable resolvable) { private void assertDirectoryExists(StandardConfigDataReference reference) {
if (!resolvable.isPatternLocation()) { Resource resource = loadResource(reference.getDirectory());
return resolveNonPattern(location, resolvable); StandardConfigDataResource configDataResource = new StandardConfigDataResource(reference, resource);
ConfigDataResourceNotFoundException.throwIfDoesNotExist(configDataResource, resource);
}
private List<StandardConfigDataResource> resolve(StandardConfigDataReference reference) {
if (!reference.isPatternLocation()) {
return resolveNonPattern(reference);
} }
return resolvePattern(location, resolvable); return resolvePattern(reference);
} }
private List<ResourceConfigDataLocation> resolveNonPattern(String location, Resolvable resolvable) { private List<StandardConfigDataResource> resolveNonPattern(StandardConfigDataReference reference) {
Resource resource = loadResource(resolvable.getResourceLocation()); Resource resource = loadResource(reference.getResourceLocation());
if (!resource.exists() && resolvable.isSkippable()) { if (!resource.exists() && reference.isSkippable()) {
logSkippingResource(resolvable); logSkippingResource(reference);
return Collections.emptyList(); return Collections.emptyList();
} }
return Collections.singletonList(createConfigResourceLocation(location, resolvable, resource)); return Collections.singletonList(createConfigResourceLocation(reference, resource));
} }
private List<ResourceConfigDataLocation> resolvePattern(String location, Resolvable resolvable) { private List<StandardConfigDataResource> resolvePattern(StandardConfigDataReference reference) {
validatePatternLocation(resolvable.getResourceLocation()); validatePatternLocation(reference.getResourceLocation());
List<ResourceConfigDataLocation> resolved = new ArrayList<>(); List<StandardConfigDataResource> resolved = new ArrayList<>();
for (Resource resource : getResourcesFromResourceLocationPattern(resolvable.getResourceLocation())) { for (Resource resource : getResourcesFromResourceLocationPattern(reference.getResourceLocation())) {
if (!resource.exists() && resolvable.isSkippable()) { if (!resource.exists() && reference.isSkippable()) {
logSkippingResource(resolvable); logSkippingResource(reference);
} }
else { else {
resolved.add(createConfigResourceLocation(location, resolvable, resource)); resolved.add(createConfigResourceLocation(reference, resource));
} }
} }
return resolved; return resolved;
} }
private void logSkippingResource(Resolvable resolvable) { private void logSkippingResource(StandardConfigDataReference reference) {
this.logger.trace(LogMessage.format("Skipping missing resource location %s", resolvable.getResourceLocation())); this.logger.trace(LogMessage.format("Skipping missing resource %s", reference));
} }
private ResourceConfigDataLocation createConfigResourceLocation(String location, Resolvable resolvable, private StandardConfigDataResource createConfigResourceLocation(StandardConfigDataReference reference,
Resource resource) { Resource resource) {
String name = String.format("Resource config '%s' imported via location \"%s\"", return new StandardConfigDataResource(reference, resource);
resolvable.getResourceLocation(), location);
return new ResourceConfigDataLocation(name, resource, resolvable.getOrigin(), resolvable.getLoader());
} }
private void validatePatternLocation(String resourceLocation) { private void validatePatternLocation(String resourceLocation) {
...@@ -340,84 +340,4 @@ public class ResourceConfigDataLocationResolver ...@@ -340,84 +340,4 @@ public class ResourceConfigDataLocationResolver
} }
} }
/**
* A resource location that could be resolved by this resolver.
*/
private static class Resolvable {
private final String directory;
private final String resourceLocation;
private final boolean optional;
private final String profile;
private Origin origin;
private final PropertySourceLoader loader;
Resolvable(String directory, String rootLocation, boolean optional, String profile, String extension,
Origin origin, PropertySourceLoader loader) {
String profileSuffix = (StringUtils.hasText(profile)) ? "-" + profile : "";
this.directory = directory;
this.resourceLocation = rootLocation + profileSuffix + ((extension != null) ? "." + extension : "");
this.optional = optional;
this.profile = profile;
this.loader = loader;
this.origin = origin;
}
boolean isNonOptionalDirectory() {
return !this.optional && this.directory != null;
}
String getDirectory() {
return this.directory;
}
boolean isSkippable() {
return this.optional || this.directory != null || this.profile != null;
}
boolean isPatternLocation() {
return this.resourceLocation.contains("*");
}
String getResourceLocation() {
return this.resourceLocation;
}
Origin getOrigin() {
return this.origin;
}
PropertySourceLoader getLoader() {
return this.loader;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if ((obj == null) || (getClass() != obj.getClass())) {
return false;
}
Resolvable other = (Resolvable) obj;
return this.resourceLocation.equals(other.resourceLocation);
}
@Override
public int hashCode() {
return this.resourceLocation.hashCode();
}
@Override
public String toString() {
return this.resourceLocation;
}
}
} }
/*
* 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 org.springframework.boot.env.PropertySourceLoader;
import org.springframework.util.StringUtils;
/**
* An reference expanded from the original {@link ConfigDataLocation} that can ultimately
* be resolved to one or more {@link StandardConfigDataResource resources}.
*
* @author Phillip Webb
*/
class StandardConfigDataReference {
private final ConfigDataLocation configDataLocation;
private final String resourceLocation;
private final String directory;
private final String profile;
private final PropertySourceLoader propertySourceLoader;
/**
* Create a new {@link StandardConfigDataReference} instance.
* @param configDataLocation the original location passed to the resolver
* @param directory the directory of the resource or {@code null} if the reference is
* to a file
* @param root the root of the resource location
* @param profile the profile being loaded
* @param extension the file extension for the resource
* @param propertySourceLoader the property source loader that should be used for this
* reference
*/
StandardConfigDataReference(ConfigDataLocation configDataLocation, String directory, String root, String profile,
String extension, PropertySourceLoader propertySourceLoader) {
this.configDataLocation = configDataLocation;
String profileSuffix = (StringUtils.hasText(profile)) ? "-" + profile : "";
this.resourceLocation = root + profileSuffix + ((extension != null) ? "." + extension : "");
this.directory = directory;
this.profile = profile;
this.propertySourceLoader = propertySourceLoader;
}
ConfigDataLocation getConfigDataLocation() {
return this.configDataLocation;
}
String getResourceLocation() {
return this.resourceLocation;
}
boolean isNonOptionalDirectory() {
return !this.configDataLocation.isOptional() && this.directory != null;
}
String getDirectory() {
return this.directory;
}
boolean isSkippable() {
return this.configDataLocation.isOptional() || this.directory != null || this.profile != null;
}
boolean isPatternLocation() {
return this.resourceLocation.contains("*");
}
PropertySourceLoader getPropertySourceLoader() {
return this.propertySourceLoader;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if ((obj == null) || (getClass() != obj.getClass())) {
return false;
}
StandardConfigDataReference other = (StandardConfigDataReference) obj;
return this.resourceLocation.equals(other.resourceLocation);
}
@Override
public int hashCode() {
return this.resourceLocation.hashCode();
}
@Override
public String toString() {
return this.resourceLocation;
}
}
...@@ -17,63 +17,43 @@ ...@@ -17,63 +17,43 @@
package org.springframework.boot.context.config; package org.springframework.boot.context.config;
import java.io.IOException; import java.io.IOException;
import java.util.List;
import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.boot.origin.Origin;
import org.springframework.boot.origin.OriginTrackedResource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.FileUrlResource; import org.springframework.core.io.FileUrlResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
* {@link ConfigDataLocation} backed by a {@link Resource}. * {@link ConfigDataResource} backed by a {@link Resource}.
* *
* @author Madhura Bhave * @author Madhura Bhave
* @author Phillip Webb * @author Phillip Webb
* @since 2.4.0 * @since 2.4.0
*/ */
public class ResourceConfigDataLocation extends ConfigDataLocation { public class StandardConfigDataResource extends ConfigDataResource {
private final String name; private final StandardConfigDataReference reference;
private final Resource resource; private final Resource resource;
private final Origin origin;
private final PropertySourceLoader propertySourceLoader;
/** /**
* Create a new {@link ResourceConfigDataLocation} instance. * Create a new {@link StandardConfigDataResource} instance.
* @param name the source location * @param reference the resource reference
* @param resource the underlying resource * @param resource the underlying resource
* @param origin the origin of the resource
* @param propertySourceLoader the loader that should be used to load the resource
*/ */
ResourceConfigDataLocation(String name, Resource resource, Origin origin, StandardConfigDataResource(StandardConfigDataReference reference, Resource resource) {
PropertySourceLoader propertySourceLoader) { Assert.notNull(reference, "Reference must not be null");
Assert.notNull(name, "Name must not be null");
Assert.notNull(resource, "Resource must not be null"); Assert.notNull(resource, "Resource must not be null");
Assert.notNull(propertySourceLoader, "PropertySourceLoader must not be null"); this.reference = reference;
this.name = name;
this.resource = resource; this.resource = resource;
this.origin = origin;
this.propertySourceLoader = propertySourceLoader;
}
Resource getResource() {
return this.resource;
} }
String getLocation() { StandardConfigDataReference getReference() {
return this.name; return this.reference;
} }
List<PropertySource<?>> load() throws IOException { Resource getResource() {
Resource resource = OriginTrackedResource.of(this.resource, this.origin); return this.resource;
return this.propertySourceLoader.load(this.name, resource);
} }
@Override @Override
...@@ -84,7 +64,7 @@ public class ResourceConfigDataLocation extends ConfigDataLocation { ...@@ -84,7 +64,7 @@ public class ResourceConfigDataLocation extends ConfigDataLocation {
if (obj == null || getClass() != obj.getClass()) { if (obj == null || getClass() != obj.getClass()) {
return false; return false;
} }
ResourceConfigDataLocation other = (ResourceConfigDataLocation) obj; StandardConfigDataResource other = (StandardConfigDataResource) obj;
return this.resource.equals(other.resource); return this.resource.equals(other.resource);
} }
......
...@@ -25,22 +25,22 @@ package org.springframework.boot.context.config; ...@@ -25,22 +25,22 @@ package org.springframework.boot.context.config;
*/ */
public class UnsupportedConfigDataLocationException extends ConfigDataException { public class UnsupportedConfigDataLocationException extends ConfigDataException {
private final String location; private final ConfigDataLocation location;
/** /**
* Create a new {@link UnsupportedConfigDataLocationException} instance. * Create a new {@link UnsupportedConfigDataLocationException} instance.
* @param location the unsupported location * @param location the unsupported location
*/ */
UnsupportedConfigDataLocationException(String location) { UnsupportedConfigDataLocationException(ConfigDataLocation location) {
super("Unsupported config data location '" + location + "'", null); super("Unsupported config data location '" + location + "'", null);
this.location = location; this.location = location;
} }
/** /**
* Return the unsupported location. * Return the unsupported location reference.
* @return the unsupported location * @return the unsupported location reference
*/ */
public String getLocation() { public ConfigDataLocation getLocation() {
return this.location; return this.location;
} }
......
...@@ -12,12 +12,12 @@ org.springframework.boot.env.YamlPropertySourceLoader ...@@ -12,12 +12,12 @@ org.springframework.boot.env.YamlPropertySourceLoader
# ConfigData Location Resolvers # ConfigData Location Resolvers
org.springframework.boot.context.config.ConfigDataLocationResolver=\ org.springframework.boot.context.config.ConfigDataLocationResolver=\
org.springframework.boot.context.config.ConfigTreeConfigDataLocationResolver,\ org.springframework.boot.context.config.ConfigTreeConfigDataLocationResolver,\
org.springframework.boot.context.config.ResourceConfigDataLocationResolver org.springframework.boot.context.config.StandardConfigDataLocationResolver
# ConfigData Loaders # ConfigData Loaders
org.springframework.boot.context.config.ConfigDataLoader=\ org.springframework.boot.context.config.ConfigDataLoader=\
org.springframework.boot.context.config.ConfigTreeConfigDataLoader,\ org.springframework.boot.context.config.ConfigTreeConfigDataLoader,\
org.springframework.boot.context.config.ResourceConfigDataLoader org.springframework.boot.context.config.StandardConfigDataLoader
# Run Listeners # Run Listeners
org.springframework.boot.SpringApplicationRunListener=\ org.springframework.boot.SpringApplicationRunListener=\
......
...@@ -44,18 +44,20 @@ import static org.mockito.Mockito.mock; ...@@ -44,18 +44,20 @@ import static org.mockito.Mockito.mock;
*/ */
class ConfigDataEnvironmentContributorTests { class ConfigDataEnvironmentContributorTests {
private static final ConfigDataLocation TEST_LOCATION = ConfigDataLocation.of("test");
private ConfigDataActivationContext activationContext = new ConfigDataActivationContext(CloudPlatform.KUBERNETES, private ConfigDataActivationContext activationContext = new ConfigDataActivationContext(CloudPlatform.KUBERNETES,
null); null);
@Test @Test
void getKindReturnsKind() { void getKindReturnsKind() {
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("test"); ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(TEST_LOCATION);
assertThat(contributor.getKind()).isEqualTo(Kind.INITIAL_IMPORT); assertThat(contributor.getKind()).isEqualTo(Kind.INITIAL_IMPORT);
} }
@Test @Test
void isActiveWhenPropertiesIsNullReturnsTrue() { void isActiveWhenPropertiesIsNullReturnsTrue() {
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("test"); ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(TEST_LOCATION);
assertThat(contributor.isActive(null)).isTrue(); assertThat(contributor.isActive(null)).isTrue();
} }
...@@ -80,10 +82,10 @@ class ConfigDataEnvironmentContributorTests { ...@@ -80,10 +82,10 @@ class ConfigDataEnvironmentContributorTests {
@Test @Test
void getLocationReturnsLocation() { void getLocationReturnsLocation() {
ConfigData configData = new ConfigData(Collections.singleton(new MockPropertySource())); ConfigData configData = new ConfigData(Collections.singleton(new MockPropertySource()));
ConfigDataLocation location = mock(ConfigDataLocation.class); ConfigDataResource resource = mock(ConfigDataResource.class);
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofUnboundImport(location, ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofUnboundImport(resource,
configData, 0); configData, 0);
assertThat(contributor.getLocation()).isSameAs(location); assertThat(contributor.getResource()).isSameAs(resource);
} }
@Test @Test
...@@ -117,7 +119,8 @@ class ConfigDataEnvironmentContributorTests { ...@@ -117,7 +119,8 @@ class ConfigDataEnvironmentContributorTests {
propertySource.setProperty("spring.config.import", "spring,boot"); propertySource.setProperty("spring.config.import", "spring,boot");
ConfigData configData = new ConfigData(Collections.singleton(propertySource)); ConfigData configData = new ConfigData(Collections.singleton(propertySource));
ConfigDataEnvironmentContributor contributor = createBoundContributor(null, configData, 0); ConfigDataEnvironmentContributor contributor = createBoundContributor(null, configData, 0);
assertThat(contributor.getImports()).containsExactly("spring", "boot"); assertThat(contributor.getImports()).containsExactly(ConfigDataLocation.of("spring"),
ConfigDataLocation.of("boot"));
} }
@Test @Test
...@@ -239,7 +242,7 @@ class ConfigDataEnvironmentContributorTests { ...@@ -239,7 +242,7 @@ class ConfigDataEnvironmentContributorTests {
ConfigDataEnvironmentContributor two = createBoundContributor("two"); ConfigDataEnvironmentContributor two = createBoundContributor("two");
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.of(Arrays.asList(one, two)); ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.of(Arrays.asList(one, two));
assertThat(contributor.getKind()).isEqualTo(Kind.ROOT); assertThat(contributor.getKind()).isEqualTo(Kind.ROOT);
assertThat(contributor.getLocation()).isNull(); assertThat(contributor.getResource()).isNull();
assertThat(contributor.getImports()).isEmpty(); assertThat(contributor.getImports()).isEmpty();
assertThat(contributor.isActive(this.activationContext)).isTrue(); assertThat(contributor.isActive(this.activationContext)).isTrue();
assertThat(contributor.getPropertySource()).isNull(); assertThat(contributor.getPropertySource()).isNull();
...@@ -249,10 +252,10 @@ class ConfigDataEnvironmentContributorTests { ...@@ -249,10 +252,10 @@ class ConfigDataEnvironmentContributorTests {
@Test @Test
void ofInitialImportCreatedInitialImportContributor() { void ofInitialImportCreatedInitialImportContributor() {
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("test"); ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(TEST_LOCATION);
assertThat(contributor.getKind()).isEqualTo(Kind.INITIAL_IMPORT); assertThat(contributor.getKind()).isEqualTo(Kind.INITIAL_IMPORT);
assertThat(contributor.getLocation()).isNull(); assertThat(contributor.getResource()).isNull();
assertThat(contributor.getImports()).containsExactly("test"); assertThat(contributor.getImports()).containsExactly(TEST_LOCATION);
assertThat(contributor.isActive(this.activationContext)).isTrue(); assertThat(contributor.isActive(this.activationContext)).isTrue();
assertThat(contributor.getPropertySource()).isNull(); assertThat(contributor.getPropertySource()).isNull();
assertThat(contributor.getConfigurationPropertySource()).isNull(); assertThat(contributor.getConfigurationPropertySource()).isNull();
...@@ -266,7 +269,7 @@ class ConfigDataEnvironmentContributorTests { ...@@ -266,7 +269,7 @@ class ConfigDataEnvironmentContributorTests {
propertySource.setProperty("spring.config.activate.on-cloud-platform", "cloudfoundry"); propertySource.setProperty("spring.config.activate.on-cloud-platform", "cloudfoundry");
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofExisting(propertySource); ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofExisting(propertySource);
assertThat(contributor.getKind()).isEqualTo(Kind.EXISTING); assertThat(contributor.getKind()).isEqualTo(Kind.EXISTING);
assertThat(contributor.getLocation()).isNull(); assertThat(contributor.getResource()).isNull();
assertThat(contributor.getImports()).isEmpty(); // Properties must not be bound assertThat(contributor.getImports()).isEmpty(); // Properties must not be bound
assertThat(contributor.isActive(this.activationContext)).isTrue(); assertThat(contributor.isActive(this.activationContext)).isTrue();
assertThat(contributor.getPropertySource()).isEqualTo(propertySource); assertThat(contributor.getPropertySource()).isEqualTo(propertySource);
...@@ -276,14 +279,14 @@ class ConfigDataEnvironmentContributorTests { ...@@ -276,14 +279,14 @@ class ConfigDataEnvironmentContributorTests {
@Test @Test
void ofUnboundImportCreatesImportedContributor() { void ofUnboundImportCreatesImportedContributor() {
TestLocation location = new TestLocation("test"); TestResource location = new TestResource("test");
MockPropertySource propertySource = new MockPropertySource(); MockPropertySource propertySource = new MockPropertySource();
propertySource.setProperty("spring.config.import", "test"); propertySource.setProperty("spring.config.import", "test");
ConfigData configData = new ConfigData(Collections.singleton(propertySource)); ConfigData configData = new ConfigData(Collections.singleton(propertySource));
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofUnboundImport(location, ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofUnboundImport(location,
configData, 0); configData, 0);
assertThat(contributor.getKind()).isEqualTo(Kind.UNBOUND_IMPORT); assertThat(contributor.getKind()).isEqualTo(Kind.UNBOUND_IMPORT);
assertThat(contributor.getLocation()).isSameAs(location); assertThat(contributor.getResource()).isSameAs(location);
assertThat(contributor.getImports()).isEmpty(); assertThat(contributor.getImports()).isEmpty();
assertThat(contributor.isActive(this.activationContext)).isTrue(); assertThat(contributor.isActive(this.activationContext)).isTrue();
assertThat(contributor.getPropertySource()).isEqualTo(propertySource); assertThat(contributor.getPropertySource()).isEqualTo(propertySource);
...@@ -293,14 +296,14 @@ class ConfigDataEnvironmentContributorTests { ...@@ -293,14 +296,14 @@ class ConfigDataEnvironmentContributorTests {
@Test @Test
void bindCreatesImportedContributor() { void bindCreatesImportedContributor() {
TestLocation location = new TestLocation("test"); TestResource location = new TestResource("test");
MockPropertySource propertySource = new MockPropertySource(); MockPropertySource propertySource = new MockPropertySource();
propertySource.setProperty("spring.config.import", "test"); propertySource.setProperty("spring.config.import", "test");
ConfigData configData = new ConfigData(Collections.singleton(propertySource)); ConfigData configData = new ConfigData(Collections.singleton(propertySource));
ConfigDataEnvironmentContributor contributor = createBoundContributor(location, configData, 0); ConfigDataEnvironmentContributor contributor = createBoundContributor(location, configData, 0);
assertThat(contributor.getKind()).isEqualTo(Kind.BOUND_IMPORT); assertThat(contributor.getKind()).isEqualTo(Kind.BOUND_IMPORT);
assertThat(contributor.getLocation()).isSameAs(location); assertThat(contributor.getResource()).isSameAs(location);
assertThat(contributor.getImports()).containsExactly("test"); assertThat(contributor.getImports()).containsExactly(TEST_LOCATION);
assertThat(contributor.isActive(this.activationContext)).isTrue(); assertThat(contributor.isActive(this.activationContext)).isTrue();
assertThat(contributor.getPropertySource()).isEqualTo(propertySource); assertThat(contributor.getPropertySource()).isEqualTo(propertySource);
assertThat(contributor.getConfigurationPropertySource()).isNotNull(); assertThat(contributor.getConfigurationPropertySource()).isNotNull();
...@@ -309,13 +312,13 @@ class ConfigDataEnvironmentContributorTests { ...@@ -309,13 +312,13 @@ class ConfigDataEnvironmentContributorTests {
@Test @Test
void bindWhenConfigDataHasIgnoreImportsOptionsCreatesImportedContributorWithoutImports() { void bindWhenConfigDataHasIgnoreImportsOptionsCreatesImportedContributorWithoutImports() {
TestLocation location = new TestLocation("test"); TestResource location = new TestResource("test");
MockPropertySource propertySource = new MockPropertySource(); MockPropertySource propertySource = new MockPropertySource();
propertySource.setProperty("spring.config.import", "test"); propertySource.setProperty("spring.config.import", "test");
ConfigData configData = new ConfigData(Collections.singleton(propertySource), ConfigData.Option.IGNORE_IMPORTS); ConfigData configData = new ConfigData(Collections.singleton(propertySource), ConfigData.Option.IGNORE_IMPORTS);
ConfigDataEnvironmentContributor contributor = createBoundContributor(location, configData, 0); ConfigDataEnvironmentContributor contributor = createBoundContributor(location, configData, 0);
assertThat(contributor.getKind()).isEqualTo(Kind.BOUND_IMPORT); assertThat(contributor.getKind()).isEqualTo(Kind.BOUND_IMPORT);
assertThat(contributor.getLocation()).isSameAs(location); assertThat(contributor.getResource()).isSameAs(location);
assertThat(contributor.getImports()).isEmpty(); assertThat(contributor.getImports()).isEmpty();
assertThat(contributor.isActive(this.activationContext)).isTrue(); assertThat(contributor.isActive(this.activationContext)).isTrue();
assertThat(contributor.getPropertySource()).isEqualTo(propertySource); assertThat(contributor.getPropertySource()).isEqualTo(propertySource);
...@@ -332,13 +335,13 @@ class ConfigDataEnvironmentContributorTests { ...@@ -332,13 +335,13 @@ class ConfigDataEnvironmentContributorTests {
} }
private ConfigDataEnvironmentContributor createBoundContributor(String location) { private ConfigDataEnvironmentContributor createBoundContributor(String location) {
return createBoundContributor(new TestLocation(location), return createBoundContributor(new TestResource(location),
new ConfigData(Collections.singleton(new MockPropertySource())), 0); new ConfigData(Collections.singleton(new MockPropertySource())), 0);
} }
private ConfigDataEnvironmentContributor createBoundContributor(ConfigDataLocation location, ConfigData configData, private ConfigDataEnvironmentContributor createBoundContributor(ConfigDataResource resource, ConfigData configData,
int propertySourceIndex) { int propertySourceIndex) {
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofUnboundImport(location, ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofUnboundImport(resource,
configData, propertySourceIndex); configData, propertySourceIndex);
Binder binder = new Binder(contributor.getConfigurationPropertySource()); Binder binder = new Binder(contributor.getConfigurationPropertySource());
return contributor.withBoundProperties(binder); return contributor.withBoundProperties(binder);
...@@ -351,14 +354,14 @@ class ConfigDataEnvironmentContributorTests { ...@@ -351,14 +354,14 @@ class ConfigDataEnvironmentContributorTests {
} }
private String getLocationName(ConfigDataEnvironmentContributor contributor) { private String getLocationName(ConfigDataEnvironmentContributor contributor) {
return contributor.getLocation().toString(); return contributor.getResource().toString();
} }
static class TestLocation extends ConfigDataLocation { static class TestResource extends ConfigDataResource {
private final String location; private final String location;
TestLocation(String location) { TestResource(String location) {
this.location = location; this.location = location;
} }
......
...@@ -57,6 +57,10 @@ import static org.mockito.Mockito.verify; ...@@ -57,6 +57,10 @@ import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class ConfigDataEnvironmentContributorsTests { class ConfigDataEnvironmentContributorsTests {
private static final ConfigDataLocation LOCATION_1 = ConfigDataLocation.of("location1");
private static final ConfigDataLocation LOCATION_2 = ConfigDataLocation.of("location2");
private DeferredLogFactory logFactory = Supplier::get; private DeferredLogFactory logFactory = Supplier::get;
private DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext(); private DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
...@@ -80,16 +84,15 @@ class ConfigDataEnvironmentContributorsTests { ...@@ -80,16 +84,15 @@ class ConfigDataEnvironmentContributorsTests {
this.environment = new MockEnvironment(); this.environment = new MockEnvironment();
this.binder = Binder.get(this.environment); this.binder = Binder.get(this.environment);
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext, ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext,
ConfigDataLocationNotFoundAction.FAIL, this.binder, null); this.binder, null);
ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext, ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext);
ConfigDataLocationNotFoundAction.FAIL); this.importer = new ConfigDataImporter(this.logFactory, ConfigDataNotFoundAction.FAIL, resolvers, loaders);
this.importer = new ConfigDataImporter(resolvers, loaders);
this.activationContext = new ConfigDataActivationContext(CloudPlatform.KUBERNETES, null); this.activationContext = new ConfigDataActivationContext(CloudPlatform.KUBERNETES, null);
} }
@Test @Test
void createCreatesWithInitialContributors() { void createCreatesWithInitialContributors() {
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("test"); ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapContext, Arrays.asList(contributor)); this.bootstrapContext, Arrays.asList(contributor));
Iterator<ConfigDataEnvironmentContributor> iterator = contributors.iterator(); Iterator<ConfigDataEnvironmentContributor> iterator = contributors.iterator();
...@@ -111,13 +114,13 @@ class ConfigDataEnvironmentContributorsTests { ...@@ -111,13 +114,13 @@ class ConfigDataEnvironmentContributorsTests {
@Test @Test
void withProcessedImportsResolvesAndLoads() { void withProcessedImportsResolvesAndLoads() {
this.importer = mock(ConfigDataImporter.class); this.importer = mock(ConfigDataImporter.class);
List<String> locations = Arrays.asList("testimport"); List<ConfigDataLocation> locations = Arrays.asList(LOCATION_1);
MockPropertySource propertySource = new MockPropertySource(); MockPropertySource propertySource = new MockPropertySource();
Map<ConfigDataLocation, ConfigData> imported = new LinkedHashMap<>(); Map<ConfigDataResource, ConfigData> imported = new LinkedHashMap<>();
imported.put(new TestConfigDataLocation("a"), new ConfigData(Arrays.asList(propertySource))); imported.put(new TestConfigDataResource("a"), new ConfigData(Arrays.asList(propertySource)));
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations))) given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations)))
.willReturn(imported); .willReturn(imported);
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("testimport"); ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapContext, Arrays.asList(contributor)); this.bootstrapContext, Arrays.asList(contributor));
ConfigDataEnvironmentContributors withProcessedImports = contributors.withProcessedImports(this.importer, ConfigDataEnvironmentContributors withProcessedImports = contributors.withProcessedImports(this.importer,
...@@ -132,21 +135,20 @@ class ConfigDataEnvironmentContributorsTests { ...@@ -132,21 +135,20 @@ class ConfigDataEnvironmentContributorsTests {
@Test @Test
void withProcessedImportsResolvesAndLoadsChainedImports() { void withProcessedImportsResolvesAndLoadsChainedImports() {
this.importer = mock(ConfigDataImporter.class); this.importer = mock(ConfigDataImporter.class);
List<String> initialLocations = Arrays.asList("initialimport"); List<ConfigDataLocation> initialLocations = Arrays.asList(LOCATION_1);
MockPropertySource initialPropertySource = new MockPropertySource(); MockPropertySource initialPropertySource = new MockPropertySource();
initialPropertySource.setProperty("spring.config.import", "secondimport"); initialPropertySource.setProperty("spring.config.import", "location2");
Map<ConfigDataLocation, ConfigData> initialImported = new LinkedHashMap<>(); Map<ConfigDataResource, ConfigData> initialImported = new LinkedHashMap<>();
initialImported.put(new TestConfigDataLocation("a"), new ConfigData(Arrays.asList(initialPropertySource))); initialImported.put(new TestConfigDataResource("a"), new ConfigData(Arrays.asList(initialPropertySource)));
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(initialLocations))) given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(initialLocations)))
.willReturn(initialImported); .willReturn(initialImported);
List<String> secondLocations = Arrays.asList("secondimport"); List<ConfigDataLocation> secondLocations = Arrays.asList(LOCATION_2);
MockPropertySource secondPropertySource = new MockPropertySource(); MockPropertySource secondPropertySource = new MockPropertySource();
Map<ConfigDataLocation, ConfigData> secondImported = new LinkedHashMap<>(); Map<ConfigDataResource, ConfigData> secondImported = new LinkedHashMap<>();
secondImported.put(new TestConfigDataLocation("b"), new ConfigData(Arrays.asList(secondPropertySource))); secondImported.put(new TestConfigDataResource("b"), new ConfigData(Arrays.asList(secondPropertySource)));
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(secondLocations))) given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(secondLocations)))
.willReturn(secondImported); .willReturn(secondImported);
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1);
.ofInitialImport("initialimport");
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapContext, Arrays.asList(contributor)); this.bootstrapContext, Arrays.asList(contributor));
ConfigDataEnvironmentContributors withProcessedImports = contributors.withProcessedImports(this.importer, ConfigDataEnvironmentContributors withProcessedImports = contributors.withProcessedImports(this.importer,
...@@ -166,13 +168,13 @@ class ConfigDataEnvironmentContributorsTests { ...@@ -166,13 +168,13 @@ class ConfigDataEnvironmentContributorsTests {
ConfigDataEnvironmentContributor existingContributor = ConfigDataEnvironmentContributor ConfigDataEnvironmentContributor existingContributor = ConfigDataEnvironmentContributor
.ofExisting(existingPropertySource); .ofExisting(existingPropertySource);
this.importer = mock(ConfigDataImporter.class); this.importer = mock(ConfigDataImporter.class);
List<String> locations = Arrays.asList("testimport"); List<ConfigDataLocation> locations = Arrays.asList(LOCATION_1);
MockPropertySource propertySource = new MockPropertySource(); MockPropertySource propertySource = new MockPropertySource();
Map<ConfigDataLocation, ConfigData> imported = new LinkedHashMap<>(); Map<ConfigDataResource, ConfigData> imported = new LinkedHashMap<>();
imported.put(new TestConfigDataLocation("a'"), new ConfigData(Arrays.asList(propertySource))); imported.put(new TestConfigDataResource("a'"), new ConfigData(Arrays.asList(propertySource)));
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations))) given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations)))
.willReturn(imported); .willReturn(imported);
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("testimport"); ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapContext, Arrays.asList(existingContributor, contributor)); this.bootstrapContext, Arrays.asList(existingContributor, contributor));
contributors.withProcessedImports(this.importer, this.activationContext); contributors.withProcessedImports(this.importer, this.activationContext);
...@@ -184,21 +186,20 @@ class ConfigDataEnvironmentContributorsTests { ...@@ -184,21 +186,20 @@ class ConfigDataEnvironmentContributorsTests {
@Test @Test
void withProcessedImportsProvidesLocationResolverContextWithAccessToParent() { void withProcessedImportsProvidesLocationResolverContextWithAccessToParent() {
this.importer = mock(ConfigDataImporter.class); this.importer = mock(ConfigDataImporter.class);
List<String> initialLocations = Arrays.asList("initialimport"); List<ConfigDataLocation> initialLocations = Arrays.asList(LOCATION_1);
MockPropertySource initialPropertySource = new MockPropertySource(); MockPropertySource initialPropertySource = new MockPropertySource();
initialPropertySource.setProperty("spring.config.import", "secondimport"); initialPropertySource.setProperty("spring.config.import", "location2");
Map<ConfigDataLocation, ConfigData> initialImported = new LinkedHashMap<>(); Map<ConfigDataResource, ConfigData> initialImported = new LinkedHashMap<>();
initialImported.put(new TestConfigDataLocation("a"), new ConfigData(Arrays.asList(initialPropertySource))); initialImported.put(new TestConfigDataResource("a"), new ConfigData(Arrays.asList(initialPropertySource)));
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(initialLocations))) given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(initialLocations)))
.willReturn(initialImported); .willReturn(initialImported);
List<String> secondLocations = Arrays.asList("secondimport"); List<ConfigDataLocation> secondLocations = Arrays.asList(LOCATION_2);
MockPropertySource secondPropertySource = new MockPropertySource(); MockPropertySource secondPropertySource = new MockPropertySource();
Map<ConfigDataLocation, ConfigData> secondImported = new LinkedHashMap<>(); Map<ConfigDataResource, ConfigData> secondImported = new LinkedHashMap<>();
secondImported.put(new TestConfigDataLocation("b"), new ConfigData(Arrays.asList(secondPropertySource))); secondImported.put(new TestConfigDataResource("b"), new ConfigData(Arrays.asList(secondPropertySource)));
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(secondLocations))) given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(secondLocations)))
.willReturn(secondImported); .willReturn(secondImported);
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1);
.ofInitialImport("initialimport");
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapContext, Arrays.asList(contributor)); this.bootstrapContext, Arrays.asList(contributor));
contributors.withProcessedImports(this.importer, this.activationContext); contributors.withProcessedImports(this.importer, this.activationContext);
...@@ -214,13 +215,13 @@ class ConfigDataEnvironmentContributorsTests { ...@@ -214,13 +215,13 @@ class ConfigDataEnvironmentContributorsTests {
ConfigDataEnvironmentContributor existingContributor = ConfigDataEnvironmentContributor ConfigDataEnvironmentContributor existingContributor = ConfigDataEnvironmentContributor
.ofExisting(existingPropertySource); .ofExisting(existingPropertySource);
this.importer = mock(ConfigDataImporter.class); this.importer = mock(ConfigDataImporter.class);
List<String> locations = Arrays.asList("testimport"); List<ConfigDataLocation> locations = Arrays.asList(LOCATION_1);
MockPropertySource propertySource = new MockPropertySource(); MockPropertySource propertySource = new MockPropertySource();
Map<ConfigDataLocation, ConfigData> imported = new LinkedHashMap<>(); Map<ConfigDataResource, ConfigData> imported = new LinkedHashMap<>();
imported.put(new TestConfigDataLocation("a'"), new ConfigData(Arrays.asList(propertySource))); imported.put(new TestConfigDataResource("a'"), new ConfigData(Arrays.asList(propertySource)));
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations))) given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations)))
.willReturn(imported); .willReturn(imported);
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("testimport"); ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapContext, Arrays.asList(existingContributor, contributor)); this.bootstrapContext, Arrays.asList(existingContributor, contributor));
contributors.withProcessedImports(this.importer, this.activationContext); contributors.withProcessedImports(this.importer, this.activationContext);
...@@ -236,13 +237,13 @@ class ConfigDataEnvironmentContributorsTests { ...@@ -236,13 +237,13 @@ class ConfigDataEnvironmentContributorsTests {
ConfigDataEnvironmentContributor existingContributor = ConfigDataEnvironmentContributor ConfigDataEnvironmentContributor existingContributor = ConfigDataEnvironmentContributor
.ofExisting(existingPropertySource); .ofExisting(existingPropertySource);
this.importer = mock(ConfigDataImporter.class); this.importer = mock(ConfigDataImporter.class);
List<String> locations = Arrays.asList("testimport"); List<ConfigDataLocation> locations = Arrays.asList(LOCATION_1);
MockPropertySource propertySource = new MockPropertySource(); MockPropertySource propertySource = new MockPropertySource();
Map<ConfigDataLocation, ConfigData> imported = new LinkedHashMap<>(); Map<ConfigDataResource, ConfigData> imported = new LinkedHashMap<>();
imported.put(new TestConfigDataLocation("a'"), new ConfigData(Arrays.asList(propertySource))); imported.put(new TestConfigDataResource("a'"), new ConfigData(Arrays.asList(propertySource)));
given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations))) given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations)))
.willReturn(imported); .willReturn(imported);
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("testimport"); ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1);
ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory,
this.bootstrapContext, Arrays.asList(existingContributor, contributor)); this.bootstrapContext, Arrays.asList(existingContributor, contributor));
contributors.withProcessedImports(this.importer, this.activationContext); contributors.withProcessedImports(this.importer, this.activationContext);
...@@ -382,11 +383,11 @@ class ConfigDataEnvironmentContributorsTests { ...@@ -382,11 +383,11 @@ class ConfigDataEnvironmentContributorsTests {
return contributor.withBoundProperties(binder); return contributor.withBoundProperties(binder);
} }
private static class TestConfigDataLocation extends ConfigDataLocation { private static class TestConfigDataResource extends ConfigDataResource {
private final String value; private final String value;
TestConfigDataLocation(String value) { TestConfigDataResource(String value) {
this.value = value; this.value = value;
} }
......
...@@ -51,7 +51,8 @@ class ConfigDataEnvironmentPostProcessorBootstrapContextIntegrationTests { ...@@ -51,7 +51,8 @@ class ConfigDataEnvironmentPostProcessorBootstrapContextIntegrationTests {
LoaderHelper bean = context.getBean(TestConfigDataBootstrap.LoaderHelper.class); LoaderHelper bean = context.getBean(TestConfigDataBootstrap.LoaderHelper.class);
assertThat(bean).isNotNull(); assertThat(bean).isNotNull();
assertThat(bean.getBound()).isEqualTo("igotbound"); assertThat(bean.getBound()).isEqualTo("igotbound");
assertThat(bean.getLocation().getResolverHelper().getLocation()).isEqualTo("testbootstrap:test"); assertThat(bean.getLocation().getResolverHelper().getLocation())
.isEqualTo(ConfigDataLocation.of("testbootstrap:test"));
} }
} }
......
...@@ -378,8 +378,8 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests { ...@@ -378,8 +378,8 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests {
List<String> names = StreamSupport.stream(context.getEnvironment().getPropertySources().spliterator(), false) List<String> names = StreamSupport.stream(context.getEnvironment().getPropertySources().spliterator(), false)
.map(org.springframework.core.env.PropertySource::getName).collect(Collectors.toList()); .map(org.springframework.core.env.PropertySource::getName).collect(Collectors.toList());
assertThat(names).contains( assertThat(names).contains(
"Resource config 'classpath:configdata/profiles/testsetprofiles.yml' imported via location \"classpath:configdata/profiles/\" (document #0)", "Config resource 'classpath:configdata/profiles/testsetprofiles.yml' via location 'classpath:configdata/profiles/' (document #0)",
"Resource config 'classpath:configdata/profiles/testsetprofiles.yml' imported via location \"classpath:configdata/profiles/\" (document #1)"); "Config resource 'classpath:configdata/profiles/testsetprofiles.yml' via location 'classpath:configdata/profiles/' (document #1)");
} }
@Test @Test
...@@ -406,8 +406,8 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests { ...@@ -406,8 +406,8 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests {
String location = "file:src/test/resources/specificlocation.properties"; String location = "file:src/test/resources/specificlocation.properties";
ConfigurableApplicationContext context = this.application.run("--spring.config.location=" + location); ConfigurableApplicationContext context = this.application.run("--spring.config.location=" + location);
assertThat(context.getEnvironment()).has(matchingPropertySource( assertThat(context.getEnvironment()).has(matchingPropertySource(
"Resource config 'file:src/test/resources/specificlocation.properties' imported via location \"" "Config resource 'file:src/test/resources/specificlocation.properties' via location '" + location
+ location + "\"")); + "'"));
} }
@Test @Test
...@@ -415,8 +415,7 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests { ...@@ -415,8 +415,7 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests {
String location = "src/test/resources/specificlocation.properties"; String location = "src/test/resources/specificlocation.properties";
ConfigurableApplicationContext context = this.application.run("--spring.config.location=" + location); ConfigurableApplicationContext context = this.application.run("--spring.config.location=" + location);
assertThat(context.getEnvironment()).has(matchingPropertySource( assertThat(context.getEnvironment()).has(matchingPropertySource(
"Resource config 'src/test/resources/specificlocation.properties' imported via location \"" + location "Config resource 'src/test/resources/specificlocation.properties' via location '" + location + "'"));
+ "\""));
} }
@Test @Test
...@@ -519,7 +518,7 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests { ...@@ -519,7 +518,7 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests {
@Test @Test
void runWhenConfigLocationHasNonOptionalMissingDirectoryThrowsException() { void runWhenConfigLocationHasNonOptionalMissingDirectoryThrowsException() {
String location = "classpath:application.unknown/"; String location = "classpath:application.unknown/";
assertThatExceptionOfType(ConfigDataLocationNotFoundException.class) assertThatExceptionOfType(ConfigDataResourceNotFoundException.class)
.isThrownBy(() -> this.application.run("--spring.config.location=" + location)); .isThrownBy(() -> this.application.run("--spring.config.location=" + location));
} }
...@@ -553,13 +552,13 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests { ...@@ -553,13 +552,13 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests {
@Test @Test
void runWhenHasNonOptionalImportThrowsException() { void runWhenHasNonOptionalImportThrowsException() {
assertThatExceptionOfType(ConfigDataLocationNotFoundException.class).isThrownBy( assertThatExceptionOfType(ConfigDataResourceNotFoundException.class).isThrownBy(
() -> this.application.run("--spring.config.location=classpath:missing-appplication.properties")); () -> this.application.run("--spring.config.location=classpath:missing-appplication.properties"));
} }
@Test @Test
void runWhenHasNonOptionalImportAndIgnoreNotFoundPropertyDoesNotThrowException() { void runWhenHasNonOptionalImportAndIgnoreNotFoundPropertyDoesNotThrowException() {
this.application.run("--spring.config.on-location-not-found=ignore", this.application.run("--spring.config.on-not-found=ignore",
"--spring.config.location=classpath:missing-appplication.properties"); "--spring.config.location=classpath:missing-appplication.properties");
} }
...@@ -613,6 +612,7 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests { ...@@ -613,6 +612,7 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests {
@Override @Override
public boolean matches(ConfigurableEnvironment value) { public boolean matches(ConfigurableEnvironment value) {
value.getPropertySources().forEach((ps) -> System.out.println(ps.getName()));
return value.getPropertySources().contains(sourceName); return value.getPropertySources().contains(sourceName);
} }
......
...@@ -214,11 +214,9 @@ class ConfigDataEnvironmentTests { ...@@ -214,11 +214,9 @@ class ConfigDataEnvironmentTests {
@Override @Override
protected ConfigDataLocationResolvers createConfigDataLocationResolvers(DeferredLogFactory logFactory, protected ConfigDataLocationResolvers createConfigDataLocationResolvers(DeferredLogFactory logFactory,
ConfigurableBootstrapContext bootstrapContext, ConfigDataLocationNotFoundAction locationNotFoundAction, ConfigurableBootstrapContext bootstrapContext, Binder binder, ResourceLoader resourceLoader) {
Binder binder, ResourceLoader resourceLoader) {
this.configDataLocationResolversBinder = binder; this.configDataLocationResolversBinder = binder;
return super.createConfigDataLocationResolvers(logFactory, bootstrapContext, locationNotFoundAction, binder, return super.createConfigDataLocationResolvers(logFactory, bootstrapContext, binder, resourceLoader);
resourceLoader);
} }
Binder getConfigDataLocationResolversBinder() { Binder getConfigDataLocationResolversBinder() {
......
...@@ -20,6 +20,7 @@ import java.util.Arrays; ...@@ -20,6 +20,7 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.function.Supplier;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
...@@ -28,6 +29,7 @@ import org.mockito.Mock; ...@@ -28,6 +29,7 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.mock.env.MockPropertySource; import org.springframework.mock.env.MockPropertySource;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
...@@ -42,6 +44,8 @@ import static org.mockito.BDDMockito.given; ...@@ -42,6 +44,8 @@ import static org.mockito.BDDMockito.given;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class ConfigDataImporterTests { class ConfigDataImporterTests {
private DeferredLogFactory logFactory = Supplier::get;
@Mock @Mock
private ConfigDataLocationResolvers resolvers; private ConfigDataLocationResolvers resolvers;
...@@ -70,43 +74,49 @@ class ConfigDataImporterTests { ...@@ -70,43 +74,49 @@ class ConfigDataImporterTests {
@Test @Test
void loadImportsResolvesAndLoadsLocations() throws Exception { void loadImportsResolvesAndLoadsLocations() throws Exception {
List<String> locations = Arrays.asList("test1", "test2"); ConfigDataLocation location1 = ConfigDataLocation.of("test1");
TestLocation resolvedLocation1 = new TestLocation(); ConfigDataLocation location2 = ConfigDataLocation.of("test2");
TestLocation resolvedLocation2 = new TestLocation(); TestResource resource1 = new TestResource("r1");
List<ConfigDataLocation> resolvedLocations = Arrays.asList(resolvedLocation1, resolvedLocation2); TestResource resource2 = new TestResource("r2");
ConfigData configData1 = new ConfigData(Collections.singleton(new MockPropertySource())); ConfigData configData1 = new ConfigData(Collections.singleton(new MockPropertySource()));
ConfigData configData2 = new ConfigData(Collections.singleton(new MockPropertySource())); ConfigData configData2 = new ConfigData(Collections.singleton(new MockPropertySource()));
given(this.resolvers.resolveAll(this.locationResolverContext, locations, this.profiles)) given(this.resolvers.resolve(this.locationResolverContext, location1, this.profiles))
.willReturn(resolvedLocations); .willReturn(Collections.singletonList(new ConfigDataResolutionResult(location1, resource1)));
given(this.loaders.load(this.loaderContext, resolvedLocation1)).willReturn(configData1); given(this.resolvers.resolve(this.locationResolverContext, location2, this.profiles))
given(this.loaders.load(this.loaderContext, resolvedLocation2)).willReturn(configData2); .willReturn(Collections.singletonList(new ConfigDataResolutionResult(location2, resource2)));
ConfigDataImporter importer = new ConfigDataImporter(this.resolvers, this.loaders); given(this.loaders.load(this.loaderContext, resource1)).willReturn(configData1);
Collection<ConfigData> loaded = importer given(this.loaders.load(this.loaderContext, resource2)).willReturn(configData2);
.resolveAndLoad(this.activationContext, this.locationResolverContext, this.loaderContext, locations) ConfigDataImporter importer = new ConfigDataImporter(this.logFactory, ConfigDataNotFoundAction.FAIL,
.values(); this.resolvers, this.loaders);
Collection<ConfigData> loaded = importer.resolveAndLoad(this.activationContext, this.locationResolverContext,
this.loaderContext, Arrays.asList(location1, location2)).values();
assertThat(loaded).containsExactly(configData2, configData1); assertThat(loaded).containsExactly(configData2, configData1);
} }
@Test @Test
void loadImportsWhenAlreadyImportedLocationSkipsLoad() throws Exception { void loadImportsWhenAlreadyImportedLocationSkipsLoad() throws Exception {
List<String> locations1and2 = Arrays.asList("test1", "test2"); ConfigDataLocation location1 = ConfigDataLocation.of("test1");
List<String> locations2and3 = Arrays.asList("test2", "test3"); ConfigDataLocation location2 = ConfigDataLocation.of("test2");
TestLocation resolvedLocation1 = new TestLocation(); ConfigDataLocation location3 = ConfigDataLocation.of("test3");
TestLocation resolvedLocation2 = new TestLocation(); List<ConfigDataLocation> locations1and2 = Arrays.asList(location1, location2);
TestLocation resolvedLocation3 = new TestLocation(); List<ConfigDataLocation> locations2and3 = Arrays.asList(location2, location3);
List<ConfigDataLocation> resolvedLocations1and2 = Arrays.asList(resolvedLocation1, resolvedLocation2); TestResource resource1 = new TestResource("r1");
List<ConfigDataLocation> resolvedLocations2and3 = Arrays.asList(resolvedLocation2, resolvedLocation3); TestResource resource2 = new TestResource("r2");
TestResource resource3 = new TestResource("r3");
ConfigData configData1 = new ConfigData(Collections.singleton(new MockPropertySource())); ConfigData configData1 = new ConfigData(Collections.singleton(new MockPropertySource()));
ConfigData configData2 = new ConfigData(Collections.singleton(new MockPropertySource())); ConfigData configData2 = new ConfigData(Collections.singleton(new MockPropertySource()));
ConfigData configData3 = new ConfigData(Collections.singleton(new MockPropertySource())); ConfigData configData3 = new ConfigData(Collections.singleton(new MockPropertySource()));
given(this.resolvers.resolveAll(this.locationResolverContext, locations1and2, this.profiles)) given(this.resolvers.resolve(this.locationResolverContext, location1, this.profiles))
.willReturn(resolvedLocations1and2); .willReturn(Collections.singletonList(new ConfigDataResolutionResult(location1, resource1)));
given(this.resolvers.resolveAll(this.locationResolverContext, locations2and3, this.profiles)) given(this.resolvers.resolve(this.locationResolverContext, location2, this.profiles))
.willReturn(resolvedLocations2and3); .willReturn(Collections.singletonList(new ConfigDataResolutionResult(location2, resource2)));
given(this.loaders.load(this.loaderContext, resolvedLocation1)).willReturn(configData1); given(this.resolvers.resolve(this.locationResolverContext, location3, this.profiles))
given(this.loaders.load(this.loaderContext, resolvedLocation2)).willReturn(configData2); .willReturn(Collections.singletonList(new ConfigDataResolutionResult(location3, resource3)));
given(this.loaders.load(this.loaderContext, resolvedLocation3)).willReturn(configData3); given(this.loaders.load(this.loaderContext, resource1)).willReturn(configData1);
ConfigDataImporter importer = new ConfigDataImporter(this.resolvers, this.loaders); given(this.loaders.load(this.loaderContext, resource2)).willReturn(configData2);
given(this.loaders.load(this.loaderContext, resource3)).willReturn(configData3);
ConfigDataImporter importer = new ConfigDataImporter(this.logFactory, ConfigDataNotFoundAction.FAIL,
this.resolvers, this.loaders);
Collection<ConfigData> loaded1and2 = importer.resolveAndLoad(this.activationContext, Collection<ConfigData> loaded1and2 = importer.resolveAndLoad(this.activationContext,
this.locationResolverContext, this.loaderContext, locations1and2).values(); this.locationResolverContext, this.loaderContext, locations1and2).values();
Collection<ConfigData> loaded2and3 = importer.resolveAndLoad(this.activationContext, Collection<ConfigData> loaded2and3 = importer.resolveAndLoad(this.activationContext,
...@@ -115,7 +125,18 @@ class ConfigDataImporterTests { ...@@ -115,7 +125,18 @@ class ConfigDataImporterTests {
assertThat(loaded2and3).containsExactly(configData3); assertThat(loaded2and3).containsExactly(configData3);
} }
static class TestLocation extends ConfigDataLocation { static class TestResource extends ConfigDataResource {
private final String name;
TestResource(String name) {
this.name = name;
}
@Override
public String toString() {
return this.name;
}
} }
......
...@@ -37,19 +37,19 @@ class ConfigDataLoaderTests { ...@@ -37,19 +37,19 @@ class ConfigDataLoaderTests {
@Test @Test
void isLoadableAlwaysReturnsTrue() { void isLoadableAlwaysReturnsTrue() {
assertThat(this.loader.isLoadable(this.context, new TestConfigDataLocation())).isTrue(); assertThat(this.loader.isLoadable(this.context, new TestConfigDataResource())).isTrue();
} }
static class TestConfigDataLoader implements ConfigDataLoader<TestConfigDataLocation> { static class TestConfigDataLoader implements ConfigDataLoader<TestConfigDataResource> {
@Override @Override
public ConfigData load(ConfigDataLoaderContext context, TestConfigDataLocation location) throws IOException { public ConfigData load(ConfigDataLoaderContext context, TestConfigDataResource resource) throws IOException {
return null; return null;
} }
} }
static class TestConfigDataLocation extends ConfigDataLocation { static class TestConfigDataResource extends ConfigDataResource {
} }
......
...@@ -53,50 +53,48 @@ class ConfigDataLoadersTests { ...@@ -53,50 +53,48 @@ class ConfigDataLoadersTests {
@Test @Test
void createWhenLoaderHasLogParameterInjectsLog() { void createWhenLoaderHasLogParameterInjectsLog() {
new ConfigDataLoaders(this.logFactory, this.bootstrapContext, ConfigDataLocationNotFoundAction.FAIL, new ConfigDataLoaders(this.logFactory, this.bootstrapContext,
Arrays.asList(LoggingConfigDataLoader.class.getName())); Arrays.asList(LoggingConfigDataLoader.class.getName()));
} }
@Test @Test
void createWhenLoaderHasBootstrapParametersInjectsBootstrapContext() { void createWhenLoaderHasBootstrapParametersInjectsBootstrapContext() {
new ConfigDataLoaders(this.logFactory, this.bootstrapContext, ConfigDataLocationNotFoundAction.FAIL, new ConfigDataLoaders(this.logFactory, this.bootstrapContext,
Arrays.asList(BootstrappingConfigDataLoader.class.getName())); Arrays.asList(BootstrappingConfigDataLoader.class.getName()));
assertThat(this.bootstrapContext.get(String.class)).isEqualTo("boot"); assertThat(this.bootstrapContext.get(String.class)).isEqualTo("boot");
} }
@Test @Test
void loadWhenSingleLoaderSupportsLocationReturnsLoadedConfigData() throws Exception { void loadWhenSingleLoaderSupportsLocationReturnsLoadedConfigData() throws Exception {
TestConfigDataLocation location = new TestConfigDataLocation("test"); TestConfigDataResource location = new TestConfigDataResource("test");
ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext, ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext,
ConfigDataLocationNotFoundAction.FAIL, Arrays.asList(TestConfigDataLoader.class.getName())); Arrays.asList(TestConfigDataLoader.class.getName()));
ConfigData loaded = loaders.load(this.context, location); ConfigData loaded = loaders.load(this.context, location);
assertThat(getLoader(loaded)).isInstanceOf(TestConfigDataLoader.class); assertThat(getLoader(loaded)).isInstanceOf(TestConfigDataLoader.class);
} }
@Test @Test
void loadWhenMultipleLoadersSupportLocationThrowsException() throws Exception { void loadWhenMultipleLoadersSupportLocationThrowsException() throws Exception {
TestConfigDataLocation location = new TestConfigDataLocation("test"); TestConfigDataResource location = new TestConfigDataResource("test");
ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext, ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext,
ConfigDataLocationNotFoundAction.FAIL,
Arrays.asList(LoggingConfigDataLoader.class.getName(), TestConfigDataLoader.class.getName())); Arrays.asList(LoggingConfigDataLoader.class.getName(), TestConfigDataLoader.class.getName()));
assertThatIllegalStateException().isThrownBy(() -> loaders.load(this.context, location)) assertThatIllegalStateException().isThrownBy(() -> loaders.load(this.context, location))
.withMessageContaining("Multiple loaders found for location test"); .withMessageContaining("Multiple loaders found for resource 'test'");
} }
@Test @Test
void loadWhenNoLoaderSupportsLocationThrowsException() { void loadWhenNoLoaderSupportsLocationThrowsException() {
TestConfigDataLocation location = new TestConfigDataLocation("test"); TestConfigDataResource location = new TestConfigDataResource("test");
ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext, ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext,
ConfigDataLocationNotFoundAction.FAIL, Arrays.asList(NonLoadableConfigDataLoader.class.getName())); Arrays.asList(NonLoadableConfigDataLoader.class.getName()));
assertThatIllegalStateException().isThrownBy(() -> loaders.load(this.context, location)) assertThatIllegalStateException().isThrownBy(() -> loaders.load(this.context, location))
.withMessage("No loader found for location 'test'"); .withMessage("No loader found for resource 'test'");
} }
@Test @Test
void loadWhenGenericTypeDoesNotMatchSkipsLoader() throws Exception { void loadWhenGenericTypeDoesNotMatchSkipsLoader() throws Exception {
TestConfigDataLocation location = new TestConfigDataLocation("test"); TestConfigDataResource location = new TestConfigDataResource("test");
ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext, ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext,
ConfigDataLocationNotFoundAction.FAIL,
Arrays.asList(OtherConfigDataLoader.class.getName(), SpecificConfigDataLoader.class.getName())); Arrays.asList(OtherConfigDataLoader.class.getName(), SpecificConfigDataLoader.class.getName()));
ConfigData loaded = loaders.load(this.context, location); ConfigData loaded = loaders.load(this.context, location);
assertThat(getLoader(loaded)).isInstanceOf(SpecificConfigDataLoader.class); assertThat(getLoader(loaded)).isInstanceOf(SpecificConfigDataLoader.class);
...@@ -106,20 +104,19 @@ class ConfigDataLoadersTests { ...@@ -106,20 +104,19 @@ class ConfigDataLoadersTests {
return (ConfigDataLoader<?>) loaded.getPropertySources().get(0).getProperty("loader"); return (ConfigDataLoader<?>) loaded.getPropertySources().get(0).getProperty("loader");
} }
private static ConfigData createConfigData(ConfigDataLoader<?> loader, ConfigDataLocation location) { private static ConfigData createConfigData(ConfigDataLoader<?> loader, ConfigDataResource resource) {
MockPropertySource propertySource = new MockPropertySource(); MockPropertySource propertySource = new MockPropertySource();
propertySource.setProperty("loader", loader); propertySource.setProperty("loader", loader);
propertySource.setProperty("location", location); propertySource.setProperty("resource", resource);
List<PropertySource<?>> propertySources = Arrays.asList(propertySource); List<PropertySource<?>> propertySources = Arrays.asList(propertySource);
return new ConfigData(propertySources); return new ConfigData(propertySources);
} }
static class TestConfigDataLocation extends ConfigDataLocation { static class TestConfigDataResource extends ConfigDataResource {
private final String value; private final String value;
TestConfigDataLocation(String value) { TestConfigDataResource(String value) {
this.value = value; this.value = value;
} }
...@@ -130,24 +127,24 @@ class ConfigDataLoadersTests { ...@@ -130,24 +127,24 @@ class ConfigDataLoadersTests {
} }
static class OtherConfigDataLocation extends ConfigDataLocation { static class OtherConfigDataResource extends ConfigDataResource {
} }
static class LoggingConfigDataLoader implements ConfigDataLoader<ConfigDataLocation> { static class LoggingConfigDataLoader implements ConfigDataLoader<ConfigDataResource> {
LoggingConfigDataLoader(Log log) { LoggingConfigDataLoader(Log log) {
assertThat(log).isNotNull(); assertThat(log).isNotNull();
} }
@Override @Override
public ConfigData load(ConfigDataLoaderContext context, ConfigDataLocation location) throws IOException { public ConfigData load(ConfigDataLoaderContext context, ConfigDataResource resource) throws IOException {
throw new AssertionError("Unexpected call"); throw new AssertionError("Unexpected call");
} }
} }
static class BootstrappingConfigDataLoader implements ConfigDataLoader<ConfigDataLocation> { static class BootstrappingConfigDataLoader implements ConfigDataLoader<ConfigDataResource> {
BootstrappingConfigDataLoader(ConfigurableBootstrapContext configurableBootstrapContext, BootstrappingConfigDataLoader(ConfigurableBootstrapContext configurableBootstrapContext,
BootstrapRegistry bootstrapRegistry, BootstrapContext bootstrapContext) { BootstrapRegistry bootstrapRegistry, BootstrapContext bootstrapContext) {
...@@ -159,17 +156,17 @@ class ConfigDataLoadersTests { ...@@ -159,17 +156,17 @@ class ConfigDataLoadersTests {
} }
@Override @Override
public ConfigData load(ConfigDataLoaderContext context, ConfigDataLocation location) throws IOException { public ConfigData load(ConfigDataLoaderContext context, ConfigDataResource resource) throws IOException {
throw new AssertionError("Unexpected call"); throw new AssertionError("Unexpected call");
} }
} }
static class TestConfigDataLoader implements ConfigDataLoader<ConfigDataLocation> { static class TestConfigDataLoader implements ConfigDataLoader<ConfigDataResource> {
@Override @Override
public ConfigData load(ConfigDataLoaderContext context, ConfigDataLocation location) throws IOException { public ConfigData load(ConfigDataLoaderContext context, ConfigDataResource resource) throws IOException {
return createConfigData(this, location); return createConfigData(this, resource);
} }
} }
...@@ -177,25 +174,25 @@ class ConfigDataLoadersTests { ...@@ -177,25 +174,25 @@ class ConfigDataLoadersTests {
static class NonLoadableConfigDataLoader extends TestConfigDataLoader { static class NonLoadableConfigDataLoader extends TestConfigDataLoader {
@Override @Override
public boolean isLoadable(ConfigDataLoaderContext context, ConfigDataLocation location) { public boolean isLoadable(ConfigDataLoaderContext context, ConfigDataResource resource) {
return false; return false;
} }
} }
static class SpecificConfigDataLoader implements ConfigDataLoader<TestConfigDataLocation> { static class SpecificConfigDataLoader implements ConfigDataLoader<TestConfigDataResource> {
@Override @Override
public ConfigData load(ConfigDataLoaderContext context, TestConfigDataLocation location) throws IOException { public ConfigData load(ConfigDataLoaderContext context, TestConfigDataResource location) throws IOException {
return createConfigData(this, location); return createConfigData(this, location);
} }
} }
static class OtherConfigDataLoader implements ConfigDataLoader<OtherConfigDataLocation> { static class OtherConfigDataLoader implements ConfigDataLoader<OtherConfigDataResource> {
@Override @Override
public ConfigData load(ConfigDataLoaderContext context, OtherConfigDataLocation location) throws IOException { public ConfigData load(ConfigDataLoaderContext context, OtherConfigDataResource location) throws IOException {
return createConfigData(this, location); return createConfigData(this, location);
} }
......
/*
* 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.List;
import org.junit.jupiter.api.Test;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ConfigDataLocationBindHandler}.
*
* @author Phillip Webb
*/
class ConfigDataLocationBindHandlerTests {
private static final Bindable<ConfigDataLocation[]> ARRAY = Bindable.of(ConfigDataLocation[].class);
private static final Bindable<ValueObject> VALUE_OBJECT = Bindable.of(ValueObject.class);
private final ConfigDataLocationBindHandler handler = new ConfigDataLocationBindHandler();
@Test
void bindToArrayFromCommaStringPropertySetsOrigin() {
MapConfigurationPropertySource source = new MapConfigurationPropertySource();
source.put("locations", "a,b,c");
Binder binder = new Binder(source);
ConfigDataLocation[] bound = binder.bind("locations", ARRAY, this.handler).get();
String expectedLocation = "\"locations\" from property source \"source\"";
assertThat(bound[0]).hasToString("a");
assertThat(bound[0].getOrigin()).hasToString(expectedLocation);
assertThat(bound[1]).hasToString("b");
assertThat(bound[1].getOrigin()).hasToString(expectedLocation);
assertThat(bound[2]).hasToString("c");
assertThat(bound[2].getOrigin()).hasToString(expectedLocation);
}
@Test
void bindToArrayFromIndexedPropertiesSetsOrigin() {
MapConfigurationPropertySource source = new MapConfigurationPropertySource();
source.put("locations[0]", "a");
source.put("locations[1]", "b");
source.put("locations[2]", "c");
Binder binder = new Binder(source);
ConfigDataLocation[] bound = binder.bind("locations", ARRAY, this.handler).get();
assertThat(bound[0]).hasToString("a");
assertThat(bound[0].getOrigin()).hasToString("\"locations[0]\" from property source \"source\"");
assertThat(bound[1]).hasToString("b");
assertThat(bound[1].getOrigin()).hasToString("\"locations[1]\" from property source \"source\"");
assertThat(bound[2]).hasToString("c");
assertThat(bound[2].getOrigin()).hasToString("\"locations[2]\" from property source \"source\"");
}
@Test
void bindToValueObjectFromCommaStringPropertySetsOrigin() {
MapConfigurationPropertySource source = new MapConfigurationPropertySource();
source.put("test.locations", "a,b,c");
Binder binder = new Binder(source);
ValueObject bound = binder.bind("test", VALUE_OBJECT, this.handler).get();
String expectedLocation = "\"test.locations\" from property source \"source\"";
assertThat(bound.getLocation(0)).hasToString("a");
assertThat(bound.getLocation(0).getOrigin()).hasToString(expectedLocation);
assertThat(bound.getLocation(1)).hasToString("b");
assertThat(bound.getLocation(1).getOrigin()).hasToString(expectedLocation);
assertThat(bound.getLocation(2)).hasToString("c");
assertThat(bound.getLocation(2).getOrigin()).hasToString(expectedLocation);
}
@Test
void bindToValueObjectFromIndexedPropertiesSetsOrigin() {
MapConfigurationPropertySource source = new MapConfigurationPropertySource();
source.put("test.locations[0]", "a");
source.put("test.locations[1]", "b");
source.put("test.locations[2]", "c");
Binder binder = new Binder(source);
ValueObject bound = binder.bind("test", VALUE_OBJECT, this.handler).get();
assertThat(bound.getLocation(0)).hasToString("a");
assertThat(bound.getLocation(0).getOrigin())
.hasToString("\"test.locations[0]\" from property source \"source\"");
assertThat(bound.getLocation(1)).hasToString("b");
assertThat(bound.getLocation(1).getOrigin())
.hasToString("\"test.locations[1]\" from property source \"source\"");
assertThat(bound.getLocation(2)).hasToString("c");
assertThat(bound.getLocation(2).getOrigin())
.hasToString("\"test.locations[2]\" from property source \"source\"");
}
static class ValueObject {
private final List<ConfigDataLocation> locations;
ValueObject(List<ConfigDataLocation> locations) {
this.locations = locations;
}
ConfigDataLocation getLocation(int index) {
return this.locations.get(index);
}
}
}
...@@ -16,19 +16,12 @@ ...@@ -16,19 +16,12 @@
package org.springframework.boot.context.config; package org.springframework.boot.context.config;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.core.io.FileSystemResource; import org.springframework.boot.origin.Origin;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
/** /**
...@@ -38,98 +31,37 @@ import static org.mockito.Mockito.mock; ...@@ -38,98 +31,37 @@ import static org.mockito.Mockito.mock;
*/ */
class ConfigDataLocationNotFoundExceptionTests { class ConfigDataLocationNotFoundExceptionTests {
private ConfigDataLocation location = mock(ConfigDataLocation.class); private Origin origin = mock(Origin.class);
private Throwable cause = new RuntimeException();
private String message = "message";
private File exists;
private File missing; private final ConfigDataLocation location = ConfigDataLocation.of("optional:test").withOrigin(this.origin);
@TempDir private final ConfigDataLocationNotFoundException exception = new ConfigDataLocationNotFoundException(
File temp; this.location);
@BeforeEach
void setup() throws IOException {
this.exists = new File(this.temp, "exists");
this.missing = new File(this.temp, "missing");
try (OutputStream out = new FileOutputStream(this.exists)) {
out.write("test".getBytes());
}
}
@Test @Test
void createWithLocationCreatesInstance() { void createWhenLocationIsNullThrowsException() {
ConfigDataLocationNotFoundException exception = new ConfigDataLocationNotFoundException(this.location); assertThatIllegalArgumentException().isThrownBy(() -> new ConfigDataLocationNotFoundException(null))
assertThat(exception.getLocation()).isSameAs(this.location); .withMessage("Location must not be null");
}
@Test
void createWithLocationAndCauseCreatesInstance() {
ConfigDataLocationNotFoundException exception = new ConfigDataLocationNotFoundException(this.location,
this.cause);
assertThat(exception.getLocation()).isSameAs(this.location);
assertThat(exception.getCause()).isSameAs(this.cause);
}
@Test
void createWithMessageAndLocationCreatesInstance() {
ConfigDataLocationNotFoundException exception = new ConfigDataLocationNotFoundException(this.message,
this.location, this.cause);
assertThat(exception.getLocation()).isSameAs(this.location);
assertThat(exception.getCause()).isSameAs(this.cause);
assertThat(exception.getMessage()).isEqualTo(this.message);
}
@Test
void createWithMessageAndLocationAndCauseCreatesInstance() {
ConfigDataLocationNotFoundException exception = new ConfigDataLocationNotFoundException(this.message,
this.location);
assertThat(exception.getLocation()).isSameAs(this.location);
assertThat(exception.getMessage()).isEqualTo(this.message);
} }
@Test @Test
void getLocationReturnsLocation() { void getLocationReturnsLocation() {
ConfigDataLocationNotFoundException exception = new ConfigDataLocationNotFoundException(this.location); assertThat(this.exception.getLocation()).isSameAs(this.location);
assertThat(exception.getLocation()).isSameAs(this.location);
}
@Test
void throwIfDoesNotExistWhenPathExistsDoesNothing() {
ConfigDataLocationNotFoundException.throwIfDoesNotExist(this.location, this.exists.toPath());
}
@Test
void throwIfDoesNotExistWhenPathDoesNotExistThrowsException() {
assertThatExceptionOfType(ConfigDataLocationNotFoundException.class).isThrownBy(
() -> ConfigDataLocationNotFoundException.throwIfDoesNotExist(this.location, this.missing.toPath()));
}
@Test
void throwIfDoesNotExistWhenFileExistsDoesNothing() {
ConfigDataLocationNotFoundException.throwIfDoesNotExist(this.location, this.exists);
} }
@Test @Test
void throwIfDoesNotExistWhenFileDoesNotExistThrowsException() { void getOriginReturnsLocationOrigin() {
assertThatExceptionOfType(ConfigDataLocationNotFoundException.class) assertThat(this.exception.getOrigin()).isSameAs(this.origin);
.isThrownBy(() -> ConfigDataLocationNotFoundException.throwIfDoesNotExist(this.location, this.missing));
} }
@Test @Test
void throwIfDoesNotExistWhenResourceExistsDoesNothing() { void getReferenceDescriptionReturnsLocationString() {
ConfigDataLocationNotFoundException.throwIfDoesNotExist(this.location, new FileSystemResource(this.exists)); assertThat(this.exception.getReferenceDescription()).isEqualTo("location 'optional:test'");
} }
@Test @Test
void throwIfDoesNotExistWhenResourceDoesNotExistThrowsException() { void getMessageReturnsMessage() {
assertThatExceptionOfType(ConfigDataLocationNotFoundException.class) assertThat(this.exception).hasMessage("Config data location 'optional:test' cannot be found");
.isThrownBy(() -> ConfigDataLocationNotFoundException.throwIfDoesNotExist(this.location,
new FileSystemResource(this.missing)));
} }
} }
...@@ -37,19 +37,19 @@ class ConfigDataLocationResolverTests { ...@@ -37,19 +37,19 @@ class ConfigDataLocationResolverTests {
@Test @Test
void resolveProfileSpecificReturnsEmptyList() { void resolveProfileSpecificReturnsEmptyList() {
assertThat(this.resolver.resolveProfileSpecific(this.context, null, true, null)).isEmpty(); assertThat(this.resolver.resolveProfileSpecific(this.context, null, null)).isEmpty();
} }
static class TestConfigDataLocationResolver implements ConfigDataLocationResolver<ConfigDataLocation> { static class TestConfigDataLocationResolver implements ConfigDataLocationResolver<ConfigDataResource> {
@Override @Override
public boolean isResolvable(ConfigDataLocationResolverContext context, String location) { public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location) {
return true; return true;
} }
@Override @Override
public List<ConfigDataLocation> resolve(ConfigDataLocationResolverContext context, String location, public List<ConfigDataResource> resolve(ConfigDataLocationResolverContext context,
boolean optional) { ConfigDataLocation location) {
return null; return null;
} }
......
...@@ -71,8 +71,7 @@ class ConfigDataLocationResolversTests { ...@@ -71,8 +71,7 @@ class ConfigDataLocationResolversTests {
@Test @Test
void createWhenInjectingBinderCreatesResolver() { void createWhenInjectingBinderCreatesResolver() {
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext, ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext,
ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader, this.binder, this.resourceLoader, Collections.singletonList(TestBoundResolver.class.getName()));
Collections.singletonList(TestBoundResolver.class.getName()));
assertThat(resolvers.getResolvers()).hasSize(1); assertThat(resolvers.getResolvers()).hasSize(1);
assertThat(resolvers.getResolvers().get(0)).isExactlyInstanceOf(TestBoundResolver.class); assertThat(resolvers.getResolvers().get(0)).isExactlyInstanceOf(TestBoundResolver.class);
assertThat(((TestBoundResolver) resolvers.getResolvers().get(0)).getBinder()).isSameAs(this.binder); assertThat(((TestBoundResolver) resolvers.getResolvers().get(0)).getBinder()).isSameAs(this.binder);
...@@ -81,25 +80,23 @@ class ConfigDataLocationResolversTests { ...@@ -81,25 +80,23 @@ class ConfigDataLocationResolversTests {
@Test @Test
void createWhenNotInjectingBinderCreatesResolver() { void createWhenNotInjectingBinderCreatesResolver() {
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext, ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext,
ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader, this.binder, this.resourceLoader, Collections.singletonList(TestResolver.class.getName()));
Collections.singletonList(TestResolver.class.getName()));
assertThat(resolvers.getResolvers()).hasSize(1); assertThat(resolvers.getResolvers()).hasSize(1);
assertThat(resolvers.getResolvers().get(0)).isExactlyInstanceOf(TestResolver.class); assertThat(resolvers.getResolvers().get(0)).isExactlyInstanceOf(TestResolver.class);
} }
@Test @Test
void createWhenResolverHasBootstrapParametersInjectsBootstrapContext() { void createWhenResolverHasBootstrapParametersInjectsBootstrapContext() {
new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext, ConfigDataLocationNotFoundAction.FAIL, new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext, this.binder, this.resourceLoader,
this.binder, this.resourceLoader, Collections.singletonList(TestBootstrappingResolver.class.getName())); Collections.singletonList(TestBootstrappingResolver.class.getName()));
assertThat(this.bootstrapContext.get(String.class)).isEqualTo("boot"); assertThat(this.bootstrapContext.get(String.class)).isEqualTo("boot");
} }
@Test @Test
void createWhenNameIsNotConfigDataLocationResolverThrowsException() { void createWhenNameIsNotConfigDataLocationResolverThrowsException() {
assertThatIllegalArgumentException() assertThatIllegalArgumentException()
.isThrownBy(() -> new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext, .isThrownBy(() -> new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext, this.binder,
ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader, this.resourceLoader, Collections.singletonList(InputStream.class.getName())))
Collections.singletonList(InputStream.class.getName())))
.withMessageContaining("Unable to instantiate").havingCause().withMessageContaining("not assignable"); .withMessageContaining("Unable to instantiate").havingCause().withMessageContaining("not assignable");
} }
...@@ -110,73 +107,75 @@ class ConfigDataLocationResolversTests { ...@@ -110,73 +107,75 @@ class ConfigDataLocationResolversTests {
names.add(LowestTestResolver.class.getName()); names.add(LowestTestResolver.class.getName());
names.add(HighestTestResolver.class.getName()); names.add(HighestTestResolver.class.getName());
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext, ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext,
ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader, names); this.binder, this.resourceLoader, names);
assertThat(resolvers.getResolvers().get(0)).isExactlyInstanceOf(HighestTestResolver.class); assertThat(resolvers.getResolvers().get(0)).isExactlyInstanceOf(HighestTestResolver.class);
assertThat(resolvers.getResolvers().get(1)).isExactlyInstanceOf(TestResolver.class); assertThat(resolvers.getResolvers().get(1)).isExactlyInstanceOf(TestResolver.class);
assertThat(resolvers.getResolvers().get(2)).isExactlyInstanceOf(LowestTestResolver.class); assertThat(resolvers.getResolvers().get(2)).isExactlyInstanceOf(LowestTestResolver.class);
} }
@Test @Test
void resolveAllResolvesUsingFirstSupportedResolver() { void resolveResolvesUsingFirstSupportedResolver() {
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext, ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext,
ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader, this.binder, this.resourceLoader,
Arrays.asList(LowestTestResolver.class.getName(), HighestTestResolver.class.getName())); Arrays.asList(LowestTestResolver.class.getName(), HighestTestResolver.class.getName()));
List<ConfigDataLocation> resolved = resolvers.resolveAll(this.context, ConfigDataLocation location = ConfigDataLocation.of("LowestTestResolver:test");
Collections.singletonList("LowestTestResolver:test"), null); List<ConfigDataResolutionResult> resolved = resolvers.resolve(this.context, location, null);
assertThat(resolved).hasSize(1); assertThat(resolved).hasSize(1);
TestConfigDataLocation location = (TestConfigDataLocation) resolved.get(0); TestConfigDataResource resource = (TestConfigDataResource) resolved.get(0).getResource();
assertThat(location.getResolver()).isInstanceOf(LowestTestResolver.class); assertThat(resource.getResolver()).isInstanceOf(LowestTestResolver.class);
assertThat(location.getLocation()).isEqualTo("LowestTestResolver:test"); assertThat(resource.getLocation()).isEqualTo(location);
assertThat(location.isProfileSpecific()).isFalse(); assertThat(resource.isProfileSpecific()).isFalse();
} }
@Test @Test
void resolveAllWhenProfileMergesResolvedLocations() { void resolveWhenProfileMergesResolvedLocations() {
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext, ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext,
ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader, this.binder, this.resourceLoader,
Arrays.asList(LowestTestResolver.class.getName(), HighestTestResolver.class.getName())); Arrays.asList(LowestTestResolver.class.getName(), HighestTestResolver.class.getName()));
List<ConfigDataLocation> resolved = resolvers.resolveAll(this.context, ConfigDataLocation location = ConfigDataLocation.of("LowestTestResolver:test");
Collections.singletonList("LowestTestResolver:test"), this.profiles); List<ConfigDataResolutionResult> resolved = resolvers.resolve(this.context, location, this.profiles);
assertThat(resolved).hasSize(2); assertThat(resolved).hasSize(2);
TestConfigDataLocation location = (TestConfigDataLocation) resolved.get(0); TestConfigDataResource resource = (TestConfigDataResource) resolved.get(0).getResource();
assertThat(location.getResolver()).isInstanceOf(LowestTestResolver.class); assertThat(resource.getResolver()).isInstanceOf(LowestTestResolver.class);
assertThat(location.getLocation()).isEqualTo("LowestTestResolver:test"); assertThat(resource.getLocation()).isEqualTo(location);
assertThat(location.isProfileSpecific()).isFalse(); assertThat(resource.isProfileSpecific()).isFalse();
TestConfigDataLocation profileLocation = (TestConfigDataLocation) resolved.get(1); TestConfigDataResource profileResource = (TestConfigDataResource) resolved.get(1).getResource();
assertThat(profileLocation.getResolver()).isInstanceOf(LowestTestResolver.class); assertThat(profileResource.getResolver()).isInstanceOf(LowestTestResolver.class);
assertThat(profileLocation.getLocation()).isEqualTo("LowestTestResolver:test"); assertThat(profileResource.getLocation()).isEqualTo(location);
assertThat(profileLocation.isProfileSpecific()).isTrue(); assertThat(profileResource.isProfileSpecific()).isTrue();
} }
@Test @Test
void resolveWhenNoResolverThrowsException() { void resolveWhenNoResolverThrowsException() {
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext, ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext,
ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader, this.binder, this.resourceLoader,
Arrays.asList(LowestTestResolver.class.getName(), HighestTestResolver.class.getName())); Arrays.asList(LowestTestResolver.class.getName(), HighestTestResolver.class.getName()));
ConfigDataLocation location = ConfigDataLocation.of("Missing:test");
assertThatExceptionOfType(UnsupportedConfigDataLocationException.class) assertThatExceptionOfType(UnsupportedConfigDataLocationException.class)
.isThrownBy(() -> resolvers.resolveAll(this.context, Collections.singletonList("Missing:test"), null)) .isThrownBy(() -> resolvers.resolve(this.context, location, null))
.satisfies((ex) -> assertThat(ex.getLocation()).isEqualTo("Missing:test")); .satisfies((ex) -> assertThat(ex.getLocation()).isEqualTo(location));
} }
static class TestResolver implements ConfigDataLocationResolver<TestConfigDataLocation> { static class TestResolver implements ConfigDataLocationResolver<TestConfigDataResource> {
@Override @Override
public boolean isResolvable(ConfigDataLocationResolverContext context, String location) { public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location) {
String name = getClass().getName(); String name = getClass().getName();
name = name.substring(name.lastIndexOf("$") + 1); name = name.substring(name.lastIndexOf("$") + 1);
return location.startsWith(name + ":"); return location.hasPrefix(name + ":");
} }
@Override @Override
public List<TestConfigDataLocation> resolve(ConfigDataLocationResolverContext context, String location, public List<TestConfigDataResource> resolve(ConfigDataLocationResolverContext context,
boolean optional) { ConfigDataLocation location)
return Collections.singletonList(new TestConfigDataLocation(this, location, false)); throws ConfigDataLocationNotFoundException, ConfigDataResourceNotFoundException {
return Collections.singletonList(new TestConfigDataResource(this, location, false));
} }
@Override @Override
public List<TestConfigDataLocation> resolveProfileSpecific(ConfigDataLocationResolverContext context, public List<TestConfigDataResource> resolveProfileSpecific(ConfigDataLocationResolverContext context,
String location, boolean optional, Profiles profiles) { ConfigDataLocation location, Profiles profiles) throws ConfigDataLocationNotFoundException {
return Collections.singletonList(new TestConfigDataLocation(this, location, true)); return Collections.singletonList(new TestConfigDataResource(this, location, true));
} }
} }
...@@ -218,15 +217,15 @@ class ConfigDataLocationResolversTests { ...@@ -218,15 +217,15 @@ class ConfigDataLocationResolversTests {
} }
static class TestConfigDataLocation extends ConfigDataLocation { static class TestConfigDataResource extends ConfigDataResource {
private final TestResolver resolver; private final TestResolver resolver;
private final String location; private final ConfigDataLocation location;
private final boolean profileSpecific; private final boolean profileSpecific;
TestConfigDataLocation(TestResolver resolver, String location, boolean profileSpecific) { TestConfigDataResource(TestResolver resolver, ConfigDataLocation location, boolean profileSpecific) {
this.resolver = resolver; this.resolver = resolver;
this.location = location; this.location = location;
this.profileSpecific = profileSpecific; this.profileSpecific = profileSpecific;
...@@ -236,7 +235,7 @@ class ConfigDataLocationResolversTests { ...@@ -236,7 +235,7 @@ class ConfigDataLocationResolversTests {
return this.resolver; return this.resolver;
} }
String getLocation() { ConfigDataLocation getLocation() {
return this.location; return this.location;
} }
......
/*
* 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 org.junit.jupiter.api.Test;
import org.springframework.boot.origin.Origin;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link ConfigDataLocation}.
*
* @author Phillip Webb
*/
class ConfigDataLocationTests {
@Test
void isOptionalWhenNotPrefixedWithOptionalReturnsFalse() {
ConfigDataLocation location = ConfigDataLocation.of("test");
assertThat(location.isOptional()).isFalse();
}
@Test
void isOptionalWhenPrefixedWithOptionalReturnsTrue() {
ConfigDataLocation location = ConfigDataLocation.of("optional:test");
assertThat(location.isOptional()).isTrue();
}
@Test
void getValueWhenNotPrefixedWithOptionalReturnsValue() {
ConfigDataLocation location = ConfigDataLocation.of("test");
assertThat(location.getValue()).isEqualTo("test");
}
@Test
void getValueWhenPrefixedWithOptionalReturnsValueWithoutPrefix() {
ConfigDataLocation location = ConfigDataLocation.of("optional:test");
assertThat(location.getValue()).isEqualTo("test");
}
@Test
void hasPrefixWhenPrefixedReturnsTrue() {
ConfigDataLocation location = ConfigDataLocation.of("optional:test:path");
assertThat(location.hasPrefix("test:")).isTrue();
}
@Test
void hasPrefixWhenNotPrefixedReturnsFalse() {
ConfigDataLocation location = ConfigDataLocation.of("optional:file:path");
assertThat(location.hasPrefix("test:")).isFalse();
}
@Test
void getNonPrefixedValueWhenPrefixedReturnsNonPrefixed() {
ConfigDataLocation location = ConfigDataLocation.of("optional:test:path");
assertThat(location.getNonPrefixedValue("test:")).isEqualTo("path");
}
@Test
void getNonPrefixedValueWhenNotPrefixedReturnsOriginalValue() {
ConfigDataLocation location = ConfigDataLocation.of("optional:file:path");
assertThat(location.getNonPrefixedValue("test:")).isEqualTo("file:path");
}
@Test
void getOriginWhenNoOriginReturnsNull() {
ConfigDataLocation location = ConfigDataLocation.of("test");
assertThat(location.getOrigin()).isNull();
}
@Test
void getOriginWhenWithOriginReturnsOrigin() {
Origin origin = mock(Origin.class);
ConfigDataLocation location = ConfigDataLocation.of("test").withOrigin(origin);
assertThat(location.getOrigin()).isSameAs(origin);
}
@Test
void equalsAndHashCode() {
ConfigDataLocation l1 = ConfigDataLocation.of("a");
ConfigDataLocation l2 = ConfigDataLocation.of("a");
ConfigDataLocation l3 = ConfigDataLocation.of("optional:a");
ConfigDataLocation l4 = ConfigDataLocation.of("b");
assertThat(l1.hashCode()).isEqualTo(l2.hashCode()).isEqualTo(l3.hashCode());
assertThat(l1).isEqualTo(l2).isEqualTo(l3).isNotEqualTo(l4);
}
@Test
void toStringReturnsOriginalString() {
ConfigDataLocation location = ConfigDataLocation.of("optional:test");
assertThat(location).hasToString("optional:test");
}
@Test
void withOriginSetsOrigin() {
Origin origin = mock(Origin.class);
ConfigDataLocation location = ConfigDataLocation.of("test").withOrigin(origin);
assertThat(location.getOrigin()).isSameAs(origin);
}
@Test
void ofWhenNullValueReturnsNull() {
assertThat(ConfigDataLocation.of(null)).isNull();
}
@Test
void ofWhenEmptyValueReturnsNull() {
assertThat(ConfigDataLocation.of("")).isNull();
}
@Test
void ofWhenEmptyOptionalValueReturnsNull() {
assertThat(ConfigDataLocation.of("optional:")).isNull();
}
@Test
void ofReturnsLocation() {
assertThat(ConfigDataLocation.of("test")).hasToString("test");
}
}
...@@ -43,13 +43,16 @@ class ConfigDataPropertiesTests { ...@@ -43,13 +43,16 @@ class ConfigDataPropertiesTests {
private static final Profiles NULL_PROFILES = null; private static final Profiles NULL_PROFILES = null;
private static final List<String> NO_IMPORTS = Collections.emptyList(); private static final List<ConfigDataLocation> NO_IMPORTS = Collections.emptyList();
@Test @Test
void getImportsReturnsImports() { void getImportsReturnsImports() {
List<String> imports = Arrays.asList("one", "two", "three"); ConfigDataLocation l1 = ConfigDataLocation.of("one");
ConfigDataLocation l2 = ConfigDataLocation.of("two");
ConfigDataLocation l3 = ConfigDataLocation.of("three");
List<ConfigDataLocation> imports = Arrays.asList(l1, l2, l3);
ConfigDataProperties properties = new ConfigDataProperties(imports, null); ConfigDataProperties properties = new ConfigDataProperties(imports, null);
assertThat(properties.getImports()).containsExactly("one", "two", "three"); assertThat(properties.getImports()).containsExactly(l1, l2, l3);
} }
@Test @Test
...@@ -164,7 +167,8 @@ class ConfigDataPropertiesTests { ...@@ -164,7 +167,8 @@ class ConfigDataPropertiesTests {
ConfigDataProperties properties = ConfigDataProperties.get(binder); ConfigDataProperties properties = ConfigDataProperties.get(binder);
ConfigDataActivationContext context = new ConfigDataActivationContext(CloudPlatform.KUBERNETES, ConfigDataActivationContext context = new ConfigDataActivationContext(CloudPlatform.KUBERNETES,
createTestProfiles()); createTestProfiles());
assertThat(properties.getImports()).containsExactly("one", "two", "three"); assertThat(properties.getImports()).containsExactly(ConfigDataLocation.of("one"), ConfigDataLocation.of("two"),
ConfigDataLocation.of("three"));
assertThat(properties.isActive(context)).isTrue(); assertThat(properties.isActive(context)).isTrue();
} }
...@@ -195,7 +199,7 @@ class ConfigDataPropertiesTests { ...@@ -195,7 +199,7 @@ class ConfigDataPropertiesTests {
source.put("spring.config.import", "one,two,three"); source.put("spring.config.import", "one,two,three");
Binder binder = new Binder(source); Binder binder = new Binder(source);
ConfigDataProperties properties = ConfigDataProperties.get(binder); ConfigDataProperties properties = ConfigDataProperties.get(binder);
assertThat(properties.getImportOrigin("two")) assertThat(properties.getImports().get(1).getOrigin())
.hasToString("\"spring.config.import\" from property source \"source\""); .hasToString("\"spring.config.import\" from property source \"source\"");
} }
...@@ -207,7 +211,7 @@ class ConfigDataPropertiesTests { ...@@ -207,7 +211,7 @@ class ConfigDataPropertiesTests {
source.put("spring.config.import[2]", "three"); source.put("spring.config.import[2]", "three");
Binder binder = new Binder(source); Binder binder = new Binder(source);
ConfigDataProperties properties = ConfigDataProperties.get(binder); ConfigDataProperties properties = ConfigDataProperties.get(binder);
assertThat(properties.getImportOrigin("two")) assertThat(properties.getImports().get(1).getOrigin())
.hasToString("\"spring.config.import[1]\" from property source \"source\""); .hasToString("\"spring.config.import[1]\" from property source \"source\"");
} }
......
/*
* 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.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.springframework.core.io.FileSystemResource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link ConfigDataResourceNotFoundException}.
*
* @author Phillip Webb
*/
class ConfigDataResourceNotFoundExceptionTests {
private ConfigDataResource resource = new TestConfigDataResource();
private ConfigDataLocation location = ConfigDataLocation.of("optional:test");
private Throwable cause = new RuntimeException();
private File exists;
private File missing;
@TempDir
File temp;
@BeforeEach
void setup() throws IOException {
this.exists = new File(this.temp, "exists");
this.missing = new File(this.temp, "missing");
try (OutputStream out = new FileOutputStream(this.exists)) {
out.write("test".getBytes());
}
}
@Test
void createWhenResourceIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> new ConfigDataResourceNotFoundException(null))
.withMessage("Resource must not be null");
}
@Test
void createWithResourceCreatesInstance() {
ConfigDataResourceNotFoundException exception = new ConfigDataResourceNotFoundException(this.resource);
assertThat(exception.getResource()).isSameAs(this.resource);
}
@Test
void createWithResourceAndCauseCreatesInstance() {
ConfigDataResourceNotFoundException exception = new ConfigDataResourceNotFoundException(this.resource,
this.cause);
assertThat(exception.getResource()).isSameAs(this.resource);
assertThat(exception.getCause()).isSameAs(this.cause);
}
@Test
void getResourceReturnsResource() {
ConfigDataResourceNotFoundException exception = new ConfigDataResourceNotFoundException(this.resource);
assertThat(exception.getResource()).isSameAs(this.resource);
}
@Test
void getLocationWhenHasNoLocationReturnsNull() {
ConfigDataResourceNotFoundException exception = new ConfigDataResourceNotFoundException(this.resource);
assertThat(exception.getLocation()).isNull();
}
@Test
void getLocationWhenHasLocationReturnsLocation() {
ConfigDataResourceNotFoundException exception = new ConfigDataResourceNotFoundException(this.resource)
.withLocation(this.location);
assertThat(exception.getLocation()).isSameAs(this.location);
}
@Test
void getReferenceDescriptionWhenHasNoLocationReturnsDescription() {
ConfigDataResourceNotFoundException exception = new ConfigDataResourceNotFoundException(this.resource);
assertThat(exception.getReferenceDescription()).isEqualTo("resource 'mytestresource'");
}
@Test
void getReferenceDescriptionWhenHasLocationReturnsDescription() {
ConfigDataResourceNotFoundException exception = new ConfigDataResourceNotFoundException(this.resource)
.withLocation(this.location);
assertThat(exception.getReferenceDescription())
.isEqualTo("resource 'mytestresource' via location 'optional:test'");
}
@Test
void withLocationReturnsNewInstanceWithLocation() {
ConfigDataResourceNotFoundException exception = new ConfigDataResourceNotFoundException(this.resource)
.withLocation(this.location);
assertThat(exception.getLocation()).isSameAs(this.location);
}
@Test
void throwIfDoesNotExistWhenPathExistsDoesNothing() {
ConfigDataResourceNotFoundException.throwIfDoesNotExist(this.resource, this.exists.toPath());
}
@Test
void throwIfDoesNotExistWhenPathDoesNotExistThrowsException() {
assertThatExceptionOfType(ConfigDataResourceNotFoundException.class).isThrownBy(
() -> ConfigDataResourceNotFoundException.throwIfDoesNotExist(this.resource, this.missing.toPath()));
}
@Test
void throwIfDoesNotExistWhenFileExistsDoesNothing() {
ConfigDataResourceNotFoundException.throwIfDoesNotExist(this.resource, this.exists);
}
@Test
void throwIfDoesNotExistWhenFileDoesNotExistThrowsException() {
assertThatExceptionOfType(ConfigDataResourceNotFoundException.class)
.isThrownBy(() -> ConfigDataResourceNotFoundException.throwIfDoesNotExist(this.resource, this.missing));
}
@Test
void throwIfDoesNotExistWhenResourceExistsDoesNothing() {
ConfigDataResourceNotFoundException.throwIfDoesNotExist(this.resource, new FileSystemResource(this.exists));
}
@Test
void throwIfDoesNotExistWhenResourceDoesNotExistThrowsException() {
assertThatExceptionOfType(ConfigDataResourceNotFoundException.class)
.isThrownBy(() -> ConfigDataResourceNotFoundException.throwIfDoesNotExist(this.resource,
new FileSystemResource(this.missing)));
}
static class TestConfigDataResource extends ConfigDataResource {
@Override
public String toString() {
return "mytestresource";
}
}
}
...@@ -51,7 +51,7 @@ public class ConfigTreeConfigDataLoaderTests { ...@@ -51,7 +51,7 @@ public class ConfigTreeConfigDataLoaderTests {
File file = this.directory.resolve("hello").toFile(); File file = this.directory.resolve("hello").toFile();
file.getParentFile().mkdirs(); file.getParentFile().mkdirs();
FileCopyUtils.copy("world".getBytes(StandardCharsets.UTF_8), file); FileCopyUtils.copy("world".getBytes(StandardCharsets.UTF_8), file);
ConfigTreeConfigDataLocation location = new ConfigTreeConfigDataLocation(this.directory.toString()); ConfigTreeConfigDataResource location = new ConfigTreeConfigDataResource(this.directory.toString());
ConfigData configData = this.loader.load(this.loaderContext, location); ConfigData configData = this.loader.load(this.loaderContext, location);
assertThat(configData.getPropertySources().size()).isEqualTo(1); assertThat(configData.getPropertySources().size()).isEqualTo(1);
PropertySource<?> source = configData.getPropertySources().get(0); PropertySource<?> source = configData.getPropertySources().get(0);
...@@ -62,8 +62,8 @@ public class ConfigTreeConfigDataLoaderTests { ...@@ -62,8 +62,8 @@ public class ConfigTreeConfigDataLoaderTests {
@Test @Test
void loadWhenPathDoesNotExistThrowsException() { void loadWhenPathDoesNotExistThrowsException() {
File missing = this.directory.resolve("missing").toFile(); File missing = this.directory.resolve("missing").toFile();
ConfigTreeConfigDataLocation location = new ConfigTreeConfigDataLocation(missing.toString()); ConfigTreeConfigDataResource location = new ConfigTreeConfigDataResource(missing.toString());
assertThatExceptionOfType(ConfigDataLocationNotFoundException.class) assertThatExceptionOfType(ConfigDataResourceNotFoundException.class)
.isThrownBy(() -> this.loader.load(this.loaderContext, location)); .isThrownBy(() -> this.loader.load(this.loaderContext, location));
} }
......
...@@ -38,19 +38,19 @@ class ConfigTreeConfigDataLocationResolverTests { ...@@ -38,19 +38,19 @@ class ConfigTreeConfigDataLocationResolverTests {
@Test @Test
void isResolvableWhenPrefixMatchesReturnsTrue() { void isResolvableWhenPrefixMatchesReturnsTrue() {
assertThat(this.resolver.isResolvable(this.context, "configtree:/etc/config")).isTrue(); assertThat(this.resolver.isResolvable(this.context, ConfigDataLocation.of("configtree:/etc/config"))).isTrue();
} }
@Test @Test
void isResolvableWhenPrefixDoesNotMatchReturnsFalse() { void isResolvableWhenPrefixDoesNotMatchReturnsFalse() {
assertThat(this.resolver.isResolvable(this.context, "http://etc/config")).isFalse(); assertThat(this.resolver.isResolvable(this.context, ConfigDataLocation.of("http://etc/config"))).isFalse();
assertThat(this.resolver.isResolvable(this.context, "/etc/config")).isFalse(); assertThat(this.resolver.isResolvable(this.context, ConfigDataLocation.of("/etc/config"))).isFalse();
} }
@Test @Test
void resolveReturnsConfigVolumeMountLocation() { void resolveReturnsConfigVolumeMountLocation() {
List<ConfigTreeConfigDataLocation> locations = this.resolver.resolve(this.context, "configtree:/etc/config", List<ConfigTreeConfigDataResource> locations = this.resolver.resolve(this.context,
false); ConfigDataLocation.of("configtree:/etc/config"));
assertThat(locations.size()).isEqualTo(1); assertThat(locations.size()).isEqualTo(1);
assertThat(locations).extracting(Object::toString) assertThat(locations).extracting(Object::toString)
.containsExactly("config tree [" + new File("/etc/config").getAbsolutePath() + "]"); .containsExactly("config tree [" + new File("/etc/config").getAbsolutePath() + "]");
......
...@@ -24,36 +24,36 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -24,36 +24,36 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/** /**
* Tests for {@link ConfigTreeConfigDataLocation}. * Tests for {@link ConfigTreeConfigDataResource}.
* *
* @author Madhura Bhave * @author Madhura Bhave
* @author Phillip Webb * @author Phillip Webb
*/ */
public class ConfigTreeConfigDataLocationTests { public class ConfigTreeConfigDataResourceTests {
@Test @Test
void constructorWhenPathIsNullThrowsException() { void constructorWhenPathIsNullThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> new ConfigTreeConfigDataLocation(null)) assertThatIllegalArgumentException().isThrownBy(() -> new ConfigTreeConfigDataResource(null))
.withMessage("Path must not be null"); .withMessage("Path must not be null");
} }
@Test @Test
void equalsWhenPathIsTheSameReturnsTrue() { void equalsWhenPathIsTheSameReturnsTrue() {
ConfigTreeConfigDataLocation location = new ConfigTreeConfigDataLocation("/etc/config"); ConfigTreeConfigDataResource location = new ConfigTreeConfigDataResource("/etc/config");
ConfigTreeConfigDataLocation other = new ConfigTreeConfigDataLocation("/etc/config"); ConfigTreeConfigDataResource other = new ConfigTreeConfigDataResource("/etc/config");
assertThat(location).isEqualTo(other); assertThat(location).isEqualTo(other);
} }
@Test @Test
void equalsWhenPathIsDifferentReturnsFalse() { void equalsWhenPathIsDifferentReturnsFalse() {
ConfigTreeConfigDataLocation location = new ConfigTreeConfigDataLocation("/etc/config"); ConfigTreeConfigDataResource location = new ConfigTreeConfigDataResource("/etc/config");
ConfigTreeConfigDataLocation other = new ConfigTreeConfigDataLocation("other-location"); ConfigTreeConfigDataResource other = new ConfigTreeConfigDataResource("other-location");
assertThat(location).isNotEqualTo(other); assertThat(location).isNotEqualTo(other);
} }
@Test @Test
void toStringReturnsDescriptiveString() { void toStringReturnsDescriptiveString() {
ConfigTreeConfigDataLocation location = new ConfigTreeConfigDataLocation("/etc/config"); ConfigTreeConfigDataResource location = new ConfigTreeConfigDataResource("/etc/config");
assertThat(location.toString()).isEqualTo("config tree [" + new File("/etc/config").getAbsolutePath() + "]"); assertThat(location.toString()).isEqualTo("config tree [" + new File("/etc/config").getAbsolutePath() + "]");
} }
......
...@@ -40,7 +40,7 @@ class InactiveConfigDataAccessExceptionTests { ...@@ -40,7 +40,7 @@ class InactiveConfigDataAccessExceptionTests {
private MockPropertySource propertySource = new MockPropertySource(); private MockPropertySource propertySource = new MockPropertySource();
private ConfigDataLocation location = new TestConfigDataLocation(); private ConfigDataResource resource = new TestConfigDataResource();
private String propertyName = "spring"; private String propertyName = "spring";
...@@ -49,7 +49,7 @@ class InactiveConfigDataAccessExceptionTests { ...@@ -49,7 +49,7 @@ class InactiveConfigDataAccessExceptionTests {
@Test @Test
void createHasCorrectMessage() { void createHasCorrectMessage() {
InactiveConfigDataAccessException exception = new InactiveConfigDataAccessException(this.propertySource, InactiveConfigDataAccessException exception = new InactiveConfigDataAccessException(this.propertySource,
this.location, this.propertyName, this.origin); this.resource, this.propertyName, this.origin);
assertThat(exception).hasMessage("Inactive property source 'mockProperties' imported from location 'test' " assertThat(exception).hasMessage("Inactive property source 'mockProperties' imported from location 'test' "
+ "cannot contain property 'spring' [origin: \"spring\" from property source \"mockProperties\"]"); + "cannot contain property 'spring' [origin: \"spring\" from property source \"mockProperties\"]");
} }
...@@ -65,7 +65,7 @@ class InactiveConfigDataAccessExceptionTests { ...@@ -65,7 +65,7 @@ class InactiveConfigDataAccessExceptionTests {
@Test @Test
void createWhenNoOriginHasCorrectMessage() { void createWhenNoOriginHasCorrectMessage() {
InactiveConfigDataAccessException exception = new InactiveConfigDataAccessException(this.propertySource, InactiveConfigDataAccessException exception = new InactiveConfigDataAccessException(this.propertySource,
this.location, this.propertyName, null); this.resource, this.propertyName, null);
assertThat(exception).hasMessage("Inactive property source 'mockProperties' imported from location 'test' " assertThat(exception).hasMessage("Inactive property source 'mockProperties' imported from location 'test' "
+ "cannot contain property 'spring'"); + "cannot contain property 'spring'");
} }
...@@ -73,28 +73,28 @@ class InactiveConfigDataAccessExceptionTests { ...@@ -73,28 +73,28 @@ class InactiveConfigDataAccessExceptionTests {
@Test @Test
void getPropertySourceReturnsPropertySource() { void getPropertySourceReturnsPropertySource() {
InactiveConfigDataAccessException exception = new InactiveConfigDataAccessException(this.propertySource, InactiveConfigDataAccessException exception = new InactiveConfigDataAccessException(this.propertySource,
this.location, this.propertyName, this.origin); this.resource, this.propertyName, this.origin);
assertThat(exception.getPropertySource()).isSameAs(this.propertySource); assertThat(exception.getPropertySource()).isSameAs(this.propertySource);
} }
@Test @Test
void getLocationReturnsLocation() { void getLocationReturnsLocation() {
InactiveConfigDataAccessException exception = new InactiveConfigDataAccessException(this.propertySource, InactiveConfigDataAccessException exception = new InactiveConfigDataAccessException(this.propertySource,
this.location, this.propertyName, this.origin); this.resource, this.propertyName, this.origin);
assertThat(exception.getLocation()).isSameAs(this.location); assertThat(exception.getLocation()).isSameAs(this.resource);
} }
@Test @Test
void getPropertyNameReturnsPropertyName() { void getPropertyNameReturnsPropertyName() {
InactiveConfigDataAccessException exception = new InactiveConfigDataAccessException(this.propertySource, InactiveConfigDataAccessException exception = new InactiveConfigDataAccessException(this.propertySource,
this.location, this.propertyName, this.origin); this.resource, this.propertyName, this.origin);
assertThat(exception.getPropertyName()).isSameAs(this.propertyName); assertThat(exception.getPropertyName()).isSameAs(this.propertyName);
} }
@Test @Test
void getOriginReturnsOrigin() { void getOriginReturnsOrigin() {
InactiveConfigDataAccessException exception = new InactiveConfigDataAccessException(this.propertySource, InactiveConfigDataAccessException exception = new InactiveConfigDataAccessException(this.propertySource,
this.location, this.propertyName, this.origin); this.resource, this.propertyName, this.origin);
assertThat(exception.getOrigin()).isSameAs(this.origin); assertThat(exception.getOrigin()).isSameAs(this.origin);
} }
...@@ -121,7 +121,7 @@ class InactiveConfigDataAccessExceptionTests { ...@@ -121,7 +121,7 @@ class InactiveConfigDataAccessExceptionTests {
ConfigurationPropertySource configurationPropertySource = ConfigurationPropertySource.from(this.propertySource); ConfigurationPropertySource configurationPropertySource = ConfigurationPropertySource.from(this.propertySource);
given(contributor.getConfigurationPropertySource()).willReturn(configurationPropertySource); given(contributor.getConfigurationPropertySource()).willReturn(configurationPropertySource);
given(contributor.getPropertySource()).willReturn((PropertySource) this.propertySource); given(contributor.getPropertySource()).willReturn((PropertySource) this.propertySource);
given(contributor.getLocation()).willReturn(this.location); given(contributor.getResource()).willReturn(this.resource);
assertThatExceptionOfType(InactiveConfigDataAccessException.class) assertThatExceptionOfType(InactiveConfigDataAccessException.class)
.isThrownBy(() -> InactiveConfigDataAccessException.throwIfPropertyFound(contributor, .isThrownBy(() -> InactiveConfigDataAccessException.throwIfPropertyFound(contributor,
ConfigurationPropertyName.of("spring"))) ConfigurationPropertyName.of("spring")))
...@@ -129,7 +129,7 @@ class InactiveConfigDataAccessExceptionTests { ...@@ -129,7 +129,7 @@ class InactiveConfigDataAccessExceptionTests {
+ "cannot contain property 'spring' [origin: \"spring\" from property source \"mockProperties\"]"); + "cannot contain property 'spring' [origin: \"spring\" from property source \"mockProperties\"]");
} }
private static class TestConfigDataLocation extends ConfigDataLocation { private static class TestConfigDataResource extends ConfigDataResource {
@Override @Override
public String toString() { public String toString() {
......
...@@ -38,7 +38,7 @@ import static org.mockito.Mockito.verify; ...@@ -38,7 +38,7 @@ import static org.mockito.Mockito.verify;
*/ */
class InvalidConfigDataPropertyExceptionTests { class InvalidConfigDataPropertyExceptionTests {
private ConfigDataLocation location = new TestConfigDataLocation(); private ConfigDataResource resource = new TestConfigDataResource();
private ConfigurationPropertyName replacement = ConfigurationPropertyName.of("replacement"); private ConfigurationPropertyName replacement = ConfigurationPropertyName.of("replacement");
...@@ -50,7 +50,7 @@ class InvalidConfigDataPropertyExceptionTests { ...@@ -50,7 +50,7 @@ class InvalidConfigDataPropertyExceptionTests {
@Test @Test
void createHasCorrectMessage() { void createHasCorrectMessage() {
assertThat(new InvalidConfigDataPropertyException(this.property, this.replacement, this.location)).hasMessage( assertThat(new InvalidConfigDataPropertyException(this.property, this.replacement, this.resource)).hasMessage(
"Property 'invalid' imported from location 'test' is invalid and should be replaced with 'replacement' [origin: origin]"); "Property 'invalid' imported from location 'test' is invalid and should be replaced with 'replacement' [origin: origin]");
} }
...@@ -62,35 +62,35 @@ class InvalidConfigDataPropertyExceptionTests { ...@@ -62,35 +62,35 @@ class InvalidConfigDataPropertyExceptionTests {
@Test @Test
void createWhenNoReplacementHasCorrectMessage() { void createWhenNoReplacementHasCorrectMessage() {
assertThat(new InvalidConfigDataPropertyException(this.property, null, this.location)) assertThat(new InvalidConfigDataPropertyException(this.property, null, this.resource))
.hasMessage("Property 'invalid' imported from location 'test' is invalid [origin: origin]"); .hasMessage("Property 'invalid' imported from location 'test' is invalid [origin: origin]");
} }
@Test @Test
void createWhenNoOriginHasCorrectMessage() { void createWhenNoOriginHasCorrectMessage() {
ConfigurationProperty property = new ConfigurationProperty(this.invalid, "bad", null); ConfigurationProperty property = new ConfigurationProperty(this.invalid, "bad", null);
assertThat(new InvalidConfigDataPropertyException(property, this.replacement, this.location)).hasMessage( assertThat(new InvalidConfigDataPropertyException(property, this.replacement, this.resource)).hasMessage(
"Property 'invalid' imported from location 'test' is invalid and should be replaced with 'replacement'"); "Property 'invalid' imported from location 'test' is invalid and should be replaced with 'replacement'");
} }
@Test @Test
void getPropertyReturnsProperty() { void getPropertyReturnsProperty() {
InvalidConfigDataPropertyException exception = new InvalidConfigDataPropertyException(this.property, InvalidConfigDataPropertyException exception = new InvalidConfigDataPropertyException(this.property,
this.replacement, this.location); this.replacement, this.resource);
assertThat(exception.getProperty()).isEqualTo(this.property); assertThat(exception.getProperty()).isEqualTo(this.property);
} }
@Test @Test
void getLocationReturnsLocation() { void getLocationReturnsLocation() {
InvalidConfigDataPropertyException exception = new InvalidConfigDataPropertyException(this.property, InvalidConfigDataPropertyException exception = new InvalidConfigDataPropertyException(this.property,
this.replacement, this.location); this.replacement, this.resource);
assertThat(exception.getLocation()).isEqualTo(this.location); assertThat(exception.getLocation()).isEqualTo(this.resource);
} }
@Test @Test
void getReplacementReturnsReplacement() { void getReplacementReturnsReplacement() {
InvalidConfigDataPropertyException exception = new InvalidConfigDataPropertyException(this.property, InvalidConfigDataPropertyException exception = new InvalidConfigDataPropertyException(this.property,
this.replacement, this.location); this.replacement, this.resource);
assertThat(exception.getReplacement()).isEqualTo(this.replacement); assertThat(exception.getReplacement()).isEqualTo(this.replacement);
} }
...@@ -123,7 +123,7 @@ class InvalidConfigDataPropertyExceptionTests { ...@@ -123,7 +123,7 @@ class InvalidConfigDataPropertyExceptionTests {
+ "'spring.config.activate.on-profile' [origin: \"spring.profiles\" from property source \"mockProperties\"]"); + "'spring.config.activate.on-profile' [origin: \"spring.profiles\" from property source \"mockProperties\"]");
} }
private static class TestConfigDataLocation extends ConfigDataLocation { private static class TestConfigDataResource extends ConfigDataResource {
@Override @Override
public String toString() { public String toString() {
......
/*
* 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.Collections;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.core.io.ClassPathResource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatObject;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link OptionalConfigDataLocation}.
*
* @author Phillip Webb
*/
class OptionalConfigDataLocationTests {
private ConfigDataLocation location;
@BeforeEach
void setup() {
this.location = new ResourceConfigDataLocation("classpath:application.properties",
new ClassPathResource("application.properties"), null, mock(PropertySourceLoader.class));
}
@Test
void createWrapsLocation() {
OptionalConfigDataLocation optionalLocation = new OptionalConfigDataLocation(this.location);
assertThat(optionalLocation.getLocation()).isSameAs(this.location);
}
@Test
void equalsAndHashCode() {
OptionalConfigDataLocation optionalLocation1 = new OptionalConfigDataLocation(this.location);
OptionalConfigDataLocation optionalLocation2 = new OptionalConfigDataLocation(this.location);
assertThat(optionalLocation1.hashCode()).isEqualTo(optionalLocation2.hashCode());
assertThat(optionalLocation1).isEqualTo(optionalLocation1).isEqualTo(optionalLocation2)
.isNotEqualTo(this.location);
}
@Test
void toStringReturnsLocationString() {
OptionalConfigDataLocation optionalLocation = new OptionalConfigDataLocation(this.location);
assertThat(optionalLocation).hasToString(this.location.toString());
}
@Test
void wrapAllWrapsList() {
List<ConfigDataLocation> locations = Collections.singletonList(this.location);
List<ConfigDataLocation> optionalLocations = OptionalConfigDataLocation.wrapAll(locations);
assertThat(optionalLocations).hasSize(1);
assertThat(optionalLocations.get(0)).isInstanceOf(OptionalConfigDataLocation.class).extracting("location")
.isSameAs(this.location);
}
@Test
void unwrapUnwrapps() {
ConfigDataLocation optionalLocation = new OptionalConfigDataLocation(this.location);
assertThatObject(OptionalConfigDataLocation.unwrap(optionalLocation)).isSameAs(this.location);
}
}
...@@ -29,36 +29,43 @@ import static org.assertj.core.api.Assertions.assertThat; ...@@ -29,36 +29,43 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
/** /**
* Tests for {@link ResourceConfigDataLoader}. * Tests for {@link StandardConfigDataLoader}.
* *
* @author Madhura Bhave * @author Madhura Bhave
* @author Phillip Webb * @author Phillip Webb
*/ */
public class ResourceConfigDataLoaderTests { public class StandardConfigDataLoaderTests {
private ResourceConfigDataLoader loader = new ResourceConfigDataLoader(); private StandardConfigDataLoader loader = new StandardConfigDataLoader();
private ConfigDataLoaderContext loaderContext = mock(ConfigDataLoaderContext.class); private ConfigDataLoaderContext loaderContext = mock(ConfigDataLoaderContext.class);
@Test @Test
void loadWhenLocationResultsInMultiplePropertySourcesAddsAllToConfigData() throws IOException { void loadWhenLocationResultsInMultiplePropertySourcesAddsAllToConfigData() throws IOException {
ResourceConfigDataLocation location = new ResourceConfigDataLocation("application.yml", ClassPathResource resource = new ClassPathResource("configdata/yaml/application.yml");
new ClassPathResource("configdata/yaml/application.yml"), null, new YamlPropertySourceLoader()); StandardConfigDataReference reference = new StandardConfigDataReference(
ConfigDataLocation.of("classpath:configdata/yaml/application.yml"), null,
"classpath:configdata/yaml/application", null, "yml", new YamlPropertySourceLoader());
StandardConfigDataResource location = new StandardConfigDataResource(reference, resource);
ConfigData configData = this.loader.load(this.loaderContext, location); ConfigData configData = this.loader.load(this.loaderContext, location);
assertThat(configData.getPropertySources().size()).isEqualTo(2); assertThat(configData.getPropertySources().size()).isEqualTo(2);
PropertySource<?> source1 = configData.getPropertySources().get(0); PropertySource<?> source1 = configData.getPropertySources().get(0);
PropertySource<?> source2 = configData.getPropertySources().get(1); PropertySource<?> source2 = configData.getPropertySources().get(1);
assertThat(source1.getName()).isEqualTo("application.yml (document #0)"); assertThat(source1.getName()).isEqualTo("Config resource 'classpath:configdata/yaml/application.yml' "
+ "via location 'classpath:configdata/yaml/application.yml' (document #0)");
assertThat(source1.getProperty("foo")).isEqualTo("bar"); assertThat(source1.getProperty("foo")).isEqualTo("bar");
assertThat(source2.getName()).isEqualTo("application.yml (document #1)"); assertThat(source2.getName()).isEqualTo("Config resource 'classpath:configdata/yaml/application.yml' "
+ "via location 'classpath:configdata/yaml/application.yml' (document #1)");
assertThat(source2.getProperty("hello")).isEqualTo("world"); assertThat(source2.getProperty("hello")).isEqualTo("world");
} }
@Test @Test
void loadWhenPropertySourceIsEmptyAddsNothingToConfigData() throws IOException { void loadWhenPropertySourceIsEmptyAddsNothingToConfigData() throws IOException {
ResourceConfigDataLocation location = new ResourceConfigDataLocation("testproperties.properties", ClassPathResource resource = new ClassPathResource("config/0-empty/testproperties.properties");
new ClassPathResource("config/0-empty/testproperties.properties"), null, StandardConfigDataReference reference = new StandardConfigDataReference(
new PropertiesPropertySourceLoader()); ConfigDataLocation.of("classpath:config/0-empty/testproperties.properties"), null,
"config/0-empty/testproperties", null, "properties", new PropertiesPropertySourceLoader());
StandardConfigDataResource location = new StandardConfigDataResource(reference, resource);
ConfigData configData = this.loader.load(this.loaderContext, location); ConfigData configData = this.loader.load(this.loaderContext, location);
assertThat(configData.getPropertySources().size()).isEqualTo(0); assertThat(configData.getPropertySources().size()).isEqualTo(0);
} }
......
...@@ -38,14 +38,14 @@ import static org.mockito.BDDMockito.given; ...@@ -38,14 +38,14 @@ import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
/** /**
* Tests for {@link ResourceConfigDataLocationResolver}. * Tests for {@link StandardConfigDataLocationResolver}.
* *
* @author Madhura Bhave * @author Madhura Bhave
* @author Phillip Webb * @author Phillip Webb
*/ */
public class ResourceConfigDataLocationResolverTests { public class StandardConfigDataLocationResolverTests {
private ResourceConfigDataLocationResolver resolver; private StandardConfigDataLocationResolver resolver;
private ConfigDataLocationResolverContext context = mock(ConfigDataLocationResolverContext.class); private ConfigDataLocationResolverContext context = mock(ConfigDataLocationResolverContext.class);
...@@ -59,19 +59,19 @@ public class ResourceConfigDataLocationResolverTests { ...@@ -59,19 +59,19 @@ public class ResourceConfigDataLocationResolverTests {
void setup() { void setup() {
this.environment = new MockEnvironment(); this.environment = new MockEnvironment();
this.environmentBinder = Binder.get(this.environment); this.environmentBinder = Binder.get(this.environment);
this.resolver = new ResourceConfigDataLocationResolver(new DeferredLog(), this.environmentBinder, this.resolver = new StandardConfigDataLocationResolver(new DeferredLog(), this.environmentBinder,
this.resourceLoader); this.resourceLoader);
} }
@Test @Test
void isResolvableAlwaysReturnsTrue() { void isResolvableAlwaysReturnsTrue() {
assertThat(this.resolver.isResolvable(this.context, "test")).isTrue(); assertThat(this.resolver.isResolvable(this.context, ConfigDataLocation.of("test"))).isTrue();
} }
@Test @Test
void resolveWhenLocationIsDirectoryResolvesAllMatchingFilesInDirectory() { void resolveWhenLocationIsDirectoryResolvesAllMatchingFilesInDirectory() {
String location = "classpath:/configdata/properties/"; ConfigDataLocation location = ConfigDataLocation.of("classpath:/configdata/properties/");
List<ResourceConfigDataLocation> locations = this.resolver.resolve(this.context, location, true); List<StandardConfigDataResource> locations = this.resolver.resolve(this.context, location);
assertThat(locations.size()).isEqualTo(1); assertThat(locations.size()).isEqualTo(1);
assertThat(locations).extracting(Object::toString) assertThat(locations).extracting(Object::toString)
.containsExactly("class path resource [configdata/properties/application.properties]"); .containsExactly("class path resource [configdata/properties/application.properties]");
...@@ -79,8 +79,9 @@ public class ResourceConfigDataLocationResolverTests { ...@@ -79,8 +79,9 @@ public class ResourceConfigDataLocationResolverTests {
@Test @Test
void resolveWhenLocationIsFileResolvesFile() { void resolveWhenLocationIsFileResolvesFile() {
String location = "file:src/test/resources/configdata/properties/application.properties"; ConfigDataLocation location = ConfigDataLocation
List<ResourceConfigDataLocation> locations = this.resolver.resolve(this.context, location, true); .of("file:src/test/resources/configdata/properties/application.properties");
List<StandardConfigDataResource> locations = this.resolver.resolve(this.context, location);
assertThat(locations.size()).isEqualTo(1); assertThat(locations.size()).isEqualTo(1);
assertThat(locations).extracting(Object::toString).containsExactly( assertThat(locations).extracting(Object::toString).containsExactly(
filePath("src", "test", "resources", "configdata", "properties", "application.properties")); filePath("src", "test", "resources", "configdata", "properties", "application.properties"));
...@@ -88,23 +89,24 @@ public class ResourceConfigDataLocationResolverTests { ...@@ -88,23 +89,24 @@ public class ResourceConfigDataLocationResolverTests {
@Test @Test
void resolveWhenLocationIsFileAndNoMatchingLoaderThrowsException() { void resolveWhenLocationIsFileAndNoMatchingLoaderThrowsException() {
String location = "file:src/test/resources/configdata/properties/application.unknown"; ConfigDataLocation location = ConfigDataLocation
assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location, true)) .of("file:src/test/resources/configdata/properties/application.unknown");
assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location))
.withMessageStartingWith("Unable to load config data from") .withMessageStartingWith("Unable to load config data from")
.satisfies((ex) -> assertThat(ex.getCause()).hasMessageStartingWith("File extension is not known")); .satisfies((ex) -> assertThat(ex.getCause()).hasMessageStartingWith("File extension is not known"));
} }
@Test @Test
void resolveWhenLocationWildcardIsSpecifiedForClasspathLocationThrowsException() { void resolveWhenLocationWildcardIsSpecifiedForClasspathLocationThrowsException() {
String location = "classpath*:application.properties"; ConfigDataLocation location = ConfigDataLocation.of("classpath*:application.properties");
assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location, true)) assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location))
.withMessageContaining("Classpath wildcard patterns cannot be used as a search location"); .withMessageContaining("Classpath wildcard patterns cannot be used as a search location");
} }
@Test @Test
void resolveWhenLocationWildcardIsNotBeforeLastSlashThrowsException() { void resolveWhenLocationWildcardIsNotBeforeLastSlashThrowsException() {
String location = "file:src/test/resources/*/config/"; ConfigDataLocation location = ConfigDataLocation.of("file:src/test/resources/*/config/");
assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location, true)) assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location))
.withMessageStartingWith("Search location '").withMessageEndingWith("' must end with '*/'"); .withMessageStartingWith("Search location '").withMessageEndingWith("' must end with '*/'");
} }
...@@ -113,24 +115,24 @@ public class ResourceConfigDataLocationResolverTests { ...@@ -113,24 +115,24 @@ public class ResourceConfigDataLocationResolverTests {
this.environment.setProperty("spring.config.name", "*/application"); this.environment.setProperty("spring.config.name", "*/application");
assertThatIllegalStateException() assertThatIllegalStateException()
.isThrownBy( .isThrownBy(
() -> new ResourceConfigDataLocationResolver(null, this.environmentBinder, this.resourceLoader)) () -> new StandardConfigDataLocationResolver(null, this.environmentBinder, this.resourceLoader))
.withMessageStartingWith("Config name '").withMessageEndingWith("' cannot contain '*'"); .withMessageStartingWith("Config name '").withMessageEndingWith("' cannot contain '*'");
} }
@Test @Test
void resolveWhenLocationHasMultipleWildcardsThrowsException() { void resolveWhenLocationHasMultipleWildcardsThrowsException() {
String location = "file:src/test/resources/config/**/"; ConfigDataLocation location = ConfigDataLocation.of("file:src/test/resources/config/**/");
assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location, true)) assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location))
.withMessageStartingWith("Search location '") .withMessageStartingWith("Search location '")
.withMessageEndingWith("' cannot contain multiple wildcards"); .withMessageEndingWith("' cannot contain multiple wildcards");
} }
@Test @Test
void resolveWhenLocationIsWildcardDirectoriesRestrictsToOneLevelDeep() { void resolveWhenLocationIsWildcardDirectoriesRestrictsToOneLevelDeep() {
String location = "file:src/test/resources/config/*/"; ConfigDataLocation location = ConfigDataLocation.of("file:src/test/resources/config/*/");
this.environment.setProperty("spring.config.name", "testproperties"); this.environment.setProperty("spring.config.name", "testproperties");
this.resolver = new ResourceConfigDataLocationResolver(null, this.environmentBinder, this.resourceLoader); this.resolver = new StandardConfigDataLocationResolver(null, this.environmentBinder, this.resourceLoader);
List<ResourceConfigDataLocation> locations = this.resolver.resolve(this.context, location, true); List<StandardConfigDataResource> locations = this.resolver.resolve(this.context, location);
assertThat(locations.size()).isEqualTo(3); assertThat(locations.size()).isEqualTo(3);
assertThat(locations).extracting(Object::toString) assertThat(locations).extracting(Object::toString)
.contains(filePath("src", "test", "resources", "config", "1-first", "testproperties.properties")) .contains(filePath("src", "test", "resources", "config", "1-first", "testproperties.properties"))
...@@ -140,10 +142,10 @@ public class ResourceConfigDataLocationResolverTests { ...@@ -140,10 +142,10 @@ public class ResourceConfigDataLocationResolverTests {
@Test @Test
void resolveWhenLocationIsWildcardDirectoriesSortsAlphabeticallyBasedOnAbsolutePath() { void resolveWhenLocationIsWildcardDirectoriesSortsAlphabeticallyBasedOnAbsolutePath() {
String location = "file:src/test/resources/config/*/"; ConfigDataLocation location = ConfigDataLocation.of("file:src/test/resources/config/*/");
this.environment.setProperty("spring.config.name", "testproperties"); this.environment.setProperty("spring.config.name", "testproperties");
this.resolver = new ResourceConfigDataLocationResolver(null, this.environmentBinder, this.resourceLoader); this.resolver = new StandardConfigDataLocationResolver(null, this.environmentBinder, this.resourceLoader);
List<ResourceConfigDataLocation> locations = this.resolver.resolve(this.context, location, true); List<StandardConfigDataResource> locations = this.resolver.resolve(this.context, location);
assertThat(locations).extracting(Object::toString).containsExactly( assertThat(locations).extracting(Object::toString).containsExactly(
filePath("src", "test", "resources", "config", "0-empty", "testproperties.properties"), filePath("src", "test", "resources", "config", "0-empty", "testproperties.properties"),
filePath("src", "test", "resources", "config", "1-first", "testproperties.properties"), filePath("src", "test", "resources", "config", "1-first", "testproperties.properties"),
...@@ -152,8 +154,9 @@ public class ResourceConfigDataLocationResolverTests { ...@@ -152,8 +154,9 @@ public class ResourceConfigDataLocationResolverTests {
@Test @Test
void resolveWhenLocationIsWildcardFilesLoadsAllFilesThatMatch() { void resolveWhenLocationIsWildcardFilesLoadsAllFilesThatMatch() {
String location = "file:src/test/resources/config/*/testproperties.properties"; ConfigDataLocation location = ConfigDataLocation
List<ResourceConfigDataLocation> locations = this.resolver.resolve(this.context, location, true); .of("file:src/test/resources/config/*/testproperties.properties");
List<StandardConfigDataResource> locations = this.resolver.resolve(this.context, location);
assertThat(locations.size()).isEqualTo(3); assertThat(locations.size()).isEqualTo(3);
assertThat(locations).extracting(Object::toString) assertThat(locations).extracting(Object::toString)
.contains(filePath("src", "test", "resources", "config", "1-first", "testproperties.properties")) .contains(filePath("src", "test", "resources", "config", "1-first", "testproperties.properties"))
...@@ -165,15 +168,17 @@ public class ResourceConfigDataLocationResolverTests { ...@@ -165,15 +168,17 @@ public class ResourceConfigDataLocationResolverTests {
@Test @Test
void resolveWhenLocationIsRelativeAndFileResolves() { void resolveWhenLocationIsRelativeAndFileResolves() {
this.environment.setProperty("spring.config.name", "other"); this.environment.setProperty("spring.config.name", "other");
String location = "other.properties"; ConfigDataLocation location = ConfigDataLocation.of("other.properties");
this.resolver = new ResourceConfigDataLocationResolver(new DeferredLog(), this.environmentBinder, this.resolver = new StandardConfigDataLocationResolver(new DeferredLog(), this.environmentBinder,
this.resourceLoader); this.resourceLoader);
ClassPathResource parentResource = new ClassPathResource("configdata/properties/application.properties"); StandardConfigDataReference parentReference = new StandardConfigDataReference(
ResourceConfigDataLocation parent = new ResourceConfigDataLocation( ConfigDataLocation.of("classpath:configdata/properties/application.properties"), null,
"classpath:/configdata/properties/application.properties", parentResource, null, "classpath:configdata/properties/application", null, "properties",
new PropertiesPropertySourceLoader()); new PropertiesPropertySourceLoader());
ClassPathResource parentResource = new ClassPathResource("configdata/properties/application.properties");
StandardConfigDataResource parent = new StandardConfigDataResource(parentReference, parentResource);
given(this.context.getParent()).willReturn(parent); given(this.context.getParent()).willReturn(parent);
List<ResourceConfigDataLocation> locations = this.resolver.resolve(this.context, location, true); List<StandardConfigDataResource> locations = this.resolver.resolve(this.context, location);
assertThat(locations.size()).isEqualTo(1); assertThat(locations.size()).isEqualTo(1);
assertThat(locations).extracting(Object::toString) assertThat(locations).extracting(Object::toString)
.contains("class path resource [configdata/properties/other.properties]"); .contains("class path resource [configdata/properties/other.properties]");
...@@ -182,14 +187,16 @@ public class ResourceConfigDataLocationResolverTests { ...@@ -182,14 +187,16 @@ public class ResourceConfigDataLocationResolverTests {
@Test @Test
void resolveWhenLocationIsRelativeAndDirectoryResolves() { void resolveWhenLocationIsRelativeAndDirectoryResolves() {
this.environment.setProperty("spring.config.name", "testproperties"); this.environment.setProperty("spring.config.name", "testproperties");
String location = "nested/3-third/"; ConfigDataLocation location = ConfigDataLocation.of("nested/3-third/");
this.resolver = new ResourceConfigDataLocationResolver(new DeferredLog(), this.environmentBinder, this.resolver = new StandardConfigDataLocationResolver(new DeferredLog(), this.environmentBinder,
this.resourceLoader); this.resourceLoader);
StandardConfigDataReference parentReference = new StandardConfigDataReference(
ConfigDataLocation.of("optional:classpath:configdata/"), null, "classpath:config/specific", null,
"properties", new PropertiesPropertySourceLoader());
ClassPathResource parentResource = new ClassPathResource("config/specific.properties"); ClassPathResource parentResource = new ClassPathResource("config/specific.properties");
ResourceConfigDataLocation parent = new ResourceConfigDataLocation("classpath:/config/specific.properties", StandardConfigDataResource parent = new StandardConfigDataResource(parentReference, parentResource);
parentResource, null, new PropertiesPropertySourceLoader());
given(this.context.getParent()).willReturn(parent); given(this.context.getParent()).willReturn(parent);
List<ResourceConfigDataLocation> locations = this.resolver.resolve(this.context, location, true); List<StandardConfigDataResource> locations = this.resolver.resolve(this.context, location);
assertThat(locations.size()).isEqualTo(1); assertThat(locations.size()).isEqualTo(1);
assertThat(locations).extracting(Object::toString) assertThat(locations).extracting(Object::toString)
.contains("class path resource [config/nested/3-third/testproperties.properties]"); .contains("class path resource [config/nested/3-third/testproperties.properties]");
...@@ -197,34 +204,36 @@ public class ResourceConfigDataLocationResolverTests { ...@@ -197,34 +204,36 @@ public class ResourceConfigDataLocationResolverTests {
@Test @Test
void resolveWhenLocationIsRelativeAndNoMatchingLoaderThrowsException() { void resolveWhenLocationIsRelativeAndNoMatchingLoaderThrowsException() {
String location = "application.other"; ConfigDataLocation location = ConfigDataLocation.of("application.other");
ClassPathResource parentResource = new ClassPathResource("configdata/application.properties"); StandardConfigDataReference parentReference = new StandardConfigDataReference(
ResourceConfigDataLocation parent = new ResourceConfigDataLocation( ConfigDataLocation.of("classpath:configdata/properties/application.properties"), null,
"classpath:/configdata/application.properties", parentResource, null, "configdata/properties/application", null, "properties", new PropertiesPropertySourceLoader());
new PropertiesPropertySourceLoader()); ClassPathResource parentResource = new ClassPathResource("configdata/properties/application.properties");
StandardConfigDataResource parent = new StandardConfigDataResource(parentReference, parentResource);
given(this.context.getParent()).willReturn(parent); given(this.context.getParent()).willReturn(parent);
assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location, true)) assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location))
.withMessageStartingWith("Unable to load config data from 'application.other'") .withMessageStartingWith("Unable to load config data from 'application.other'")
.satisfies((ex) -> assertThat(ex.getCause()).hasMessageStartingWith("File extension is not known")); .satisfies((ex) -> assertThat(ex.getCause()).hasMessageStartingWith("File extension is not known"));
} }
@Test @Test
void resolveWhenLocationUsesOptionalExtensionSyntaxResolves() throws Exception { void resolveWhenLocationUsesOptionalExtensionSyntaxResolves() throws Exception {
String location = "classpath:/application-props-no-extension[.properties]"; ConfigDataLocation location = ConfigDataLocation.of("classpath:/application-props-no-extension[.properties]");
List<ResourceConfigDataLocation> locations = this.resolver.resolve(this.context, location, true); List<StandardConfigDataResource> locations = this.resolver.resolve(this.context, location);
assertThat(locations.size()).isEqualTo(1); assertThat(locations.size()).isEqualTo(1);
ResourceConfigDataLocation resolved = locations.get(0); StandardConfigDataResource resolved = locations.get(0);
assertThat(resolved.getResource().getFilename()).endsWith("application-props-no-extension"); assertThat(resolved.getResource().getFilename()).endsWith("application-props-no-extension");
PropertySource<?> propertySource = resolved.load().get(0); ConfigData loaded = new StandardConfigDataLoader().load(null, resolved);
PropertySource<?> propertySource = loaded.getPropertySources().get(0);
assertThat(propertySource.getProperty("withnotext")).isEqualTo("test"); assertThat(propertySource.getProperty("withnotext")).isEqualTo("test");
} }
@Test @Test
void resolveProfileSpecificReturnsProfileSpecificFiles() { void resolveProfileSpecificReturnsProfileSpecificFiles() {
String location = "classpath:/configdata/properties/"; ConfigDataLocation location = ConfigDataLocation.of("classpath:/configdata/properties/");
Profiles profiles = mock(Profiles.class); Profiles profiles = mock(Profiles.class);
given(profiles.iterator()).willReturn(Collections.singletonList("dev").iterator()); given(profiles.iterator()).willReturn(Collections.singletonList("dev").iterator());
List<ResourceConfigDataLocation> locations = this.resolver.resolveProfileSpecific(this.context, location, true, List<StandardConfigDataResource> locations = this.resolver.resolveProfileSpecific(this.context, location,
profiles); profiles);
assertThat(locations.size()).isEqualTo(1); assertThat(locations.size()).isEqualTo(1);
assertThat(locations).extracting(Object::toString) assertThat(locations).extracting(Object::toString)
...@@ -233,11 +242,11 @@ public class ResourceConfigDataLocationResolverTests { ...@@ -233,11 +242,11 @@ public class ResourceConfigDataLocationResolverTests {
@Test @Test
void resolveProfileSpecificWhenLocationIsFileReturnsEmptyList() { void resolveProfileSpecificWhenLocationIsFileReturnsEmptyList() {
String location = "classpath:/configdata/properties/application.properties"; ConfigDataLocation location = ConfigDataLocation.of("classpath:/configdata/properties/application.properties");
Profiles profiles = mock(Profiles.class); Profiles profiles = mock(Profiles.class);
given(profiles.iterator()).willReturn(Collections.emptyIterator()); given(profiles.iterator()).willReturn(Collections.emptyIterator());
given(profiles.getActive()).willReturn(Collections.singletonList("dev")); given(profiles.getActive()).willReturn(Collections.singletonList("dev"));
List<ResourceConfigDataLocation> locations = this.resolver.resolveProfileSpecific(this.context, location, true, List<StandardConfigDataResource> locations = this.resolver.resolveProfileSpecific(this.context, location,
profiles); profiles);
assertThat(locations).isEmpty(); assertThat(locations).isEmpty();
} }
......
...@@ -18,7 +18,6 @@ package org.springframework.boot.context.config; ...@@ -18,7 +18,6 @@ package org.springframework.boot.context.config;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
...@@ -27,47 +26,34 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException ...@@ -27,47 +26,34 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
/** /**
* Tests for {@link ResourceConfigDataLocation}. * Tests for {@link StandardConfigDataResource}.
* *
* @author Madhura Bhave * @author Madhura Bhave
* @author Phillip Webb * @author Phillip Webb
*/ */
public class ResourceConfigDataLocationTests { public class StandardConfigDataResourceTests {
private final String location = "location"; StandardConfigDataReference reference = mock(StandardConfigDataReference.class);
private final Resource resource = mock(Resource.class); private final Resource resource = mock(Resource.class);
private final PropertySourceLoader propertySourceLoader = mock(PropertySourceLoader.class);
@Test @Test
void constructorWhenNameIsNullThrowsException() { void createWhenReferenceIsNullThrowsException() {
assertThatIllegalArgumentException() assertThatIllegalArgumentException().isThrownBy(() -> new StandardConfigDataResource(null, this.resource))
.isThrownBy(() -> new ResourceConfigDataLocation(null, this.resource, null, this.propertySourceLoader)) .withMessage("Reference must not be null");
.withMessage("Name must not be null");
} }
@Test @Test
void constructorWhenResourceIsNullThrowsException() { void createWhenResourceIsNullThrowsException() {
assertThatIllegalArgumentException() assertThatIllegalArgumentException().isThrownBy(() -> new StandardConfigDataResource(this.reference, null))
.isThrownBy(() -> new ResourceConfigDataLocation(this.location, null, null, this.propertySourceLoader))
.withMessage("Resource must not be null"); .withMessage("Resource must not be null");
} }
@Test
void constructorWhenLoaderIsNullThrowsException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new ResourceConfigDataLocation(this.location, this.resource, null, null))
.withMessage("PropertySourceLoader must not be null");
}
@Test @Test
void equalsWhenResourceIsTheSameReturnsTrue() { void equalsWhenResourceIsTheSameReturnsTrue() {
Resource resource = new ClassPathResource("config/"); Resource resource = new ClassPathResource("config/");
ResourceConfigDataLocation location = new ResourceConfigDataLocation("my-location", resource, null, StandardConfigDataResource location = new StandardConfigDataResource(this.reference, resource);
this.propertySourceLoader); StandardConfigDataResource other = new StandardConfigDataResource(this.reference, resource);
ResourceConfigDataLocation other = new ResourceConfigDataLocation("other-location", resource, null,
this.propertySourceLoader);
assertThat(location).isEqualTo(other); assertThat(location).isEqualTo(other);
} }
...@@ -75,10 +61,8 @@ public class ResourceConfigDataLocationTests { ...@@ -75,10 +61,8 @@ public class ResourceConfigDataLocationTests {
void equalsWhenResourceIsDifferentReturnsFalse() { void equalsWhenResourceIsDifferentReturnsFalse() {
Resource resource1 = new ClassPathResource("config/"); Resource resource1 = new ClassPathResource("config/");
Resource resource2 = new ClassPathResource("configdata/"); Resource resource2 = new ClassPathResource("configdata/");
ResourceConfigDataLocation location = new ResourceConfigDataLocation("my-location", resource1, null, StandardConfigDataResource location = new StandardConfigDataResource(this.reference, resource1);
this.propertySourceLoader); StandardConfigDataResource other = new StandardConfigDataResource(this.reference, resource2);
ResourceConfigDataLocation other = new ResourceConfigDataLocation("other-location", resource2, null,
this.propertySourceLoader);
assertThat(location).isNotEqualTo(other); assertThat(location).isNotEqualTo(other);
} }
......
...@@ -37,27 +37,27 @@ import org.springframework.core.env.MapPropertySource; ...@@ -37,27 +37,27 @@ import org.springframework.core.env.MapPropertySource;
*/ */
class TestConfigDataBootstrap { class TestConfigDataBootstrap {
static class LocationResolver implements ConfigDataLocationResolver<Location> { static class LocationResolver implements ConfigDataLocationResolver<Resource> {
@Override @Override
public boolean isResolvable(ConfigDataLocationResolverContext context, String location) { public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location) {
return location.startsWith("testbootstrap:"); return location.hasPrefix("testbootstrap:");
} }
@Override @Override
public List<Location> resolve(ConfigDataLocationResolverContext context, String location, boolean optional) { public List<Resource> resolve(ConfigDataLocationResolverContext context, ConfigDataLocation location) {
context.getBootstrapContext().registerIfAbsent(ResolverHelper.class, context.getBootstrapContext().registerIfAbsent(ResolverHelper.class,
InstanceSupplier.from(() -> new ResolverHelper(location))); InstanceSupplier.from(() -> new ResolverHelper(location)));
ResolverHelper helper = context.getBootstrapContext().get(ResolverHelper.class); ResolverHelper helper = context.getBootstrapContext().get(ResolverHelper.class);
return Collections.singletonList(new Location(helper)); return Collections.singletonList(new Resource(helper));
} }
} }
static class Loader implements ConfigDataLoader<Location> { static class Loader implements ConfigDataLoader<Resource> {
@Override @Override
public ConfigData load(ConfigDataLoaderContext context, Location location) throws IOException { public ConfigData load(ConfigDataLoaderContext context, Resource location) throws IOException {
context.getBootstrapContext().registerIfAbsent(LoaderHelper.class, context.getBootstrapContext().registerIfAbsent(LoaderHelper.class,
(bootstrapContext) -> new LoaderHelper(location, () -> bootstrapContext.get(Binder.class))); (bootstrapContext) -> new LoaderHelper(location, () -> bootstrapContext.get(Binder.class)));
LoaderHelper helper = context.getBootstrapContext().get(LoaderHelper.class); LoaderHelper helper = context.getBootstrapContext().get(LoaderHelper.class);
...@@ -68,11 +68,11 @@ class TestConfigDataBootstrap { ...@@ -68,11 +68,11 @@ class TestConfigDataBootstrap {
} }
static class Location extends ConfigDataLocation { static class Resource extends ConfigDataResource {
private final ResolverHelper resolverHelper; private final ResolverHelper resolverHelper;
Location(ResolverHelper resolverHelper) { Resource(ResolverHelper resolverHelper) {
this.resolverHelper = resolverHelper; this.resolverHelper = resolverHelper;
} }
...@@ -89,13 +89,13 @@ class TestConfigDataBootstrap { ...@@ -89,13 +89,13 @@ class TestConfigDataBootstrap {
static class ResolverHelper { static class ResolverHelper {
private final String location; private final ConfigDataLocation location;
ResolverHelper(String location) { ResolverHelper(ConfigDataLocation location) {
this.location = location; this.location = location;
} }
String getLocation() { ConfigDataLocation getLocation() {
return this.location; return this.location;
} }
...@@ -103,16 +103,16 @@ class TestConfigDataBootstrap { ...@@ -103,16 +103,16 @@ class TestConfigDataBootstrap {
static class LoaderHelper implements ApplicationListener<BootstrapContextClosedEvent> { static class LoaderHelper implements ApplicationListener<BootstrapContextClosedEvent> {
private final Location location; private final Resource location;
private final Supplier<Binder> binder; private final Supplier<Binder> binder;
LoaderHelper(Location location, Supplier<Binder> binder) { LoaderHelper(Resource location, Supplier<Binder> binder) {
this.location = location; this.location = location;
this.binder = binder; this.binder = binder;
} }
Location getLocation() { Resource getLocation() {
return this.location; return this.location;
} }
......
...@@ -30,14 +30,16 @@ class UnsupportedConfigDataLocationExceptionTests { ...@@ -30,14 +30,16 @@ class UnsupportedConfigDataLocationExceptionTests {
@Test @Test
void createSetsMessage() { void createSetsMessage() {
UnsupportedConfigDataLocationException exception = new UnsupportedConfigDataLocationException("test"); UnsupportedConfigDataLocationException exception = new UnsupportedConfigDataLocationException(
ConfigDataLocation.of("test"));
assertThat(exception).hasMessage("Unsupported config data location 'test'"); assertThat(exception).hasMessage("Unsupported config data location 'test'");
} }
@Test @Test
void getLocationReturnsLocation() { void getLocationReturnsLocation() {
UnsupportedConfigDataLocationException exception = new UnsupportedConfigDataLocationException("test"); ConfigDataLocation location = ConfigDataLocation.of("test");
assertThat(exception.getLocation()).isEqualTo("test"); UnsupportedConfigDataLocationException exception = new UnsupportedConfigDataLocationException(location);
assertThat(exception.getLocation()).isEqualTo(location);
} }
} }
...@@ -36,7 +36,7 @@ import org.springframework.core.env.PropertySource; ...@@ -36,7 +36,7 @@ import org.springframework.core.env.PropertySource;
* *
* @author Phillip Webb * @author Phillip Webb
*/ */
class SubversionConfigDataLoader implements ConfigDataLoader<SubversionConfigDataLocation> { class SubversionConfigDataLoader implements ConfigDataLoader<SubversionConfigDataResource> {
private static final ApplicationListener<BootstrapContextClosedEvent> closeListener = SubversionConfigDataLoader::onBootstrapContextClosed; private static final ApplicationListener<BootstrapContextClosedEvent> closeListener = SubversionConfigDataLoader::onBootstrapContextClosed;
...@@ -50,12 +50,12 @@ class SubversionConfigDataLoader implements ConfigDataLoader<SubversionConfigDat ...@@ -50,12 +50,12 @@ class SubversionConfigDataLoader implements ConfigDataLoader<SubversionConfigDat
} }
@Override @Override
public ConfigData load(ConfigDataLoaderContext context, SubversionConfigDataLocation location) public ConfigData load(ConfigDataLoaderContext context, SubversionConfigDataResource resource)
throws IOException, ConfigDataLocationNotFoundException { throws IOException, ConfigDataLocationNotFoundException {
context.getBootstrapContext().registerIfAbsent(SubversionServerCertificate.class, context.getBootstrapContext().registerIfAbsent(SubversionServerCertificate.class,
InstanceSupplier.of(location.getServerCertificate())); InstanceSupplier.of(resource.getServerCertificate()));
SubversionClient client = context.getBootstrapContext().get(SubversionClient.class); SubversionClient client = context.getBootstrapContext().get(SubversionClient.class);
String loaded = client.load(location.getLocation()); String loaded = client.load(resource.getLocation());
PropertySource<?> propertySource = new MapPropertySource("svn", Collections.singletonMap("svn", loaded)); PropertySource<?> propertySource = new MapPropertySource("svn", Collections.singletonMap("svn", loaded));
return new ConfigData(Collections.singleton(propertySource)); return new ConfigData(Collections.singleton(propertySource));
} }
......
...@@ -19,30 +19,33 @@ package smoketest.bootstrapregistry.external.svn; ...@@ -19,30 +19,33 @@ package smoketest.bootstrapregistry.external.svn;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.springframework.boot.context.config.ConfigDataLocation;
import org.springframework.boot.context.config.ConfigDataLocationNotFoundException; import org.springframework.boot.context.config.ConfigDataLocationNotFoundException;
import org.springframework.boot.context.config.ConfigDataLocationResolver; import org.springframework.boot.context.config.ConfigDataLocationResolver;
import org.springframework.boot.context.config.ConfigDataLocationResolverContext; import org.springframework.boot.context.config.ConfigDataLocationResolverContext;
import org.springframework.boot.context.config.ConfigDataResourceNotFoundException;
/** /**
* {@link ConfigDataLocationResolver} for subversion. * {@link ConfigDataLocationResolver} for subversion.
* *
* @author Phillip Webb * @author Phillip Webb
*/ */
class SubversionConfigDataLocationResolver implements ConfigDataLocationResolver<SubversionConfigDataLocation> { class SubversionConfigDataLocationResolver implements ConfigDataLocationResolver<SubversionConfigDataResource> {
private static final String PREFIX = "svn:"; private static final String PREFIX = "svn:";
@Override @Override
public boolean isResolvable(ConfigDataLocationResolverContext context, String location) { public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location) {
return location.startsWith(PREFIX); return location.hasPrefix(PREFIX);
} }
@Override @Override
public List<SubversionConfigDataLocation> resolve(ConfigDataLocationResolverContext context, String location, public List<SubversionConfigDataResource> resolve(ConfigDataLocationResolverContext context,
boolean optional) throws ConfigDataLocationNotFoundException { ConfigDataLocation location)
throws ConfigDataLocationNotFoundException, ConfigDataResourceNotFoundException {
String serverCertificate = context.getBinder().bind("spring.svn.server.certificate", String.class).orElse(null); String serverCertificate = context.getBinder().bind("spring.svn.server.certificate", String.class).orElse(null);
return Collections.singletonList( return Collections.singletonList(
new SubversionConfigDataLocation(location.substring(PREFIX.length()), serverCertificate)); new SubversionConfigDataResource(location.getNonPrefixedValue(PREFIX), serverCertificate));
} }
} }
...@@ -16,20 +16,20 @@ ...@@ -16,20 +16,20 @@
package smoketest.bootstrapregistry.external.svn; package smoketest.bootstrapregistry.external.svn;
import org.springframework.boot.context.config.ConfigDataLocation; import org.springframework.boot.context.config.ConfigDataResource;
/** /**
* A subversion {@link ConfigDataLocation}. * A subversion {@link ConfigDataResource}.
* *
* @author Phillip Webb * @author Phillip Webb
*/ */
class SubversionConfigDataLocation extends ConfigDataLocation { class SubversionConfigDataResource extends ConfigDataResource {
private final String location; private final String location;
private final SubversionServerCertificate serverCertificate; private final SubversionServerCertificate serverCertificate;
SubversionConfigDataLocation(String location, String serverCertificate) { SubversionConfigDataResource(String location, String serverCertificate) {
this.location = location; this.location = location;
this.serverCertificate = SubversionServerCertificate.of(serverCertificate); this.serverCertificate = SubversionServerCertificate.of(serverCertificate);
} }
...@@ -50,7 +50,7 @@ class SubversionConfigDataLocation extends ConfigDataLocation { ...@@ -50,7 +50,7 @@ class SubversionConfigDataLocation extends ConfigDataLocation {
if (obj == null || getClass() != obj.getClass()) { if (obj == null || getClass() != obj.getClass()) {
return false; return false;
} }
SubversionConfigDataLocation other = (SubversionConfigDataLocation) obj; SubversionConfigDataResource other = (SubversionConfigDataResource) obj;
return this.location.equals(other.location); return this.location.equals(other.location);
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment