From 1cf9fc107e5ed29a44935688eaca2bf6fa85108b Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Tue, 13 Oct 2020 14:43:34 -0700 Subject: [PATCH] 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 --- .../docs/asciidoc/spring-boot-features.adoc | 2 +- .../boot/context/config/ConfigData.java | 6 +- .../context/config/ConfigDataEnvironment.java | 67 +-- .../ConfigDataEnvironmentContributor.java | 47 +- ...onmentContributorPlaceholdersResolver.java | 4 +- .../ConfigDataEnvironmentContributors.java | 18 +- .../ConfigDataEnvironmentPostProcessor.java | 4 +- .../context/config/ConfigDataImporter.java | 83 +++- .../boot/context/config/ConfigDataLoader.java | 28 +- .../context/config/ConfigDataLoaders.java | 72 +-- .../context/config/ConfigDataLocation.java | 123 ++++- .../config/ConfigDataLocationBindHandler.java | 68 +++ .../ConfigDataLocationNotFoundException.java | 84 +--- .../config/ConfigDataLocationResolver.java | 38 +- .../ConfigDataLocationResolverContext.java | 14 +- .../config/ConfigDataLocationResolvers.java | 84 +--- ...ion.java => ConfigDataNotFoundAction.java} | 16 +- .../config/ConfigDataNotFoundException.java | 44 ++ .../context/config/ConfigDataProperties.java | 51 +-- .../config/ConfigDataResolutionResult.java | 44 ++ ...ataLoader.java => ConfigDataResource.java} | 16 +- .../ConfigDataResourceNotFoundException.java | 148 ++++++ .../config/ConfigFileApplicationListener.java | 43 +- .../config/ConfigTreeConfigDataLoader.java | 9 +- .../ConfigTreeConfigDataLocationResolver.java | 12 +- ...java => ConfigTreeConfigDataResource.java} | 8 +- .../InactiveConfigDataAccessException.java | 14 +- .../InvalidConfigDataPropertyException.java | 12 +- .../config/OptionalConfigDataLocation.java | 72 --- .../ResourceConfigDataLocationResolver.java | 423 ------------------ .../config/StandardConfigDataLoader.java | 49 ++ .../StandardConfigDataLocationResolver.java | 343 ++++++++++++++ .../config/StandardConfigDataReference.java | 111 +++++ ...n.java => StandardConfigDataResource.java} | 46 +- ...nsupportedConfigDataLocationException.java | 10 +- .../main/resources/META-INF/spring.factories | 4 +- ...ConfigDataEnvironmentContributorTests.java | 51 ++- ...onfigDataEnvironmentContributorsTests.java | 83 ++-- ...essorBootstrapContextIntegrationTests.java | 3 +- ...ironmentPostProcessorIntegrationTests.java | 18 +- .../config/ConfigDataEnvironmentTests.java | 6 +- .../config/ConfigDataImporterTests.java | 77 ++-- .../context/config/ConfigDataLoaderTests.java | 8 +- .../config/ConfigDataLoadersTests.java | 57 ++- .../ConfigDataLocationBindHandlerTests.java | 121 +++++ ...figDataLocationNotFoundExceptionTests.java | 100 +---- .../ConfigDataLocationResolverTests.java | 10 +- .../ConfigDataLocationResolversTests.java | 91 ++-- .../config/ConfigDataLocationTests.java | 137 ++++++ .../config/ConfigDataPropertiesTests.java | 16 +- ...figDataResourceNotFoundExceptionTests.java | 167 +++++++ .../ConfigTreeConfigDataLoaderTests.java | 6 +- ...igTreeConfigDataLocationResolverTests.java | 10 +- ...=> ConfigTreeConfigDataResourceTests.java} | 16 +- ...nactiveConfigDataAccessExceptionTests.java | 20 +- ...validConfigDataPropertyExceptionTests.java | 18 +- .../OptionalConfigDataLocationTests.java | 83 ---- ...ava => StandardConfigDataLoaderTests.java} | 27 +- ...ndardConfigDataLocationResolverTests.java} | 111 ++--- ...a => StandardConfigDataResourceTests.java} | 40 +- .../config/TestConfigDataBootstrap.java | 30 +- ...ortedConfigDataLocationExceptionTests.java | 8 +- .../svn/SubversionConfigDataLoader.java | 8 +- .../SubversionConfigDataLocationResolver.java | 15 +- ...java => SubversionConfigDataResource.java} | 10 +- 65 files changed, 2078 insertions(+), 1486 deletions(-) create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationBindHandler.java rename spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/{ConfigDataLocationNotFoundAction.java => ConfigDataNotFoundAction.java} (63%) create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataNotFoundException.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataResolutionResult.java rename spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/{ResourceConfigDataLoader.java => ConfigDataResource.java} (59%) create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataResourceNotFoundException.java rename spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/{ConfigTreeConfigDataLocation.java => ConfigTreeConfigDataResource.java} (85%) delete mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/OptionalConfigDataLocation.java delete mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ResourceConfigDataLocationResolver.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataLoader.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataLocationResolver.java create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataReference.java rename spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/{ResourceConfigDataLocation.java => StandardConfigDataResource.java} (56%) create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationBindHandlerTests.java create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationTests.java create mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataResourceNotFoundExceptionTests.java rename spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/{ConfigTreeConfigDataLocationTests.java => ConfigTreeConfigDataResourceTests.java} (71%) delete mode 100644 spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/OptionalConfigDataLocationTests.java rename spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/{ResourceConfigDataLoaderTests.java => StandardConfigDataLoaderTests.java} (58%) rename spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/{ResourceConfigDataLocationResolverTests.java => StandardConfigDataLocationResolverTests.java} (64%) rename spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/{ResourceConfigDataLocationTests.java => StandardConfigDataResourceTests.java} (50%) rename spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/{SubversionConfigDataLocation.java => SubversionConfigDataResource.java} (81%) diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc index 20a807dcdf..9bd73c7142 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc @@ -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. -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. diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigData.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigData.java index 28ec4915e7..4d25015ef0 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigData.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigData.java @@ -29,9 +29,9 @@ import org.springframework.core.env.PropertySource; import org.springframework.util.Assert; /** - * Configuration data that has been loaded from an external {@link ConfigDataLocation - * location} and may ultimately contribute {@link PropertySource property sources} to - * Spring's {@link Environment}. + * Configuration data that has been loaded from a {@link ConfigDataResource} and may + * ultimately contribute {@link PropertySource property sources} to Spring's + * {@link Environment}. * * @author Phillip Webb * @author Madhura Bhave diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java index e6de58c8ae..1c4709c688 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java @@ -48,10 +48,10 @@ import org.springframework.util.StringUtils; * Wrapper around a {@link ConfigurableEnvironment} that can be used to import and apply * {@link ConfigData}. Configures the initial set of * {@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. *

- * The initial imports can be influenced via the {@link #LOCATION_PROPERTY}, - * {@value #ADDITIONAL_LOCATION_PROPERTY} and {@value #IMPORT_PROPERTY} properties. If not + * The initial locations can be influenced via the {@link #LOCATION_PROPERTY}, + * {@value #ADDITIONAL_LOCATION_PROPERTY} and {@value #IMPORT_PROPERTY} properties. If no * explicit properties are set, the {@link #DEFAULT_SEARCH_LOCATIONS} will be used. * * @author Phillip Webb @@ -76,28 +76,41 @@ class ConfigDataEnvironment { /** * Property used to determine what action to take when a - * {@code ConfigDataLocationNotFoundException} is thrown. - * @see ConfigDataLocationNotFoundAction + * {@code ConfigDataNotFoundAction} is thrown. + * @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. */ - static final String[] DEFAULT_SEARCH_LOCATIONS = { "optional:classpath:/", "optional:classpath:/config/", - "optional:file:./", "optional:file:./config/*/", "optional:file:./config/" }; - - private static final String[] EMPTY_LOCATIONS = new String[0]; + static final ConfigDataLocation[] DEFAULT_SEARCH_LOCATIONS; + static { + List locations = new ArrayList<>(); + 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 .of(Profiles.INCLUDE_PROFILES_PROPERTY_NAME); + private static final Bindable CONFIG_DATA_LOCATION_ARRAY = Bindable + .of(ConfigDataLocation[].class); + private static final Bindable> STRING_LIST = Bindable.listOf(String.class); private final DeferredLogFactory logFactory; private final Log logger; + private final ConfigDataNotFoundAction notFoundAction; + private final ConfigurableBootstrapContext bootstrapContext; private final ConfigurableEnvironment environment; @@ -122,25 +135,21 @@ class ConfigDataEnvironment { ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection additionalProfiles) { Binder binder = Binder.get(environment); UseLegacyConfigProcessingException.throwIfRequested(binder); - ConfigDataLocationNotFoundAction locationNotFoundAction = binder - .bind(ON_LOCATION_NOT_FOUND_PROPERTY, ConfigDataLocationNotFoundAction.class) - .orElse(ConfigDataLocationNotFoundAction.FAIL); this.logFactory = logFactory; this.logger = logFactory.getLog(getClass()); + this.notFoundAction = binder.bind(ON_NOT_FOUND_PROPERTY, ConfigDataNotFoundAction.class) + .orElse(ConfigDataNotFoundAction.FAIL); this.bootstrapContext = bootstrapContext; this.environment = environment; - this.resolvers = createConfigDataLocationResolvers(logFactory, bootstrapContext, locationNotFoundAction, binder, - resourceLoader); + this.resolvers = createConfigDataLocationResolvers(logFactory, bootstrapContext, binder, resourceLoader); this.additionalProfiles = additionalProfiles; - this.loaders = new ConfigDataLoaders(logFactory, bootstrapContext, locationNotFoundAction); + this.loaders = new ConfigDataLoaders(logFactory, bootstrapContext); this.contributors = createContributors(binder); } protected ConfigDataLocationResolvers createConfigDataLocationResolvers(DeferredLogFactory logFactory, - ConfigurableBootstrapContext bootstrapContext, ConfigDataLocationNotFoundAction locationNotFoundAction, - Binder binder, ResourceLoader resourceLoader) { - return new ConfigDataLocationResolvers(logFactory, bootstrapContext, locationNotFoundAction, binder, - resourceLoader); + ConfigurableBootstrapContext bootstrapContext, Binder binder, ResourceLoader resourceLoader) { + return new ConfigDataLocationResolvers(logFactory, bootstrapContext, binder, resourceLoader); } private ConfigDataEnvironmentContributors createContributors(Binder binder) { @@ -172,23 +181,26 @@ class ConfigDataEnvironment { private List getInitialImportContributors(Binder binder) { List initialContributors = new ArrayList<>(); + addInitialImportContributors(initialContributors, bindLocations(binder, IMPORT_PROPERTY, EMPTY_LOCATIONS)); addInitialImportContributors(initialContributors, - binder.bind(IMPORT_PROPERTY, String[].class).orElse(EMPTY_LOCATIONS)); + bindLocations(binder, ADDITIONAL_LOCATION_PROPERTY, EMPTY_LOCATIONS)); addInitialImportContributors(initialContributors, - binder.bind(ADDITIONAL_LOCATION_PROPERTY, String[].class).orElse(EMPTY_LOCATIONS)); - addInitialImportContributors(initialContributors, - binder.bind(LOCATION_PROPERTY, String[].class).orElse(DEFAULT_SEARCH_LOCATIONS)); + bindLocations(binder, LOCATION_PROPERTY, DEFAULT_SEARCH_LOCATIONS)); 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 initialContributors, - String[] locations) { + ConfigDataLocation[] locations) { for (int i = locations.length - 1; i >= 0; 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)); return ConfigDataEnvironmentContributor.ofInitialImport(location); } @@ -198,7 +210,8 @@ class ConfigDataEnvironment { * {@link Environment}. */ 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 .from(() -> this.contributors.getBinder(null, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE))); ConfigDataEnvironmentContributors contributors = processInitial(this.contributors, importer); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributor.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributor.java index 053b0f64f0..30c685e8c5 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributor.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributor.java @@ -28,7 +28,6 @@ import java.util.stream.StreamSupport; import org.springframework.boot.context.properties.bind.Binder; 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.PropertySource; @@ -52,7 +51,7 @@ import org.springframework.core.env.PropertySource; */ class ConfigDataEnvironmentContributor implements Iterable { - private final ConfigDataLocation location; + private final ConfigDataResource resource; private final PropertySource propertySource; @@ -69,7 +68,7 @@ class ConfigDataEnvironmentContributor implements Iterable propertySource, + ConfigDataEnvironmentContributor(Kind kind, ConfigDataResource resource, PropertySource propertySource, ConfigurationPropertySource configurationPropertySource, ConfigDataProperties properties, boolean ignoreImports, Map> children) { this.kind = kind; - this.location = location; + this.resource = resource; this.properties = properties; this.propertySource = propertySource; this.configurationPropertySource = configurationPropertySource; @@ -107,11 +106,11 @@ class ConfigDataEnvironmentContributor implements Iterable getImports() { + List getImports() { 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 * given phase. @@ -184,13 +179,19 @@ class ConfigDataEnvironmentContributor implements Iterable children) { Map> updatedChildren = new LinkedHashMap<>(this.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); } @@ -230,7 +231,7 @@ class ConfigDataEnvironmentContributor implements Iterable imports = Collections.singletonList(importLocation); + static ConfigDataEnvironmentContributor ofInitialImport(ConfigDataLocation initialImport) { + List imports = Collections.singletonList(initialImport); ConfigDataProperties properties = new ConfigDataProperties(imports, null); return new ConfigDataEnvironmentContributor(Kind.INITIAL_IMPORT, null, null, null, properties, false, null); } @@ -274,17 +275,17 @@ class ConfigDataEnvironmentContributor implements Iterable propertySource = configData.getPropertySources().get(propertySourceIndex); ConfigurationPropertySource configurationPropertySource = ConfigurationPropertySource.from(propertySource); boolean ignoreImports = configData.getOptions().contains(ConfigData.Option.IGNORE_IMPORTS); - return new ConfigDataEnvironmentContributor(Kind.UNBOUND_IMPORT, location, propertySource, + return new ConfigDataEnvironmentContributor(Kind.UNBOUND_IMPORT, resource, propertySource, configurationPropertySource, null, ignoreImports, null); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorPlaceholdersResolver.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorPlaceholdersResolver.java index 06ae47d9df..434d0915d4 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorPlaceholdersResolver.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorPlaceholdersResolver.java @@ -64,9 +64,9 @@ class ConfigDataEnvironmentContributorPlaceholdersResolver implements Placeholde Object value = (propertySource != null) ? propertySource.getProperty(placeholder) : null; if (value != null && !contributor.isActive(this.activationContext)) { if (this.failOnResolveFromInactiveContributor) { + ConfigDataResource resource = contributor.getResource(); Origin origin = OriginLookup.getOrigin(propertySource, placeholder); - throw new InactiveConfigDataAccessException(propertySource, contributor.getLocation(), placeholder, - origin); + throw new InactiveConfigDataAccessException(propertySource, resource, placeholder, origin); } value = null; } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributors.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributors.java index 053c2d0661..9267902275 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributors.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributors.java @@ -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.ConfigurationPropertySource; import org.springframework.boot.logging.DeferredLogFactory; -import org.springframework.boot.origin.Origin; import org.springframework.core.log.LogMessage; import org.springframework.util.ObjectUtils; @@ -114,12 +113,12 @@ class ConfigDataEnvironmentContributors implements Iterable imports = contributor.getImports(); + List imports = contributor.getImports(); this.logger.trace(LogMessage.format("Processing imports %s", imports)); - Map imported = importer.resolveAndLoad(activationContext, + Map imported = importer.resolveAndLoad(activationContext, locationResolverContext, loaderContext, imports); 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, asContributors(imported)); result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext, @@ -148,7 +147,7 @@ class ConfigDataEnvironmentContributors implements Iterable asContributors(Map imported) { + private List asContributors(Map imported) { List contributors = new ArrayList<>(imported.size() * 5); imported.forEach((location, data) -> { for (int i = data.getPropertySources().size() - 1; i >= 0; i--) { @@ -259,8 +258,8 @@ class ConfigDataEnvironmentContributors implements Iterable loadedLocations = new HashSet<>(); + private final ConfigDataNotFoundAction notFoundAction; + + private final Set loaded = new HashSet<>(); /** * 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 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.loaders = loaders; + this.notFoundAction = notFoundAction; } /** @@ -59,31 +73,70 @@ class ConfigDataImporter { * @param locations the locations to resolve * @return a map of the loaded locations and data */ - Map resolveAndLoad(ConfigDataActivationContext activationContext, + Map resolveAndLoad(ConfigDataActivationContext activationContext, ConfigDataLocationResolverContext locationResolverContext, ConfigDataLoaderContext loaderContext, - List locations) { + List locations) { try { Profiles profiles = (activationContext != null) ? activationContext.getProfiles() : null; - return load(loaderContext, this.resolvers.resolveAll(locationResolverContext, locations, profiles)); + List resolved = resolve(locationResolverContext, profiles, locations); + return load(loaderContext, resolved); } catch (IOException ex) { throw new IllegalStateException("IO error on loading imports from " + locations, ex); } } - private Map load(ConfigDataLoaderContext loaderContext, - List locations) throws IOException { - Map result = new LinkedHashMap<>(); - for (int i = locations.size() - 1; i >= 0; i--) { - ConfigDataLocation location = locations.get(i); - if (this.loadedLocations.add(location)) { - ConfigData loaded = this.loaders.load(loaderContext, location); - if (loaded != null) { - result.put(location, loaded); + private List resolve(ConfigDataLocationResolverContext locationResolverContext, + Profiles profiles, List locations) { + List resolved = new ArrayList<>(locations.size()); + for (ConfigDataLocation location : locations) { + resolved.addAll(resolve(locationResolverContext, profiles, location)); + } + return Collections.unmodifiableList(resolved); + } + + private List 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 load(ConfigDataLoaderContext loaderContext, + List candidates) throws IOException { + Map 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); } + 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; + } + } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoader.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoader.java index 8c3fa0f6b1..22e5d3a754 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoader.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoader.java @@ -25,8 +25,8 @@ import org.springframework.boot.BootstrapRegistry; import org.springframework.boot.ConfigurableBootstrapContext; /** - * Strategy class that can be used used to load {@link ConfigData} instances from a - * {@link ConfigDataLocation location}. Implementations should be added as a + * Strategy class that can be used used to load {@link ConfigData} for a given + * {@link ConfigDataResource}. Implementations should be added as a * {@code spring.factories} entries. The following constructor parameter types are * supported: *

*

- * Multiple loaders cannot claim the same location. + * Multiple loaders cannot claim the same resource. * - * @param the location type + * @param the resource type * @author Phillip Webb * @author Madhura Bhave * @since 2.4.0 */ -public interface ConfigDataLoader { +public interface ConfigDataLoader { /** - * 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 location the location to check. - * @return if the location is supported by this loader + * @param resource the resource to check. + * @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; } /** - * Load {@link ConfigData} for the given location. + * Load {@link ConfigData} for the given resource. * @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 * @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) - throws IOException, ConfigDataLocationNotFoundException; + ConfigData load(ConfigDataLoaderContext context, R resource) + throws IOException, ConfigDataResourceNotFoundException; } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoaders.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoaders.java index e7cc4e1fc5..d2c1c681e2 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoaders.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLoaders.java @@ -43,37 +43,28 @@ class ConfigDataLoaders { private final Log logger; - private final ConfigDataLocationNotFoundAction locationNotFoundAction; - private final List> loaders; - private final List> locationTypes; + private final List> resourceTypes; /** * Create a new {@link ConfigDataLoaders} instance. * @param logFactory the deferred log factory * @param bootstrapContext the bootstrap context - * @param locationNotFoundAction the action to take if a - * {@link ConfigDataLocationNotFoundException} is thrown */ - ConfigDataLoaders(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext, - ConfigDataLocationNotFoundAction locationNotFoundAction) { - this(logFactory, bootstrapContext, locationNotFoundAction, - SpringFactoriesLoader.loadFactoryNames(ConfigDataLoader.class, null)); + ConfigDataLoaders(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext) { + this(logFactory, bootstrapContext, SpringFactoriesLoader.loadFactoryNames(ConfigDataLoader.class, null)); } /** * Create a new {@link ConfigDataLoaders} instance. * @param logFactory the deferred log factory * @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 */ ConfigDataLoaders(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext, - ConfigDataLocationNotFoundAction locationNotFoundAction, List names) { + List names) { this.logger = logFactory.getLog(getClass()); - this.locationNotFoundAction = locationNotFoundAction; Instantiator> instantiator = new Instantiator<>(ConfigDataLoader.class, (availableParameters) -> { availableParameters.add(Log.class, logFactory::getLog); @@ -82,69 +73,52 @@ class ConfigDataLoaders { availableParameters.add(BootstrapRegistry.class, bootstrapContext); }); this.loaders = instantiator.instantiate(names); - this.locationTypes = getLocationTypes(this.loaders); + this.resourceTypes = getResourceTypes(this.loaders); } - private List> getLocationTypes(List> loaders) { - List> locationTypes = new ArrayList<>(loaders.size()); + private List> getResourceTypes(List> loaders) { + List> resourceTypes = new ArrayList<>(loaders.size()); 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(); } /** * Load {@link ConfigData} using the first appropriate {@link ConfigDataLoader}. - * @param the config data location type + * @param the resource type * @param context the loader context - * @param location the location to load + * @param resource the resource to load * @return the loaded {@link ConfigData} * @throws IOException on IO error */ - ConfigData load(ConfigDataLoaderContext context, L location) throws IOException { - boolean optional = location instanceof OptionalConfigDataLocation; - location = (!optional) ? location : OptionalConfigDataLocation.unwrap(location); - return load(context, optional, location); - } - - private ConfigData load(ConfigDataLoaderContext context, boolean optional, - L location) throws IOException { - ConfigDataLoader 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; - } + ConfigData load(ConfigDataLoaderContext context, R resource) throws IOException { + ConfigDataLoader loader = getLoader(context, resource); + this.logger.trace(LogMessage.of(() -> "Loading " + resource + " using loader " + loader.getClass().getName())); + return loader.load(context, resource); } @SuppressWarnings("unchecked") - private ConfigDataLoader getLoader(ConfigDataLoaderContext context, L location) { - ConfigDataLoader result = null; + private ConfigDataLoader getLoader(ConfigDataLoaderContext context, R resource) { + ConfigDataLoader result = null; for (int i = 0; i < this.loaders.size(); i++) { ConfigDataLoader candidate = this.loaders.get(i); - if (this.locationTypes.get(i).isInstance(location)) { - ConfigDataLoader loader = (ConfigDataLoader) candidate; - if (loader.isLoadable(context, location)) { + if (this.resourceTypes.get(i).isInstance(resource)) { + ConfigDataLoader loader = (ConfigDataLoader) candidate; + if (loader.isLoadable(context, resource)) { 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() + "]"); } result = loader; } } } - Assert.state(result != null, () -> "No loader found for location '" + location + "'"); + Assert.state(result != null, () -> "No loader found for resource '" + resource + "'"); return result; } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocation.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocation.java index 137c6d6384..01075a3c2f 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocation.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocation.java @@ -16,20 +16,131 @@ 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 valid {@link #equals(Object) equals}, {@link #hashCode() hashCode} and - * {@link #toString() toString} methods. + * A user specified location that can be {@link ConfigDataLocationResolver resolved} to + * one or {@link ConfigDataResource config data resources}. A {@link ConfigDataLocation} + * 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}. + *

+ * Locations can be mandatory or {@link #isOptional() optional}. Optional locations are + * prefixed with {@code optional:}. * * @author Phillip Webb - * @author Madhura Bhave * @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:"; + 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); + } + } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationBindHandler.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationBindHandler.java new file mode 100644 index 0000000000..f40cc6badd --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationBindHandler.java @@ -0,0 +1,68 @@ +/* + * 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 list = (List) 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); + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationNotFoundException.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationNotFoundException.java index ee52755f4e..33337bf589 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationNotFoundException.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationNotFoundException.java @@ -16,25 +16,23 @@ package org.springframework.boot.context.config; -import java.io.File; -import java.nio.file.Files; -import java.nio.file.Path; - -import org.springframework.core.io.Resource; +import org.springframework.boot.origin.Origin; +import org.springframework.util.Assert; /** - * Exception thrown when a config data location cannot be found. + * {@link ConfigDataNotFoundException} thrown when a {@link ConfigDataLocation} cannot be + * found. * * @author Phillip Webb * @since 2.4.0 */ -public class ConfigDataLocationNotFoundException extends ConfigDataException { +public class ConfigDataLocationNotFoundException extends ConfigDataNotFoundException { private final ConfigDataLocation location; /** * 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) { this(location, null); @@ -42,79 +40,39 @@ public class ConfigDataLocationNotFoundException extends ConfigDataException { /** * Create a new {@link ConfigDataLocationNotFoundException} instance. - * @param location the location that was not found - * @param cause the cause of the exception + * @param location the location that could not be found + * @param cause the exception cause */ public ConfigDataLocationNotFoundException(ConfigDataLocation location, Throwable cause) { - this(getMessage(location), location, cause); - } - - /** - * 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); + super(getMessage(location), cause); + Assert.notNull(location, "Location must not be null"); this.location = location; } /** * Return the location that could not be found. - * @return the location that could not be found. + * @return the location */ public ConfigDataLocation getLocation() { return this.location; } - private static String getMessage(ConfigDataLocation location) { - return "Config data location '" + location + "' does not exist"; - } - - /** - * 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 + public Origin getOrigin() { + return Origin.from(this.location); } - /** - * Throw a {@link ConfigDataLocationNotFoundException} if the specified {@link File} - * does not exist. - * @param location the location being checked - * @param file the file to check - */ - public static void throwIfDoesNotExist(ConfigDataLocation location, File file) { - throwIfDoesNotExist(location, file.exists()); + @Override + public String getReferenceDescription() { + return getReferenceDescription(this.location); } - /** - * Throw a {@link ConfigDataLocationNotFoundException} if the specified - * {@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 String getMessage(ConfigDataLocation location) { + return String.format("Config data %s cannot be found", getReferenceDescription(location)); } - private static void throwIfDoesNotExist(ConfigDataLocation location, boolean exists) { - if (!exists) { - throw new ConfigDataLocationNotFoundException(location); - } + private static String getReferenceDescription(ConfigDataLocation location) { + return String.format("location '%s'", location); } } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolver.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolver.java index a29e88196a..f51ae7c50c 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolver.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolver.java @@ -31,9 +31,10 @@ import org.springframework.core.env.Environment; import org.springframework.core.io.ResourceLoader; /** - * Strategy interface used to resolve {@link ConfigDataLocation locations} from a String - * based location address. Implementations should be added as a {@code spring.factories} - * entries. The following constructor parameter types are supported: + * Strategy interface used to resolve {@link ConfigDataLocation locations} into one or + * more {@link ConfigDataResource resources}. Implementations should be added as a + * {@code spring.factories} entries. The following constructor parameter types are + * supported: *
    *
  • {@link Log} - if the resolver needs deferred logging
  • *
  • {@link Binder} - if the resolver needs to obtain values from the initial @@ -47,12 +48,12 @@ import org.springframework.core.io.ResourceLoader; * Resolvers may implement {@link Ordered} or use the {@link Order @Order} annotation. The * first resolver that supports the given location will be used. * - * @param the location type + * @param the location type * @author Phillip Webb * @author Madhura Bhave * @since 2.4.0 */ -public interface ConfigDataLocationResolver { +public interface ConfigDataLocationResolver { /** * Returns if the specified location address can be resolved by this resolver. @@ -60,35 +61,34 @@ public interface ConfigDataLocationResolver { * @param location the location to check. * @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 location the location that should be resolved - * @param optional if the location is optional - * @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. + * @return a list of {@link ConfigDataResource resources} in ascending priority order. * @throws ConfigDataLocationNotFoundException on a non-optional location that cannot * be found + * @throws ConfigDataResourceNotFoundException if a resolved resource cannot be found */ - List resolve(ConfigDataLocationResolverContext context, String location, boolean optional) - throws ConfigDataLocationNotFoundException; + List resolve(ConfigDataLocationResolverContext context, ConfigDataLocation location) + throws ConfigDataLocationNotFoundException, ConfigDataResourceNotFoundException; /** - * Resolve a location string into one or more {@link ConfigDataLocation} instances - * based on available profiles. This method is called once profiles have been deduced - * from the contributed values. By default this method returns an empty list. + * Resolve a {@link ConfigDataLocation} into one or more {@link ConfigDataResource} + * instances based on available profiles. This method is called once profiles have + * been deduced from the contributed values. By default this method returns an empty + * list. * @param context the location resolver context * @param location the location that should be resolved - * @param optional if the location is optional * @param profiles profile information - * @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. + * @return a list of resolved locations in ascending priority order. * @throws ConfigDataLocationNotFoundException on a non-optional location that cannot * be found */ - default List resolveProfileSpecific(ConfigDataLocationResolverContext context, String location, boolean optional, + default List resolveProfileSpecific(ConfigDataLocationResolverContext context, ConfigDataLocation location, Profiles profiles) throws ConfigDataLocationNotFoundException { return Collections.emptyList(); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolverContext.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolverContext.java index fa8a8bedc4..2ba29d0b29 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolverContext.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolverContext.java @@ -19,7 +19,6 @@ package org.springframework.boot.context.config; import org.springframework.boot.ConfigurableBootstrapContext; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.env.EnvironmentPostProcessor; -import org.springframework.boot.origin.Origin; /** * Context provided to {@link ConfigDataLocationResolver} methods. @@ -38,11 +37,11 @@ public interface ConfigDataLocationResolverContext { Binder getBinder(); /** - * Provides access to the parent location that triggered the resolve or {@code null} - * if there is no available parent. + * Provides access to the parent {@link ConfigDataResource} that triggered the resolve + * or {@code null} if there is no available parent. * @return the parent location */ - ConfigDataLocation getParent(); + ConfigDataResource getParent(); /** * Provides access to the {@link ConfigurableBootstrapContext} shared across all @@ -51,11 +50,4 @@ public interface ConfigDataLocationResolverContext { */ 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); - } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolvers.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolvers.java index 6b07ffb14e..ab77cb3adb 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolvers.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationResolvers.java @@ -32,8 +32,6 @@ import org.springframework.boot.util.Instantiator; import org.springframework.core.env.Environment; import org.springframework.core.io.ResourceLoader; 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 @@ -44,24 +42,18 @@ import org.springframework.util.StringUtils; */ class ConfigDataLocationResolvers { - private final Log logger; - - private final ConfigDataLocationNotFoundAction locationNotFoundAction; - private final List> resolvers; /** * Create a new {@link ConfigDataLocationResolvers} instance. * @param logFactory a {@link DeferredLogFactory} used to inject {@link Log} instances * @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 resourceLoader {@link ResourceLoader} to load resource locations */ ConfigDataLocationResolvers(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext, - ConfigDataLocationNotFoundAction locationNotFoundAction, Binder binder, ResourceLoader resourceLoader) { - this(logFactory, bootstrapContext, locationNotFoundAction, binder, resourceLoader, + Binder binder, ResourceLoader resourceLoader) { + this(logFactory, bootstrapContext, binder, resourceLoader, SpringFactoriesLoader.loadFactoryNames(ConfigDataLocationResolver.class, null)); } @@ -69,17 +61,12 @@ class ConfigDataLocationResolvers { * Create a new {@link ConfigDataLocationResolvers} instance. * @param logFactory a {@link DeferredLogFactory} used to inject {@link Log} instances * @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 resourceLoader {@link ResourceLoader} to load resource locations * @param names the {@link ConfigDataLocationResolver} class names */ ConfigDataLocationResolvers(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext, - ConfigDataLocationNotFoundAction locationNotFoundAction, Binder binder, ResourceLoader resourceLoader, - List names) { - this.logger = logFactory.getLog(getClass()); - this.locationNotFoundAction = locationNotFoundAction; + Binder binder, ResourceLoader resourceLoader, List names) { Instantiator> instantiator = new Instantiator<>(ConfigDataLocationResolver.class, (availableParameters) -> { availableParameters.add(Log.class, logFactory::getLog); @@ -94,10 +81,10 @@ class ConfigDataLocationResolvers { private List> reorder(List> resolvers) { List> reordered = new ArrayList<>(resolvers.size()); - ResourceConfigDataLocationResolver resourceResolver = null; + StandardConfigDataLocationResolver resourceResolver = null; for (ConfigDataLocationResolver resolver : resolvers) { - if (resolver instanceof ResourceConfigDataLocationResolver) { - resourceResolver = (ResourceConfigDataLocationResolver) resolver; + if (resolver instanceof StandardConfigDataLocationResolver) { + resourceResolver = (StandardConfigDataLocationResolver) resolver; } else { reordered.add(resolver); @@ -109,67 +96,38 @@ class ConfigDataLocationResolvers { return Collections.unmodifiableList(reordered); } - /** - * 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 resolveAll(ConfigDataLocationResolverContext context, List locations, + List resolve(ConfigDataLocationResolverContext context, ConfigDataLocation location, Profiles profiles) { - List resolved = new ArrayList<>(locations.size()); - for (String location : locations) { - resolved.addAll(resolveAll(context, location, profiles)); - } - return resolved; - } - - private List 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)) { + if (location == null) { return Collections.emptyList(); } for (ConfigDataLocationResolver resolver : getResolvers()) { if (resolver.isResolvable(context, location)) { - return resolve(resolver, context, optional, location, profiles); + return resolve(resolver, context, location, profiles); } } throw new UnsupportedConfigDataLocationException(location); } - private List resolve(ConfigDataLocationResolver resolver, - ConfigDataLocationResolverContext context, boolean optional, String location, Profiles profiles) { - List resolved = resolve(location, optional, - () -> resolver.resolve(context, location, optional)); + private List resolve(ConfigDataLocationResolver resolver, + ConfigDataLocationResolverContext context, ConfigDataLocation location, Profiles profiles) { + List resolved = resolve(location, () -> resolver.resolve(context, location)); if (profiles == null) { return resolved; } - List profileSpecific = resolve(location, optional, - () -> resolver.resolveProfileSpecific(context, location, optional, profiles)); + List profileSpecific = resolve(location, + () -> resolver.resolveProfileSpecific(context, location, profiles)); return merge(resolved, profileSpecific); } - private List resolve(String location, boolean optional, - Supplier> resolveAction) { - try { - List resolved = nonNullList(resolveAction.get()); - if (!resolved.isEmpty() && optional) { - resolved = OptionalConfigDataLocation.wrapAll(resolved); - } - 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(); + private List resolve(ConfigDataLocation location, + Supplier> resolveAction) { + List resources = nonNullList(resolveAction.get()); + List resolved = new ArrayList<>(resources.size()); + for (ConfigDataResource resource : resources) { + resolved.add(new ConfigDataResolutionResult(location, resource)); } + return resolved; } @SuppressWarnings("unchecked") diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationNotFoundAction.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataNotFoundAction.java similarity index 63% rename from spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationNotFoundAction.java rename to spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataNotFoundAction.java index b4a3392120..74f92d14a4 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationNotFoundAction.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataNotFoundAction.java @@ -21,12 +21,12 @@ import org.apache.commons.logging.Log; 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 * @since 2.4.0 */ -public enum ConfigDataLocationNotFoundAction { +public enum ConfigDataNotFoundAction { /** * Throw the exception to fail startup. @@ -34,7 +34,7 @@ public enum ConfigDataLocationNotFoundAction { FAIL { @Override - void handle(Log logger, Object location, ConfigDataLocationNotFoundException ex) { + void handle(Log logger, ConfigDataNotFoundException ex) { throw ex; } @@ -46,19 +46,17 @@ public enum ConfigDataLocationNotFoundAction { IGNORE { @Override - void handle(Log logger, Object location, ConfigDataLocationNotFoundException ex) { - logger.trace(LogMessage.format("Ignoring missing resource from location %s", location)); + void handle(Log logger, ConfigDataNotFoundException ex) { + logger.trace(LogMessage.format("Ignoring missing config data %s", ex.getReferenceDescription())); } }; /** * Handle the given exception. - * @param logger the logger used for output - * @param location the location being checked (a {@link ConfigDataLocation} or - * {@code String}) + * @param logger the logger used for output {@code ConfigDataLocation}) * @param ex the exception to handle */ - abstract void handle(Log logger, Object location, ConfigDataLocationNotFoundException ex); + abstract void handle(Log logger, ConfigDataNotFoundException ex); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataNotFoundException.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataNotFoundException.java new file mode 100644 index 0000000000..2accaeb615 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataNotFoundException.java @@ -0,0 +1,44 @@ +/* + * 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(); + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataProperties.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataProperties.java index c275e77452..6ece358551 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataProperties.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataProperties.java @@ -16,11 +16,8 @@ package org.springframework.boot.context.config; -import java.util.ArrayList; import java.util.Collections; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import java.util.function.Predicate; import org.springframework.boot.cloud.CloudPlatform; @@ -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.Bindable; 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.source.ConfigurationProperty; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; -import org.springframework.boot.origin.Origin; import org.springframework.util.ObjectUtils; /** @@ -45,8 +40,6 @@ class ConfigDataProperties { 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 .of("spring.profiles"); @@ -54,57 +47,33 @@ class ConfigDataProperties { private static final Bindable BINDABLE_STRING_ARRAY = Bindable.of(String[].class); - private final List imports; + private final List imports; private final Activate activate; - private final Map boundProperties; - /** * Create a new {@link ConfigDataProperties} instance. * @param imports the imports requested * @param activate the activate properties */ - ConfigDataProperties(@Name("import") List imports, Activate activate) { + ConfigDataProperties(@Name("import") List imports, Activate activate) { this(imports, activate, Collections.emptyList()); } - private ConfigDataProperties(List imports, Activate activate, List boundProperties) { + private ConfigDataProperties(List imports, Activate activate, + List boundProperties) { this.imports = (imports != null) ? imports : Collections.emptyList(); this.activate = activate; - this.boundProperties = mapByName(boundProperties); - } - - private Map mapByName( - List boundProperties) { - Map result = new LinkedHashMap<>(); - boundProperties.forEach((property) -> result.put(property.getName(), property)); - return Collections.unmodifiableMap(result); } /** * Return any additional imports requested. * @return the requested imports */ - List getImports() { + List getImports() { 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 * is active for the given activation context. @@ -130,10 +99,6 @@ class ConfigDataProperties { return new ConfigDataProperties(this.imports, new Activate(this.activate.onCloudPlatform, legacyProfiles)); } - ConfigDataProperties withBoundProperties(List boundProperties) { - return new ConfigDataProperties(this.imports, this.activate, boundProperties); - } - /** * Factory method used to create {@link ConfigDataProperties} from the given * {@link Binder}. @@ -144,16 +109,14 @@ class ConfigDataProperties { LegacyProfilesBindHandler legacyProfilesBindHandler = new LegacyProfilesBindHandler(); String[] legacyProfiles = binder.bind(LEGACY_PROFILES_NAME, BINDABLE_STRING_ARRAY, legacyProfilesBindHandler) .orElse(null); - List boundProperties = new ArrayList<>(); - ConfigDataProperties properties = binder - .bind(NAME, BINDABLE_PROPERTIES, new BoundPropertiesTrackingBindHandler(boundProperties::add)) + ConfigDataProperties properties = binder.bind(NAME, BINDABLE_PROPERTIES, new ConfigDataLocationBindHandler()) .orElse(null); if (!ObjectUtils.isEmpty(legacyProfiles)) { properties = (properties != null) ? properties.withLegacyProfiles(legacyProfiles, legacyProfilesBindHandler.getProperty()) : new ConfigDataProperties(null, new Activate(null, legacyProfiles)); } - return (properties != null) ? properties.withBoundProperties(boundProperties) : null; + return properties; } /** diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataResolutionResult.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataResolutionResult.java new file mode 100644 index 0000000000..09c7d7aed3 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataResolutionResult.java @@ -0,0 +1,44 @@ +/* + * 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; + +/** + * Result returned from {@link ConfigDataLocationResolvers} containing both the + * {@link ConfigDataResource} and the original {@link ConfigDataLocation}. + * + * @author Phillip Webb + */ +class ConfigDataResolutionResult { + + private final ConfigDataLocation location; + + private final ConfigDataResource resource; + + ConfigDataResolutionResult(ConfigDataLocation location, ConfigDataResource resource) { + this.location = location; + this.resource = resource; + } + + ConfigDataLocation getLocation() { + return this.location; + } + + ConfigDataResource getResource() { + return this.resource; + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ResourceConfigDataLoader.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataResource.java similarity index 59% rename from spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ResourceConfigDataLoader.java rename to spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataResource.java index 743cfe7b95..e16dbd4b0b 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ResourceConfigDataLoader.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataResource.java @@ -16,23 +16,15 @@ 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 Madhura Bhave * @since 2.4.0 */ -public class ResourceConfigDataLoader implements ConfigDataLoader { - - @Override - public ConfigData load(ConfigDataLoaderContext context, ResourceConfigDataLocation location) throws IOException { - ConfigDataLocationNotFoundException.throwIfDoesNotExist(location, location.getResource()); - return new ConfigData(location.load()); - } +public abstract class ConfigDataResource { } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataResourceNotFoundException.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataResourceNotFoundException.java new file mode 100644 index 0000000000..523b464238 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataResourceNotFoundException.java @@ -0,0 +1,148 @@ +/* + * 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); + } + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java index 52cf90550f..46a3207142 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java @@ -426,25 +426,13 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor, private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) { getSearchLocations().forEach((location) -> { + String nonOptionalLocation = ConfigDataLocation.of(location).getValue(); boolean isDirectory = location.endsWith("/"); Set 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, DocumentConsumer consumer) { if (!StringUtils.hasText(name)) { @@ -552,9 +540,9 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor, } } - private String getLocationName(String location, Resource resource) { - if (!location.contains("*")) { - return location; + private String getLocationName(String locationReference, Resource resource) { + if (!locationReference.contains("*")) { + return locationReference; } if (resource instanceof FileSystemResource) { return ((FileSystemResource) resource).getPath(); @@ -562,24 +550,24 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor, return resource.getDescription(); } - private Resource[] getResources(String location) { + private Resource[] getResources(String locationReference) { try { - if (location.contains("*")) { - return getResourcesFromPatternLocation(location); + if (locationReference.contains("*")) { + return getResourcesFromPatternLocationReference(locationReference); } - return new Resource[] { this.resourceLoader.getResource(location) }; + return new Resource[] { this.resourceLoader.getResource(locationReference) }; } catch (Exception ex) { return EMPTY_RESOURCES; } } - private Resource[] getResourcesFromPatternLocation(String location) throws IOException { - String directoryPath = location.substring(0, location.indexOf("*/")); + private Resource[] getResourcesFromPatternLocationReference(String locationReference) throws IOException { + String directoryPath = locationReference.substring(0, locationReference.indexOf("*/")); Resource resource = this.resourceLoader.getResource(directoryPath); File[] files = resource.getFile().listFiles(File::isDirectory); if (files != null) { - String fileName = location.substring(location.lastIndexOf("/") + 1); + String fileName = locationReference.substring(locationReference.lastIndexOf("/") + 1); Arrays.sort(files, FILE_COMPARATOR); return Arrays.stream(files).map((file) -> file.listFiles((dir, name) -> name.equals(fileName))) .filter(Objects::nonNull).flatMap((Function>) Arrays::stream) @@ -622,7 +610,8 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor, }).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); try { if (resource != null) { @@ -630,12 +619,12 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor, result.append("'"); result.append(uri); result.append("' ("); - result.append(location); + result.append(locationReference); result.append(")"); } } catch (IOException ex) { - result.append(location); + result.append(locationReference); } if (profile != null) { result.append(" for profile "); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigTreeConfigDataLoader.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigTreeConfigDataLoader.java index cc01098330..254a02a021 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigTreeConfigDataLoader.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigTreeConfigDataLoader.java @@ -29,12 +29,13 @@ import org.springframework.boot.env.ConfigTreePropertySource; * @author Phillip Webb * @since 2.4.0 */ -public class ConfigTreeConfigDataLoader implements ConfigDataLoader { +public class ConfigTreeConfigDataLoader implements ConfigDataLoader { @Override - public ConfigData load(ConfigDataLoaderContext context, ConfigTreeConfigDataLocation location) throws IOException { - ConfigDataLocationNotFoundException.throwIfDoesNotExist(location, location.getPath()); - Path path = location.getPath(); + public ConfigData load(ConfigDataLoaderContext context, ConfigTreeConfigDataResource resource) + throws IOException, ConfigDataResourceNotFoundException { + Path path = resource.getPath(); + ConfigDataResourceNotFoundException.throwIfDoesNotExist(resource, path); String name = "Config tree '" + path + "'"; ConfigTreePropertySource source = new ConfigTreePropertySource(name, path); return new ConfigData(Collections.singletonList(source)); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigTreeConfigDataLocationResolver.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigTreeConfigDataLocationResolver.java index e507bf6aa3..9810491c9c 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigTreeConfigDataLocationResolver.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigTreeConfigDataLocationResolver.java @@ -26,19 +26,19 @@ import java.util.List; * @author Phillip Webb * @since 2.4.0 */ -public class ConfigTreeConfigDataLocationResolver implements ConfigDataLocationResolver { +public class ConfigTreeConfigDataLocationResolver implements ConfigDataLocationResolver { private static final String PREFIX = "configtree:"; @Override - public boolean isResolvable(ConfigDataLocationResolverContext context, String location) { - return location.startsWith(PREFIX); + public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location) { + return location.hasPrefix(PREFIX); } @Override - public List resolve(ConfigDataLocationResolverContext context, String location, - boolean optional) { - ConfigTreeConfigDataLocation resolved = new ConfigTreeConfigDataLocation(location.substring(PREFIX.length())); + public List resolve(ConfigDataLocationResolverContext context, + ConfigDataLocation location) { + ConfigTreeConfigDataResource resolved = new ConfigTreeConfigDataResource(location.getNonPrefixedValue(PREFIX)); return Collections.singletonList(resolved); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigTreeConfigDataLocation.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigTreeConfigDataResource.java similarity index 85% rename from spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigTreeConfigDataLocation.java rename to spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigTreeConfigDataResource.java index 9e9a135a4d..4cb6eeb9b5 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigTreeConfigDataLocation.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigTreeConfigDataResource.java @@ -24,18 +24,18 @@ import org.springframework.boot.env.ConfigTreePropertySource; 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 Phillip Webb * @since 2.4.0 * @see ConfigTreePropertySource */ -public class ConfigTreeConfigDataLocation extends ConfigDataLocation { +public class ConfigTreeConfigDataResource extends ConfigDataResource { private final Path path; - ConfigTreeConfigDataLocation(String path) { + ConfigTreeConfigDataResource(String path) { Assert.notNull(path, "Path must not be null"); this.path = Paths.get(path).toAbsolutePath(); } @@ -52,7 +52,7 @@ public class ConfigTreeConfigDataLocation extends ConfigDataLocation { if (obj == null || getClass() != obj.getClass()) { return false; } - ConfigTreeConfigDataLocation other = (ConfigTreeConfigDataLocation) obj; + ConfigTreeConfigDataResource other = (ConfigTreeConfigDataResource) obj; return Objects.equals(this.path, other.path); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/InactiveConfigDataAccessException.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/InactiveConfigDataAccessException.java index 6b535117b5..143e68932b 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/InactiveConfigDataAccessException.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/InactiveConfigDataAccessException.java @@ -35,7 +35,7 @@ public class InactiveConfigDataAccessException extends ConfigDataException { private final PropertySource propertySource; - private final ConfigDataLocation location; + private final ConfigDataResource location; private final String propertyName; @@ -44,12 +44,12 @@ public class InactiveConfigDataAccessException extends ConfigDataException { /** * Create a new {@link InactiveConfigDataAccessException} instance. * @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}. * @param propertyName the name of the property * @param origin the origin or the property or {@code null} */ - InactiveConfigDataAccessException(PropertySource propertySource, ConfigDataLocation location, + InactiveConfigDataAccessException(PropertySource propertySource, ConfigDataResource location, String propertyName, Origin origin) { super(getMessage(propertySource, location, propertyName, origin), null); this.propertySource = propertySource; @@ -58,7 +58,7 @@ public class InactiveConfigDataAccessException extends ConfigDataException { 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) { StringBuilder message = new StringBuilder("Inactive property source '"); message.append(propertySource.getName()); @@ -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}. * @return the config data location or {@code null} */ - public ConfigDataLocation getLocation() { + public ConfigDataResource getLocation() { return this.location; } @@ -121,7 +121,7 @@ public class InactiveConfigDataAccessException extends ConfigDataException { ConfigurationProperty property = (source != null) ? source.getConfigurationProperty(name) : null; if (property != null) { PropertySource propertySource = contributor.getPropertySource(); - ConfigDataLocation location = contributor.getLocation(); + ConfigDataResource location = contributor.getResource(); throw new InactiveConfigDataAccessException(propertySource, location, name.toString(), property.getOrigin()); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/InvalidConfigDataPropertyException.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/InvalidConfigDataPropertyException.java index b44775de0a..fc77435d70 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/InvalidConfigDataPropertyException.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/InvalidConfigDataPropertyException.java @@ -47,10 +47,10 @@ public class InvalidConfigDataPropertyException extends ConfigDataException { private final ConfigurationPropertyName replacement; - private final ConfigDataLocation location; + private final ConfigDataResource location; InvalidConfigDataPropertyException(ConfigurationProperty property, ConfigurationPropertyName replacement, - ConfigDataLocation location) { + ConfigDataResource location) { super(getMessage(property, replacement, location), null); this.property = property; this.replacement = replacement; @@ -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}. * @return the config data location or {@code null} */ - public ConfigDataLocation getLocation() { + public ConfigDataResource getLocation() { return this.location; } @@ -97,14 +97,14 @@ public class InvalidConfigDataPropertyException extends ConfigDataException { WARNING.forEach((invalid, replacement) -> { ConfigurationProperty property = propertySource.getConfigurationProperty(invalid); 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, - ConfigDataLocation location) { + ConfigDataResource location) { StringBuilder message = new StringBuilder("Property '"); message.append(property.getName()); if (location != null) { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/OptionalConfigDataLocation.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/OptionalConfigDataLocation.java deleted file mode 100644 index 3954f65051..0000000000 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/OptionalConfigDataLocation.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2012-2020 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.context.config; - -import java.util.ArrayList; -import java.util.List; - -/** - * {@link ConfigDataLocation} wrapper used to indicate that it's optional. - * - * @author Phillip Webb - */ -class OptionalConfigDataLocation extends ConfigDataLocation { - - private ConfigDataLocation location; - - OptionalConfigDataLocation(ConfigDataLocation location) { - this.location = location; - } - - ConfigDataLocation getLocation() { - return this.location; - } - - @Override - public boolean equals(Object obj) { - 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 wrapAll(List locations) { - List wrapped = new ArrayList<>(locations.size()); - locations.forEach((location) -> wrapped.add(new OptionalConfigDataLocation(location))); - return wrapped; - } - - @SuppressWarnings("unchecked") - static L unwrap(ConfigDataLocation wrapped) { - return (L) ((OptionalConfigDataLocation) wrapped).getLocation(); - } - -} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ResourceConfigDataLocationResolver.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ResourceConfigDataLocationResolver.java deleted file mode 100644 index c03b9d12e5..0000000000 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ResourceConfigDataLocationResolver.java +++ /dev/null @@ -1,423 +0,0 @@ -/* - * 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.FilenameFilter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.apache.commons.logging.Log; - -import org.springframework.boot.context.properties.bind.Binder; -import org.springframework.boot.env.PropertySourceLoader; -import org.springframework.boot.origin.Origin; -import org.springframework.core.Ordered; -import org.springframework.core.env.Environment; -import org.springframework.core.io.FileSystemResource; -import org.springframework.core.io.Resource; -import org.springframework.core.io.ResourceLoader; -import org.springframework.core.io.support.ResourcePatternResolver; -import org.springframework.core.io.support.SpringFactoriesLoader; -import org.springframework.core.log.LogMessage; -import org.springframework.util.Assert; -import org.springframework.util.ResourceUtils; -import org.springframework.util.StringUtils; - -/** - * {@link ConfigDataLocationResolver} for standard locations. - * - * @author Madhura Bhave - * @author Phillip Webb - * @since 2.4.0 - */ -public class ResourceConfigDataLocationResolver - implements ConfigDataLocationResolver, Ordered { - - private static final String PREFIX = "resource:"; - - static final String CONFIG_NAME_PROPERTY = "spring.config.name"; - - private static final String[] DEFAULT_CONFIG_NAMES = { "application" }; - - private static final Resource[] EMPTY_RESOURCES = {}; - - private static final Comparator FILE_COMPARATOR = Comparator.comparing(File::getAbsolutePath); - - private static final Pattern URL_PREFIX = Pattern.compile("^([a-zA-Z][a-zA-Z0-9*]*?:)(.*$)"); - - private static final Pattern EXTENSION_HINT_PATTERN = Pattern.compile("^(.*)\\[(\\.\\w+)\\](?!\\[)$"); - - private static final String NO_PROFILE = null; - - private final Log logger; - - private final List propertySourceLoaders; - - private final String[] configNames; - - private final ResourceLoader resourceLoader; - - /** - * Create a new {@link ResourceConfigDataLocationResolver} instance. - * @param logger the logger to use - * @param binder a binder backed by the initial {@link Environment} - * @param resourceLoader a {@link ResourceLoader} used to load resources - */ - public ResourceConfigDataLocationResolver(Log logger, Binder binder, ResourceLoader resourceLoader) { - this.logger = logger; - this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class, - getClass().getClassLoader()); - this.configNames = getConfigNames(binder); - this.resourceLoader = resourceLoader; - } - - private String[] getConfigNames(Binder binder) { - String[] configNames = binder.bind(CONFIG_NAME_PROPERTY, String[].class).orElse(DEFAULT_CONFIG_NAMES); - for (String configName : configNames) { - validateConfigName(configName); - } - return configNames; - } - - private void validateConfigName(String name) { - Assert.state(!name.contains("*"), () -> "Config name '" + name + "' cannot contain '*'"); - } - - @Override - public int getOrder() { - return Ordered.LOWEST_PRECEDENCE; - } - - @Override - public boolean isResolvable(ConfigDataLocationResolverContext context, String location) { - return true; - } - - @Override - public List resolve(ConfigDataLocationResolverContext context, String location, - boolean optional) { - return resolve(location, getResolvables(context, location, optional)); - } - - @Override - public List resolveProfileSpecific(ConfigDataLocationResolverContext context, - String location, boolean optional, Profiles profiles) { - return resolve(location, getProfileSpecificResolvables(context, location, optional, profiles)); - } - - private Set getResolvables(ConfigDataLocationResolverContext context, String location, - boolean optional) { - Origin origin = context.getLocationOrigin(location); - String resourceLocation = getResourceLocation(context, location); - try { - if (isDirectoryLocation(resourceLocation)) { - return getResolvablesForDirectory(resourceLocation, optional, NO_PROFILE, origin); - } - return getResolvablesForFile(resourceLocation, optional, NO_PROFILE, origin); - } - catch (RuntimeException ex) { - throw new IllegalStateException("Unable to load config data from '" + location + "'", ex); - } - } - - private Set getProfileSpecificResolvables(ConfigDataLocationResolverContext context, String location, - boolean optional, Profiles profiles) { - Origin origin = context.getLocationOrigin(location); - Set resolvables = new LinkedHashSet<>(); - String resourceLocation = getResourceLocation(context, location); - for (String profile : profiles) { - resolvables.addAll(getResolvables(resourceLocation, optional, profile, origin)); - } - return resolvables; - } - - private String getResourceLocation(ConfigDataLocationResolverContext context, String location) { - String resourceLocation = (location.startsWith(PREFIX)) ? location.substring(PREFIX.length()) : location; - boolean isAbsolute = resourceLocation.startsWith("/") || URL_PREFIX.matcher(resourceLocation).matches(); - if (isAbsolute) { - return resourceLocation; - } - ConfigDataLocation parent = context.getParent(); - if (parent instanceof ResourceConfigDataLocation) { - String parentLocation = ((ResourceConfigDataLocation) parent).getLocation(); - String parentDirectory = parentLocation.substring(0, parentLocation.lastIndexOf("/") + 1); - return parentDirectory + resourceLocation; - } - return resourceLocation; - } - - private Set getResolvables(String resourceLocation, boolean optional, String profile, Origin origin) { - if (isDirectoryLocation(resourceLocation)) { - return getResolvablesForDirectory(resourceLocation, optional, profile, origin); - } - return getResolvablesForFile(resourceLocation, optional, profile, origin); - } - - private Set getResolvablesForDirectory(String directoryLocation, boolean optional, String profile, - Origin origin) { - Set resolvables = new LinkedHashSet<>(); - for (String name : this.configNames) { - String rootLocation = directoryLocation + name; - for (PropertySourceLoader loader : this.propertySourceLoaders) { - for (String extension : loader.getFileExtensions()) { - Resolvable resolvable = new Resolvable(directoryLocation, rootLocation, optional, profile, - extension, origin, loader); - resolvables.add(resolvable); - } - } - } - return resolvables; - } - - private Set getResolvablesForFile(String fileLocation, boolean optional, String profile, - Origin origin) { - Matcher extensionHintMatcher = EXTENSION_HINT_PATTERN.matcher(fileLocation); - boolean extensionHintLocation = extensionHintMatcher.matches(); - if (extensionHintLocation) { - fileLocation = extensionHintMatcher.group(1) + extensionHintMatcher.group(2); - } - for (PropertySourceLoader loader : this.propertySourceLoaders) { - String extension = getLoadableFileExtension(loader, fileLocation); - if (extension != null) { - String root = fileLocation.substring(0, fileLocation.length() - extension.length() - 1); - return Collections.singleton(new Resolvable(null, root, optional, profile, - (!extensionHintLocation) ? extension : null, origin, loader)); - } - } - throw new IllegalStateException("File extension is not known to any PropertySourceLoader. " - + "If the location is meant to reference a directory, it must end in '/'"); - } - - private String getLoadableFileExtension(PropertySourceLoader loader, String resourceLocation) { - for (String fileExtension : loader.getFileExtensions()) { - if (StringUtils.endsWithIgnoreCase(resourceLocation, fileExtension)) { - return fileExtension; - } - } - return null; - } - - private boolean isDirectoryLocation(String resourceLocation) { - return resourceLocation.endsWith("/"); - } - - private List resolve(String location, Set resolvables) { - List resolved = new ArrayList<>(); - for (Resolvable resolvable : resolvables) { - resolved.addAll(resolve(location, resolvable)); - } - if (resolved.isEmpty()) { - assertNonOptionalDirectories(location, resolvables); - } - return resolved; - } - - private void assertNonOptionalDirectories(String location, Set resolvables) { - for (Resolvable resolvable : resolvables) { - if (resolvable.isNonOptionalDirectory()) { - Resource resource = loadResource(resolvable.getDirectory()); - ResourceConfigDataLocation resourceLocation = createConfigResourceLocation(location, resolvable, - resource); - ConfigDataLocationNotFoundException.throwIfDoesNotExist(resourceLocation, resource); - } - } - } - - private List resolve(String location, Resolvable resolvable) { - if (!resolvable.isPatternLocation()) { - return resolveNonPattern(location, resolvable); - } - return resolvePattern(location, resolvable); - } - - private List resolveNonPattern(String location, Resolvable resolvable) { - Resource resource = loadResource(resolvable.getResourceLocation()); - if (!resource.exists() && resolvable.isSkippable()) { - logSkippingResource(resolvable); - return Collections.emptyList(); - } - return Collections.singletonList(createConfigResourceLocation(location, resolvable, resource)); - } - - private List resolvePattern(String location, Resolvable resolvable) { - validatePatternLocation(resolvable.getResourceLocation()); - List resolved = new ArrayList<>(); - for (Resource resource : getResourcesFromResourceLocationPattern(resolvable.getResourceLocation())) { - if (!resource.exists() && resolvable.isSkippable()) { - logSkippingResource(resolvable); - } - else { - resolved.add(createConfigResourceLocation(location, resolvable, resource)); - } - } - return resolved; - } - - private void logSkippingResource(Resolvable resolvable) { - this.logger.trace(LogMessage.format("Skipping missing resource location %s", resolvable.getResourceLocation())); - } - - private ResourceConfigDataLocation createConfigResourceLocation(String location, Resolvable resolvable, - Resource resource) { - String name = String.format("Resource config '%s' imported via location \"%s\"", - resolvable.getResourceLocation(), location); - return new ResourceConfigDataLocation(name, resource, resolvable.getOrigin(), resolvable.getLoader()); - } - - private void validatePatternLocation(String resourceLocation) { - Assert.state(!resourceLocation.startsWith(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX), - "Classpath wildcard patterns cannot be used as a search location"); - Assert.state(StringUtils.countOccurrencesOf(resourceLocation, "*") == 1, - () -> "Search location '" + resourceLocation + "' cannot contain multiple wildcards"); - String directoryPath = resourceLocation.substring(0, resourceLocation.lastIndexOf("/") + 1); - Assert.state(directoryPath.endsWith("*/"), - () -> "Search location '" + resourceLocation + "' must end with '*/'"); - } - - private Resource[] getResourcesFromResourceLocationPattern(String resourceLocationPattern) { - String directoryPath = resourceLocationPattern.substring(0, resourceLocationPattern.indexOf("*/")); - String fileName = resourceLocationPattern.substring(resourceLocationPattern.lastIndexOf("/") + 1); - Resource directoryResource = loadResource(directoryPath); - if (!directoryResource.exists()) { - return new Resource[] { directoryResource }; - } - File directory = getDirectory(resourceLocationPattern, directoryResource); - File[] subDirectories = directory.listFiles(File::isDirectory); - if (subDirectories == null) { - return EMPTY_RESOURCES; - } - Arrays.sort(subDirectories, FILE_COMPARATOR); - List resources = new ArrayList<>(); - FilenameFilter filter = (dir, name) -> name.equals(fileName); - for (File subDirectory : subDirectories) { - File[] files = subDirectory.listFiles(filter); - if (files != null) { - Arrays.stream(files).map(FileSystemResource::new).forEach(resources::add); - } - } - return resources.toArray(EMPTY_RESOURCES); - } - - private Resource loadResource(String location) { - location = StringUtils.cleanPath(location); - if (!ResourceUtils.isUrl(location)) { - location = ResourceUtils.FILE_URL_PREFIX + location; - } - return this.resourceLoader.getResource(location); - } - - private File getDirectory(String patternLocation, Resource resource) { - try { - File directory = resource.getFile(); - Assert.state(directory.isDirectory(), () -> "'" + directory + "' is not a directory"); - return directory; - } - catch (Exception ex) { - throw new IllegalStateException( - "Unable to load config data resource from pattern '" + patternLocation + "'", ex); - } - } - - /** - * 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; - } - - } - -} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataLoader.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataLoader.java new file mode 100644 index 0000000000..bd8f730dfc --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataLoader.java @@ -0,0 +1,49 @@ +/* + * 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 { + + @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> propertySources = reference.getPropertySourceLoader().load(name, originTrackedResource); + return new ConfigData(propertySources); + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataLocationResolver.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataLocationResolver.java new file mode 100644 index 0000000000..3b0cbabc01 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataLocationResolver.java @@ -0,0 +1,343 @@ +/* + * 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.FilenameFilter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.logging.Log; + +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.boot.env.PropertySourceLoader; +import org.springframework.core.Ordered; +import org.springframework.core.env.Environment; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.core.io.support.SpringFactoriesLoader; +import org.springframework.core.log.LogMessage; +import org.springframework.util.Assert; +import org.springframework.util.ResourceUtils; +import org.springframework.util.StringUtils; + +/** + * {@link ConfigDataLocationResolver} for standard locations. + * + * @author Madhura Bhave + * @author Phillip Webb + * @since 2.4.0 + */ +public class StandardConfigDataLocationResolver + implements ConfigDataLocationResolver, Ordered { + + private static final String PREFIX = "resource:"; + + static final String CONFIG_NAME_PROPERTY = "spring.config.name"; + + private static final String[] DEFAULT_CONFIG_NAMES = { "application" }; + + private static final Resource[] EMPTY_RESOURCES = {}; + + private static final Comparator FILE_COMPARATOR = Comparator.comparing(File::getAbsolutePath); + + private static final Pattern URL_PREFIX = Pattern.compile("^([a-zA-Z][a-zA-Z0-9*]*?:)(.*$)"); + + private static final Pattern EXTENSION_HINT_PATTERN = Pattern.compile("^(.*)\\[(\\.\\w+)\\](?!\\[)$"); + + private static final String NO_PROFILE = null; + + private final Log logger; + + private final List propertySourceLoaders; + + private final String[] configNames; + + private final ResourceLoader resourceLoader; + + /** + * Create a new {@link StandardConfigDataLocationResolver} instance. + * @param logger the logger to use + * @param binder a binder backed by the initial {@link Environment} + * @param resourceLoader a {@link ResourceLoader} used to load resources + */ + public StandardConfigDataLocationResolver(Log logger, Binder binder, ResourceLoader resourceLoader) { + this.logger = logger; + this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class, + getClass().getClassLoader()); + this.configNames = getConfigNames(binder); + this.resourceLoader = resourceLoader; + } + + private String[] getConfigNames(Binder binder) { + String[] configNames = binder.bind(CONFIG_NAME_PROPERTY, String[].class).orElse(DEFAULT_CONFIG_NAMES); + for (String configName : configNames) { + validateConfigName(configName); + } + return configNames; + } + + private void validateConfigName(String name) { + Assert.state(!name.contains("*"), () -> "Config name '" + name + "' cannot contain '*'"); + } + + @Override + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE; + } + + @Override + public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location) { + return true; + } + + @Override + public List resolve(ConfigDataLocationResolverContext context, + ConfigDataLocation location) throws ConfigDataNotFoundException { + return resolve(getReferences(context, location)); + } + + private Set getReferences(ConfigDataLocationResolverContext context, + ConfigDataLocation configDataLocation) { + String resourceLocation = getResourceLocation(context, configDataLocation); + try { + if (isDirectory(resourceLocation)) { + return getReferencesForDirectory(configDataLocation, resourceLocation, NO_PROFILE); + } + return getReferencesForFile(configDataLocation, resourceLocation, NO_PROFILE); + } + catch (RuntimeException ex) { + throw new IllegalStateException("Unable to load config data from '" + configDataLocation + "'", ex); + } + } + + @Override + public List resolveProfileSpecific(ConfigDataLocationResolverContext context, + ConfigDataLocation location, Profiles profiles) { + return resolve(getProfileSpecificReferences(context, location, profiles)); + } + + private Set getProfileSpecificReferences(ConfigDataLocationResolverContext context, + ConfigDataLocation configDataLocation, Profiles profiles) { + Set references = new LinkedHashSet<>(); + String resourceLocation = getResourceLocation(context, configDataLocation); + for (String profile : profiles) { + references.addAll(getReferences(configDataLocation, resourceLocation, profile)); + } + return references; + } + + private String getResourceLocation(ConfigDataLocationResolverContext context, + ConfigDataLocation configDataLocation) { + String resourceLocation = configDataLocation.getNonPrefixedValue(PREFIX); + boolean isAbsolute = resourceLocation.startsWith("/") || URL_PREFIX.matcher(resourceLocation).matches(); + if (isAbsolute) { + return resourceLocation; + } + ConfigDataResource parent = context.getParent(); + if (parent instanceof StandardConfigDataResource) { + String parentResourceLocation = ((StandardConfigDataResource) parent).getReference().getResourceLocation(); + String parentDirectory = parentResourceLocation.substring(0, parentResourceLocation.lastIndexOf("/") + 1); + return parentDirectory + resourceLocation; + } + return resourceLocation; + } + + private Set getReferences(ConfigDataLocation configDataLocation, + String resourceLocation, String profile) { + if (isDirectory(resourceLocation)) { + return getReferencesForDirectory(configDataLocation, resourceLocation, profile); + } + return getReferencesForFile(configDataLocation, resourceLocation, profile); + } + + private Set getReferencesForDirectory(ConfigDataLocation configDataLocation, + String directory, String profile) { + Set references = new LinkedHashSet<>(); + for (String name : this.configNames) { + for (PropertySourceLoader propertySourceLoader : this.propertySourceLoaders) { + for (String extension : propertySourceLoader.getFileExtensions()) { + StandardConfigDataReference reference = new StandardConfigDataReference(configDataLocation, + directory, directory + name, profile, extension, propertySourceLoader); + references.add(reference); + } + } + } + return references; + } + + private Set getReferencesForFile(ConfigDataLocation configDataLocation, String file, + String profile) { + Matcher extensionHintMatcher = EXTENSION_HINT_PATTERN.matcher(file); + boolean extensionHintLocation = extensionHintMatcher.matches(); + if (extensionHintLocation) { + file = extensionHintMatcher.group(1) + extensionHintMatcher.group(2); + } + for (PropertySourceLoader propertySourceLoader : this.propertySourceLoaders) { + String extension = getLoadableFileExtension(propertySourceLoader, file); + if (extension != null) { + String root = file.substring(0, file.length() - extension.length() - 1); + StandardConfigDataReference reference = new StandardConfigDataReference(configDataLocation, null, root, + profile, (!extensionHintLocation) ? extension : null, propertySourceLoader); + return Collections.singleton(reference); + } + } + throw new IllegalStateException("File extension is not known to any PropertySourceLoader. " + + "If the location is meant to reference a directory, it must end in '/'"); + } + + private String getLoadableFileExtension(PropertySourceLoader loader, String file) { + for (String fileExtension : loader.getFileExtensions()) { + if (StringUtils.endsWithIgnoreCase(file, fileExtension)) { + return fileExtension; + } + } + return null; + } + + private boolean isDirectory(String resourceLocation) { + return resourceLocation.endsWith("/"); + } + + private List resolve(Set references) { + List resolved = new ArrayList<>(); + for (StandardConfigDataReference reference : references) { + resolved.addAll(resolve(reference)); + } + if (resolved.isEmpty()) { + assertNonOptionalDirectories(references); + } + return resolved; + } + + private void assertNonOptionalDirectories(Set references) { + for (StandardConfigDataReference reference : references) { + if (reference.isNonOptionalDirectory()) { + assertDirectoryExists(reference); + } + } + } + + private void assertDirectoryExists(StandardConfigDataReference reference) { + Resource resource = loadResource(reference.getDirectory()); + StandardConfigDataResource configDataResource = new StandardConfigDataResource(reference, resource); + ConfigDataResourceNotFoundException.throwIfDoesNotExist(configDataResource, resource); + } + + private List resolve(StandardConfigDataReference reference) { + if (!reference.isPatternLocation()) { + return resolveNonPattern(reference); + } + return resolvePattern(reference); + } + + private List resolveNonPattern(StandardConfigDataReference reference) { + Resource resource = loadResource(reference.getResourceLocation()); + if (!resource.exists() && reference.isSkippable()) { + logSkippingResource(reference); + return Collections.emptyList(); + } + return Collections.singletonList(createConfigResourceLocation(reference, resource)); + } + + private List resolvePattern(StandardConfigDataReference reference) { + validatePatternLocation(reference.getResourceLocation()); + List resolved = new ArrayList<>(); + for (Resource resource : getResourcesFromResourceLocationPattern(reference.getResourceLocation())) { + if (!resource.exists() && reference.isSkippable()) { + logSkippingResource(reference); + } + else { + resolved.add(createConfigResourceLocation(reference, resource)); + } + } + return resolved; + } + + private void logSkippingResource(StandardConfigDataReference reference) { + this.logger.trace(LogMessage.format("Skipping missing resource %s", reference)); + } + + private StandardConfigDataResource createConfigResourceLocation(StandardConfigDataReference reference, + Resource resource) { + return new StandardConfigDataResource(reference, resource); + } + + private void validatePatternLocation(String resourceLocation) { + Assert.state(!resourceLocation.startsWith(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX), + "Classpath wildcard patterns cannot be used as a search location"); + Assert.state(StringUtils.countOccurrencesOf(resourceLocation, "*") == 1, + () -> "Search location '" + resourceLocation + "' cannot contain multiple wildcards"); + String directoryPath = resourceLocation.substring(0, resourceLocation.lastIndexOf("/") + 1); + Assert.state(directoryPath.endsWith("*/"), + () -> "Search location '" + resourceLocation + "' must end with '*/'"); + } + + private Resource[] getResourcesFromResourceLocationPattern(String resourceLocationPattern) { + String directoryPath = resourceLocationPattern.substring(0, resourceLocationPattern.indexOf("*/")); + String fileName = resourceLocationPattern.substring(resourceLocationPattern.lastIndexOf("/") + 1); + Resource directoryResource = loadResource(directoryPath); + if (!directoryResource.exists()) { + return new Resource[] { directoryResource }; + } + File directory = getDirectory(resourceLocationPattern, directoryResource); + File[] subDirectories = directory.listFiles(File::isDirectory); + if (subDirectories == null) { + return EMPTY_RESOURCES; + } + Arrays.sort(subDirectories, FILE_COMPARATOR); + List resources = new ArrayList<>(); + FilenameFilter filter = (dir, name) -> name.equals(fileName); + for (File subDirectory : subDirectories) { + File[] files = subDirectory.listFiles(filter); + if (files != null) { + Arrays.stream(files).map(FileSystemResource::new).forEach(resources::add); + } + } + return resources.toArray(EMPTY_RESOURCES); + } + + private Resource loadResource(String location) { + location = StringUtils.cleanPath(location); + if (!ResourceUtils.isUrl(location)) { + location = ResourceUtils.FILE_URL_PREFIX + location; + } + return this.resourceLoader.getResource(location); + } + + private File getDirectory(String patternLocation, Resource resource) { + try { + File directory = resource.getFile(); + Assert.state(directory.isDirectory(), () -> "'" + directory + "' is not a directory"); + return directory; + } + catch (Exception ex) { + throw new IllegalStateException( + "Unable to load config data resource from pattern '" + patternLocation + "'", ex); + } + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataReference.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataReference.java new file mode 100644 index 0000000000..55878557bf --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataReference.java @@ -0,0 +1,111 @@ +/* + * 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; + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ResourceConfigDataLocation.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataResource.java similarity index 56% rename from spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ResourceConfigDataLocation.java rename to spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataResource.java index 54668b37a4..59f097cc61 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ResourceConfigDataLocation.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataResource.java @@ -17,63 +17,43 @@ package org.springframework.boot.context.config; 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.FileUrlResource; import org.springframework.core.io.Resource; import org.springframework.util.Assert; /** - * {@link ConfigDataLocation} backed by a {@link Resource}. + * {@link ConfigDataResource} backed by a {@link Resource}. * * @author Madhura Bhave * @author Phillip Webb * @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 Origin origin; - - private final PropertySourceLoader propertySourceLoader; - /** - * Create a new {@link ResourceConfigDataLocation} instance. - * @param name the source location + * Create a new {@link StandardConfigDataResource} instance. + * @param reference the resource reference * @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, - PropertySourceLoader propertySourceLoader) { - Assert.notNull(name, "Name must not be null"); + StandardConfigDataResource(StandardConfigDataReference reference, Resource resource) { + Assert.notNull(reference, "Reference must not be null"); Assert.notNull(resource, "Resource must not be null"); - Assert.notNull(propertySourceLoader, "PropertySourceLoader must not be null"); - this.name = name; + this.reference = reference; this.resource = resource; - this.origin = origin; - this.propertySourceLoader = propertySourceLoader; - } - - Resource getResource() { - return this.resource; } - String getLocation() { - return this.name; + StandardConfigDataReference getReference() { + return this.reference; } - List> load() throws IOException { - Resource resource = OriginTrackedResource.of(this.resource, this.origin); - return this.propertySourceLoader.load(this.name, resource); + Resource getResource() { + return this.resource; } @Override @@ -84,7 +64,7 @@ public class ResourceConfigDataLocation extends ConfigDataLocation { if (obj == null || getClass() != obj.getClass()) { return false; } - ResourceConfigDataLocation other = (ResourceConfigDataLocation) obj; + StandardConfigDataResource other = (StandardConfigDataResource) obj; return this.resource.equals(other.resource); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/UnsupportedConfigDataLocationException.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/UnsupportedConfigDataLocationException.java index 6581b428c8..072107fecd 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/UnsupportedConfigDataLocationException.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/UnsupportedConfigDataLocationException.java @@ -25,22 +25,22 @@ package org.springframework.boot.context.config; */ public class UnsupportedConfigDataLocationException extends ConfigDataException { - private final String location; + private final ConfigDataLocation location; /** * Create a new {@link UnsupportedConfigDataLocationException} instance. * @param location the unsupported location */ - UnsupportedConfigDataLocationException(String location) { + UnsupportedConfigDataLocationException(ConfigDataLocation location) { super("Unsupported config data location '" + location + "'", null); this.location = location; } /** - * Return the unsupported location. - * @return the unsupported location + * Return the unsupported location reference. + * @return the unsupported location reference */ - public String getLocation() { + public ConfigDataLocation getLocation() { return this.location; } diff --git a/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories index d460701d02..303f9ced73 100644 --- a/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories @@ -12,12 +12,12 @@ org.springframework.boot.env.YamlPropertySourceLoader # ConfigData Location Resolvers org.springframework.boot.context.config.ConfigDataLocationResolver=\ org.springframework.boot.context.config.ConfigTreeConfigDataLocationResolver,\ -org.springframework.boot.context.config.ResourceConfigDataLocationResolver +org.springframework.boot.context.config.StandardConfigDataLocationResolver # ConfigData Loaders org.springframework.boot.context.config.ConfigDataLoader=\ org.springframework.boot.context.config.ConfigTreeConfigDataLoader,\ -org.springframework.boot.context.config.ResourceConfigDataLoader +org.springframework.boot.context.config.StandardConfigDataLoader # Run Listeners org.springframework.boot.SpringApplicationRunListener=\ diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorTests.java index 7c680de4ae..4c6cdb0df7 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorTests.java @@ -44,18 +44,20 @@ import static org.mockito.Mockito.mock; */ class ConfigDataEnvironmentContributorTests { + private static final ConfigDataLocation TEST_LOCATION = ConfigDataLocation.of("test"); + private ConfigDataActivationContext activationContext = new ConfigDataActivationContext(CloudPlatform.KUBERNETES, null); @Test void getKindReturnsKind() { - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("test"); + ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(TEST_LOCATION); assertThat(contributor.getKind()).isEqualTo(Kind.INITIAL_IMPORT); } @Test void isActiveWhenPropertiesIsNullReturnsTrue() { - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("test"); + ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(TEST_LOCATION); assertThat(contributor.isActive(null)).isTrue(); } @@ -80,10 +82,10 @@ class ConfigDataEnvironmentContributorTests { @Test void getLocationReturnsLocation() { ConfigData configData = new ConfigData(Collections.singleton(new MockPropertySource())); - ConfigDataLocation location = mock(ConfigDataLocation.class); - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofUnboundImport(location, + ConfigDataResource resource = mock(ConfigDataResource.class); + ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofUnboundImport(resource, configData, 0); - assertThat(contributor.getLocation()).isSameAs(location); + assertThat(contributor.getResource()).isSameAs(resource); } @Test @@ -117,7 +119,8 @@ class ConfigDataEnvironmentContributorTests { propertySource.setProperty("spring.config.import", "spring,boot"); ConfigData configData = new ConfigData(Collections.singleton(propertySource)); ConfigDataEnvironmentContributor contributor = createBoundContributor(null, configData, 0); - assertThat(contributor.getImports()).containsExactly("spring", "boot"); + assertThat(contributor.getImports()).containsExactly(ConfigDataLocation.of("spring"), + ConfigDataLocation.of("boot")); } @Test @@ -239,7 +242,7 @@ class ConfigDataEnvironmentContributorTests { ConfigDataEnvironmentContributor two = createBoundContributor("two"); ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.of(Arrays.asList(one, two)); assertThat(contributor.getKind()).isEqualTo(Kind.ROOT); - assertThat(contributor.getLocation()).isNull(); + assertThat(contributor.getResource()).isNull(); assertThat(contributor.getImports()).isEmpty(); assertThat(contributor.isActive(this.activationContext)).isTrue(); assertThat(contributor.getPropertySource()).isNull(); @@ -249,10 +252,10 @@ class ConfigDataEnvironmentContributorTests { @Test void ofInitialImportCreatedInitialImportContributor() { - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("test"); + ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(TEST_LOCATION); assertThat(contributor.getKind()).isEqualTo(Kind.INITIAL_IMPORT); - assertThat(contributor.getLocation()).isNull(); - assertThat(contributor.getImports()).containsExactly("test"); + assertThat(contributor.getResource()).isNull(); + assertThat(contributor.getImports()).containsExactly(TEST_LOCATION); assertThat(contributor.isActive(this.activationContext)).isTrue(); assertThat(contributor.getPropertySource()).isNull(); assertThat(contributor.getConfigurationPropertySource()).isNull(); @@ -266,7 +269,7 @@ class ConfigDataEnvironmentContributorTests { propertySource.setProperty("spring.config.activate.on-cloud-platform", "cloudfoundry"); ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofExisting(propertySource); 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.isActive(this.activationContext)).isTrue(); assertThat(contributor.getPropertySource()).isEqualTo(propertySource); @@ -276,14 +279,14 @@ class ConfigDataEnvironmentContributorTests { @Test void ofUnboundImportCreatesImportedContributor() { - TestLocation location = new TestLocation("test"); + TestResource location = new TestResource("test"); MockPropertySource propertySource = new MockPropertySource(); propertySource.setProperty("spring.config.import", "test"); ConfigData configData = new ConfigData(Collections.singleton(propertySource)); ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofUnboundImport(location, configData, 0); assertThat(contributor.getKind()).isEqualTo(Kind.UNBOUND_IMPORT); - assertThat(contributor.getLocation()).isSameAs(location); + assertThat(contributor.getResource()).isSameAs(location); assertThat(contributor.getImports()).isEmpty(); assertThat(contributor.isActive(this.activationContext)).isTrue(); assertThat(contributor.getPropertySource()).isEqualTo(propertySource); @@ -293,14 +296,14 @@ class ConfigDataEnvironmentContributorTests { @Test void bindCreatesImportedContributor() { - TestLocation location = new TestLocation("test"); + TestResource location = new TestResource("test"); MockPropertySource propertySource = new MockPropertySource(); propertySource.setProperty("spring.config.import", "test"); ConfigData configData = new ConfigData(Collections.singleton(propertySource)); ConfigDataEnvironmentContributor contributor = createBoundContributor(location, configData, 0); assertThat(contributor.getKind()).isEqualTo(Kind.BOUND_IMPORT); - assertThat(contributor.getLocation()).isSameAs(location); - assertThat(contributor.getImports()).containsExactly("test"); + assertThat(contributor.getResource()).isSameAs(location); + assertThat(contributor.getImports()).containsExactly(TEST_LOCATION); assertThat(contributor.isActive(this.activationContext)).isTrue(); assertThat(contributor.getPropertySource()).isEqualTo(propertySource); assertThat(contributor.getConfigurationPropertySource()).isNotNull(); @@ -309,13 +312,13 @@ class ConfigDataEnvironmentContributorTests { @Test void bindWhenConfigDataHasIgnoreImportsOptionsCreatesImportedContributorWithoutImports() { - TestLocation location = new TestLocation("test"); + TestResource location = new TestResource("test"); MockPropertySource propertySource = new MockPropertySource(); propertySource.setProperty("spring.config.import", "test"); ConfigData configData = new ConfigData(Collections.singleton(propertySource), ConfigData.Option.IGNORE_IMPORTS); ConfigDataEnvironmentContributor contributor = createBoundContributor(location, configData, 0); assertThat(contributor.getKind()).isEqualTo(Kind.BOUND_IMPORT); - assertThat(contributor.getLocation()).isSameAs(location); + assertThat(contributor.getResource()).isSameAs(location); assertThat(contributor.getImports()).isEmpty(); assertThat(contributor.isActive(this.activationContext)).isTrue(); assertThat(contributor.getPropertySource()).isEqualTo(propertySource); @@ -332,13 +335,13 @@ class ConfigDataEnvironmentContributorTests { } private ConfigDataEnvironmentContributor createBoundContributor(String location) { - return createBoundContributor(new TestLocation(location), + return createBoundContributor(new TestResource(location), new ConfigData(Collections.singleton(new MockPropertySource())), 0); } - private ConfigDataEnvironmentContributor createBoundContributor(ConfigDataLocation location, ConfigData configData, + private ConfigDataEnvironmentContributor createBoundContributor(ConfigDataResource resource, ConfigData configData, int propertySourceIndex) { - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofUnboundImport(location, + ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofUnboundImport(resource, configData, propertySourceIndex); Binder binder = new Binder(contributor.getConfigurationPropertySource()); return contributor.withBoundProperties(binder); @@ -351,14 +354,14 @@ class ConfigDataEnvironmentContributorTests { } 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; - TestLocation(String location) { + TestResource(String location) { this.location = location; } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorsTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorsTests.java index 3225b68ecc..dfbc888a08 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorsTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorsTests.java @@ -57,6 +57,10 @@ import static org.mockito.Mockito.verify; @ExtendWith(MockitoExtension.class) 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 DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext(); @@ -80,16 +84,15 @@ class ConfigDataEnvironmentContributorsTests { this.environment = new MockEnvironment(); this.binder = Binder.get(this.environment); ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext, - ConfigDataLocationNotFoundAction.FAIL, this.binder, null); - ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext, - ConfigDataLocationNotFoundAction.FAIL); - this.importer = new ConfigDataImporter(resolvers, loaders); + this.binder, null); + ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext); + this.importer = new ConfigDataImporter(this.logFactory, ConfigDataNotFoundAction.FAIL, resolvers, loaders); this.activationContext = new ConfigDataActivationContext(CloudPlatform.KUBERNETES, null); } @Test void createCreatesWithInitialContributors() { - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("test"); + ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapContext, Arrays.asList(contributor)); Iterator iterator = contributors.iterator(); @@ -111,13 +114,13 @@ class ConfigDataEnvironmentContributorsTests { @Test void withProcessedImportsResolvesAndLoads() { this.importer = mock(ConfigDataImporter.class); - List locations = Arrays.asList("testimport"); + List locations = Arrays.asList(LOCATION_1); MockPropertySource propertySource = new MockPropertySource(); - Map imported = new LinkedHashMap<>(); - imported.put(new TestConfigDataLocation("a"), new ConfigData(Arrays.asList(propertySource))); + Map imported = new LinkedHashMap<>(); + imported.put(new TestConfigDataResource("a"), new ConfigData(Arrays.asList(propertySource))); given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations))) .willReturn(imported); - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("testimport"); + ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapContext, Arrays.asList(contributor)); ConfigDataEnvironmentContributors withProcessedImports = contributors.withProcessedImports(this.importer, @@ -132,21 +135,20 @@ class ConfigDataEnvironmentContributorsTests { @Test void withProcessedImportsResolvesAndLoadsChainedImports() { this.importer = mock(ConfigDataImporter.class); - List initialLocations = Arrays.asList("initialimport"); + List initialLocations = Arrays.asList(LOCATION_1); MockPropertySource initialPropertySource = new MockPropertySource(); - initialPropertySource.setProperty("spring.config.import", "secondimport"); - Map initialImported = new LinkedHashMap<>(); - initialImported.put(new TestConfigDataLocation("a"), new ConfigData(Arrays.asList(initialPropertySource))); + initialPropertySource.setProperty("spring.config.import", "location2"); + Map initialImported = new LinkedHashMap<>(); + initialImported.put(new TestConfigDataResource("a"), new ConfigData(Arrays.asList(initialPropertySource))); given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(initialLocations))) .willReturn(initialImported); - List secondLocations = Arrays.asList("secondimport"); + List secondLocations = Arrays.asList(LOCATION_2); MockPropertySource secondPropertySource = new MockPropertySource(); - Map secondImported = new LinkedHashMap<>(); - secondImported.put(new TestConfigDataLocation("b"), new ConfigData(Arrays.asList(secondPropertySource))); + Map secondImported = new LinkedHashMap<>(); + secondImported.put(new TestConfigDataResource("b"), new ConfigData(Arrays.asList(secondPropertySource))); given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(secondLocations))) .willReturn(secondImported); - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor - .ofInitialImport("initialimport"); + ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapContext, Arrays.asList(contributor)); ConfigDataEnvironmentContributors withProcessedImports = contributors.withProcessedImports(this.importer, @@ -166,13 +168,13 @@ class ConfigDataEnvironmentContributorsTests { ConfigDataEnvironmentContributor existingContributor = ConfigDataEnvironmentContributor .ofExisting(existingPropertySource); this.importer = mock(ConfigDataImporter.class); - List locations = Arrays.asList("testimport"); + List locations = Arrays.asList(LOCATION_1); MockPropertySource propertySource = new MockPropertySource(); - Map imported = new LinkedHashMap<>(); - imported.put(new TestConfigDataLocation("a'"), new ConfigData(Arrays.asList(propertySource))); + Map imported = new LinkedHashMap<>(); + imported.put(new TestConfigDataResource("a'"), new ConfigData(Arrays.asList(propertySource))); given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations))) .willReturn(imported); - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("testimport"); + ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapContext, Arrays.asList(existingContributor, contributor)); contributors.withProcessedImports(this.importer, this.activationContext); @@ -184,21 +186,20 @@ class ConfigDataEnvironmentContributorsTests { @Test void withProcessedImportsProvidesLocationResolverContextWithAccessToParent() { this.importer = mock(ConfigDataImporter.class); - List initialLocations = Arrays.asList("initialimport"); + List initialLocations = Arrays.asList(LOCATION_1); MockPropertySource initialPropertySource = new MockPropertySource(); - initialPropertySource.setProperty("spring.config.import", "secondimport"); - Map initialImported = new LinkedHashMap<>(); - initialImported.put(new TestConfigDataLocation("a"), new ConfigData(Arrays.asList(initialPropertySource))); + initialPropertySource.setProperty("spring.config.import", "location2"); + Map initialImported = new LinkedHashMap<>(); + initialImported.put(new TestConfigDataResource("a"), new ConfigData(Arrays.asList(initialPropertySource))); given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(initialLocations))) .willReturn(initialImported); - List secondLocations = Arrays.asList("secondimport"); + List secondLocations = Arrays.asList(LOCATION_2); MockPropertySource secondPropertySource = new MockPropertySource(); - Map secondImported = new LinkedHashMap<>(); - secondImported.put(new TestConfigDataLocation("b"), new ConfigData(Arrays.asList(secondPropertySource))); + Map secondImported = new LinkedHashMap<>(); + secondImported.put(new TestConfigDataResource("b"), new ConfigData(Arrays.asList(secondPropertySource))); given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(secondLocations))) .willReturn(secondImported); - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor - .ofInitialImport("initialimport"); + ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapContext, Arrays.asList(contributor)); contributors.withProcessedImports(this.importer, this.activationContext); @@ -214,13 +215,13 @@ class ConfigDataEnvironmentContributorsTests { ConfigDataEnvironmentContributor existingContributor = ConfigDataEnvironmentContributor .ofExisting(existingPropertySource); this.importer = mock(ConfigDataImporter.class); - List locations = Arrays.asList("testimport"); + List locations = Arrays.asList(LOCATION_1); MockPropertySource propertySource = new MockPropertySource(); - Map imported = new LinkedHashMap<>(); - imported.put(new TestConfigDataLocation("a'"), new ConfigData(Arrays.asList(propertySource))); + Map imported = new LinkedHashMap<>(); + imported.put(new TestConfigDataResource("a'"), new ConfigData(Arrays.asList(propertySource))); given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations))) .willReturn(imported); - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("testimport"); + ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapContext, Arrays.asList(existingContributor, contributor)); contributors.withProcessedImports(this.importer, this.activationContext); @@ -236,13 +237,13 @@ class ConfigDataEnvironmentContributorsTests { ConfigDataEnvironmentContributor existingContributor = ConfigDataEnvironmentContributor .ofExisting(existingPropertySource); this.importer = mock(ConfigDataImporter.class); - List locations = Arrays.asList("testimport"); + List locations = Arrays.asList(LOCATION_1); MockPropertySource propertySource = new MockPropertySource(); - Map imported = new LinkedHashMap<>(); - imported.put(new TestConfigDataLocation("a'"), new ConfigData(Arrays.asList(propertySource))); + Map imported = new LinkedHashMap<>(); + imported.put(new TestConfigDataResource("a'"), new ConfigData(Arrays.asList(propertySource))); given(this.importer.resolveAndLoad(eq(this.activationContext), any(), any(), eq(locations))) .willReturn(imported); - ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport("testimport"); + ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofInitialImport(LOCATION_1); ConfigDataEnvironmentContributors contributors = new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapContext, Arrays.asList(existingContributor, contributor)); contributors.withProcessedImports(this.importer, this.activationContext); @@ -382,11 +383,11 @@ class ConfigDataEnvironmentContributorsTests { return contributor.withBoundProperties(binder); } - private static class TestConfigDataLocation extends ConfigDataLocation { + private static class TestConfigDataResource extends ConfigDataResource { private final String value; - TestConfigDataLocation(String value) { + TestConfigDataResource(String value) { this.value = value; } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorBootstrapContextIntegrationTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorBootstrapContextIntegrationTests.java index 9ea7359182..1e4eda88fe 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorBootstrapContextIntegrationTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorBootstrapContextIntegrationTests.java @@ -51,7 +51,8 @@ class ConfigDataEnvironmentPostProcessorBootstrapContextIntegrationTests { LoaderHelper bean = context.getBean(TestConfigDataBootstrap.LoaderHelper.class); assertThat(bean).isNotNull(); assertThat(bean.getBound()).isEqualTo("igotbound"); - assertThat(bean.getLocation().getResolverHelper().getLocation()).isEqualTo("testbootstrap:test"); + assertThat(bean.getLocation().getResolverHelper().getLocation()) + .isEqualTo(ConfigDataLocation.of("testbootstrap:test")); } } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java index 6fae8b8860..1efc106095 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java @@ -378,8 +378,8 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests { List names = StreamSupport.stream(context.getEnvironment().getPropertySources().spliterator(), false) .map(org.springframework.core.env.PropertySource::getName).collect(Collectors.toList()); assertThat(names).contains( - "Resource config 'classpath:configdata/profiles/testsetprofiles.yml' imported 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 #0)", + "Config resource 'classpath:configdata/profiles/testsetprofiles.yml' via location 'classpath:configdata/profiles/' (document #1)"); } @Test @@ -406,8 +406,8 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests { String location = "file:src/test/resources/specificlocation.properties"; ConfigurableApplicationContext context = this.application.run("--spring.config.location=" + location); assertThat(context.getEnvironment()).has(matchingPropertySource( - "Resource config 'file:src/test/resources/specificlocation.properties' imported via location \"" - + location + "\"")); + "Config resource 'file:src/test/resources/specificlocation.properties' via location '" + location + + "'")); } @Test @@ -415,8 +415,7 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests { String location = "src/test/resources/specificlocation.properties"; ConfigurableApplicationContext context = this.application.run("--spring.config.location=" + location); 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 @@ -519,7 +518,7 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests { @Test void runWhenConfigLocationHasNonOptionalMissingDirectoryThrowsException() { String location = "classpath:application.unknown/"; - assertThatExceptionOfType(ConfigDataLocationNotFoundException.class) + assertThatExceptionOfType(ConfigDataResourceNotFoundException.class) .isThrownBy(() -> this.application.run("--spring.config.location=" + location)); } @@ -553,13 +552,13 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests { @Test void runWhenHasNonOptionalImportThrowsException() { - assertThatExceptionOfType(ConfigDataLocationNotFoundException.class).isThrownBy( + assertThatExceptionOfType(ConfigDataResourceNotFoundException.class).isThrownBy( () -> this.application.run("--spring.config.location=classpath:missing-appplication.properties")); } @Test 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"); } @@ -613,6 +612,7 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests { @Override public boolean matches(ConfigurableEnvironment value) { + value.getPropertySources().forEach((ps) -> System.out.println(ps.getName())); return value.getPropertySources().contains(sourceName); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentTests.java index be14fb81b3..8327d9d52f 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentTests.java @@ -214,11 +214,9 @@ class ConfigDataEnvironmentTests { @Override protected ConfigDataLocationResolvers createConfigDataLocationResolvers(DeferredLogFactory logFactory, - ConfigurableBootstrapContext bootstrapContext, ConfigDataLocationNotFoundAction locationNotFoundAction, - Binder binder, ResourceLoader resourceLoader) { + ConfigurableBootstrapContext bootstrapContext, Binder binder, ResourceLoader resourceLoader) { this.configDataLocationResolversBinder = binder; - return super.createConfigDataLocationResolvers(logFactory, bootstrapContext, locationNotFoundAction, binder, - resourceLoader); + return super.createConfigDataLocationResolvers(logFactory, bootstrapContext, binder, resourceLoader); } Binder getConfigDataLocationResolversBinder() { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataImporterTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataImporterTests.java index a86fe987b8..76df8e511f 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataImporterTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataImporterTests.java @@ -20,6 +20,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.function.Supplier; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -28,6 +29,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.boot.logging.DeferredLogFactory; import org.springframework.mock.env.MockPropertySource; import static org.assertj.core.api.Assertions.assertThat; @@ -42,6 +44,8 @@ import static org.mockito.BDDMockito.given; @ExtendWith(MockitoExtension.class) class ConfigDataImporterTests { + private DeferredLogFactory logFactory = Supplier::get; + @Mock private ConfigDataLocationResolvers resolvers; @@ -70,43 +74,49 @@ class ConfigDataImporterTests { @Test void loadImportsResolvesAndLoadsLocations() throws Exception { - List locations = Arrays.asList("test1", "test2"); - TestLocation resolvedLocation1 = new TestLocation(); - TestLocation resolvedLocation2 = new TestLocation(); - List resolvedLocations = Arrays.asList(resolvedLocation1, resolvedLocation2); + ConfigDataLocation location1 = ConfigDataLocation.of("test1"); + ConfigDataLocation location2 = ConfigDataLocation.of("test2"); + TestResource resource1 = new TestResource("r1"); + TestResource resource2 = new TestResource("r2"); ConfigData configData1 = new ConfigData(Collections.singleton(new MockPropertySource())); ConfigData configData2 = new ConfigData(Collections.singleton(new MockPropertySource())); - given(this.resolvers.resolveAll(this.locationResolverContext, locations, this.profiles)) - .willReturn(resolvedLocations); - given(this.loaders.load(this.loaderContext, resolvedLocation1)).willReturn(configData1); - given(this.loaders.load(this.loaderContext, resolvedLocation2)).willReturn(configData2); - ConfigDataImporter importer = new ConfigDataImporter(this.resolvers, this.loaders); - Collection loaded = importer - .resolveAndLoad(this.activationContext, this.locationResolverContext, this.loaderContext, locations) - .values(); + given(this.resolvers.resolve(this.locationResolverContext, location1, this.profiles)) + .willReturn(Collections.singletonList(new ConfigDataResolutionResult(location1, resource1))); + given(this.resolvers.resolve(this.locationResolverContext, location2, this.profiles)) + .willReturn(Collections.singletonList(new ConfigDataResolutionResult(location2, resource2))); + given(this.loaders.load(this.loaderContext, resource1)).willReturn(configData1); + given(this.loaders.load(this.loaderContext, resource2)).willReturn(configData2); + ConfigDataImporter importer = new ConfigDataImporter(this.logFactory, ConfigDataNotFoundAction.FAIL, + this.resolvers, this.loaders); + Collection loaded = importer.resolveAndLoad(this.activationContext, this.locationResolverContext, + this.loaderContext, Arrays.asList(location1, location2)).values(); assertThat(loaded).containsExactly(configData2, configData1); } @Test void loadImportsWhenAlreadyImportedLocationSkipsLoad() throws Exception { - List locations1and2 = Arrays.asList("test1", "test2"); - List locations2and3 = Arrays.asList("test2", "test3"); - TestLocation resolvedLocation1 = new TestLocation(); - TestLocation resolvedLocation2 = new TestLocation(); - TestLocation resolvedLocation3 = new TestLocation(); - List resolvedLocations1and2 = Arrays.asList(resolvedLocation1, resolvedLocation2); - List resolvedLocations2and3 = Arrays.asList(resolvedLocation2, resolvedLocation3); + ConfigDataLocation location1 = ConfigDataLocation.of("test1"); + ConfigDataLocation location2 = ConfigDataLocation.of("test2"); + ConfigDataLocation location3 = ConfigDataLocation.of("test3"); + List locations1and2 = Arrays.asList(location1, location2); + List locations2and3 = Arrays.asList(location2, location3); + TestResource resource1 = new TestResource("r1"); + TestResource resource2 = new TestResource("r2"); + TestResource resource3 = new TestResource("r3"); ConfigData configData1 = new ConfigData(Collections.singleton(new MockPropertySource())); ConfigData configData2 = new ConfigData(Collections.singleton(new MockPropertySource())); ConfigData configData3 = new ConfigData(Collections.singleton(new MockPropertySource())); - given(this.resolvers.resolveAll(this.locationResolverContext, locations1and2, this.profiles)) - .willReturn(resolvedLocations1and2); - given(this.resolvers.resolveAll(this.locationResolverContext, locations2and3, this.profiles)) - .willReturn(resolvedLocations2and3); - given(this.loaders.load(this.loaderContext, resolvedLocation1)).willReturn(configData1); - given(this.loaders.load(this.loaderContext, resolvedLocation2)).willReturn(configData2); - given(this.loaders.load(this.loaderContext, resolvedLocation3)).willReturn(configData3); - ConfigDataImporter importer = new ConfigDataImporter(this.resolvers, this.loaders); + given(this.resolvers.resolve(this.locationResolverContext, location1, this.profiles)) + .willReturn(Collections.singletonList(new ConfigDataResolutionResult(location1, resource1))); + given(this.resolvers.resolve(this.locationResolverContext, location2, this.profiles)) + .willReturn(Collections.singletonList(new ConfigDataResolutionResult(location2, resource2))); + given(this.resolvers.resolve(this.locationResolverContext, location3, this.profiles)) + .willReturn(Collections.singletonList(new ConfigDataResolutionResult(location3, resource3))); + given(this.loaders.load(this.loaderContext, resource1)).willReturn(configData1); + 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 loaded1and2 = importer.resolveAndLoad(this.activationContext, this.locationResolverContext, this.loaderContext, locations1and2).values(); Collection loaded2and3 = importer.resolveAndLoad(this.activationContext, @@ -115,7 +125,18 @@ class ConfigDataImporterTests { 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; + } } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLoaderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLoaderTests.java index 87aae67504..2f538516b4 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLoaderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLoaderTests.java @@ -37,19 +37,19 @@ class ConfigDataLoaderTests { @Test 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 { + static class TestConfigDataLoader implements ConfigDataLoader { @Override - public ConfigData load(ConfigDataLoaderContext context, TestConfigDataLocation location) throws IOException { + public ConfigData load(ConfigDataLoaderContext context, TestConfigDataResource resource) throws IOException { return null; } } - static class TestConfigDataLocation extends ConfigDataLocation { + static class TestConfigDataResource extends ConfigDataResource { } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLoadersTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLoadersTests.java index f69339508e..ca0aa74b38 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLoadersTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLoadersTests.java @@ -53,50 +53,48 @@ class ConfigDataLoadersTests { @Test void createWhenLoaderHasLogParameterInjectsLog() { - new ConfigDataLoaders(this.logFactory, this.bootstrapContext, ConfigDataLocationNotFoundAction.FAIL, + new ConfigDataLoaders(this.logFactory, this.bootstrapContext, Arrays.asList(LoggingConfigDataLoader.class.getName())); } @Test void createWhenLoaderHasBootstrapParametersInjectsBootstrapContext() { - new ConfigDataLoaders(this.logFactory, this.bootstrapContext, ConfigDataLocationNotFoundAction.FAIL, + new ConfigDataLoaders(this.logFactory, this.bootstrapContext, Arrays.asList(BootstrappingConfigDataLoader.class.getName())); assertThat(this.bootstrapContext.get(String.class)).isEqualTo("boot"); } @Test void loadWhenSingleLoaderSupportsLocationReturnsLoadedConfigData() throws Exception { - TestConfigDataLocation location = new TestConfigDataLocation("test"); + TestConfigDataResource location = new TestConfigDataResource("test"); 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); assertThat(getLoader(loaded)).isInstanceOf(TestConfigDataLoader.class); } @Test void loadWhenMultipleLoadersSupportLocationThrowsException() throws Exception { - TestConfigDataLocation location = new TestConfigDataLocation("test"); + TestConfigDataResource location = new TestConfigDataResource("test"); ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext, - ConfigDataLocationNotFoundAction.FAIL, Arrays.asList(LoggingConfigDataLoader.class.getName(), TestConfigDataLoader.class.getName())); assertThatIllegalStateException().isThrownBy(() -> loaders.load(this.context, location)) - .withMessageContaining("Multiple loaders found for location test"); + .withMessageContaining("Multiple loaders found for resource 'test'"); } @Test void loadWhenNoLoaderSupportsLocationThrowsException() { - TestConfigDataLocation location = new TestConfigDataLocation("test"); + TestConfigDataResource location = new TestConfigDataResource("test"); 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)) - .withMessage("No loader found for location 'test'"); + .withMessage("No loader found for resource 'test'"); } @Test void loadWhenGenericTypeDoesNotMatchSkipsLoader() throws Exception { - TestConfigDataLocation location = new TestConfigDataLocation("test"); + TestConfigDataResource location = new TestConfigDataResource("test"); ConfigDataLoaders loaders = new ConfigDataLoaders(this.logFactory, this.bootstrapContext, - ConfigDataLocationNotFoundAction.FAIL, Arrays.asList(OtherConfigDataLoader.class.getName(), SpecificConfigDataLoader.class.getName())); ConfigData loaded = loaders.load(this.context, location); assertThat(getLoader(loaded)).isInstanceOf(SpecificConfigDataLoader.class); @@ -106,20 +104,19 @@ class ConfigDataLoadersTests { 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(); propertySource.setProperty("loader", loader); - propertySource.setProperty("location", location); + propertySource.setProperty("resource", resource); List> propertySources = Arrays.asList(propertySource); return new ConfigData(propertySources); - } - static class TestConfigDataLocation extends ConfigDataLocation { + static class TestConfigDataResource extends ConfigDataResource { private final String value; - TestConfigDataLocation(String value) { + TestConfigDataResource(String value) { this.value = value; } @@ -130,24 +127,24 @@ class ConfigDataLoadersTests { } - static class OtherConfigDataLocation extends ConfigDataLocation { + static class OtherConfigDataResource extends ConfigDataResource { } - static class LoggingConfigDataLoader implements ConfigDataLoader { + static class LoggingConfigDataLoader implements ConfigDataLoader { LoggingConfigDataLoader(Log log) { assertThat(log).isNotNull(); } @Override - public ConfigData load(ConfigDataLoaderContext context, ConfigDataLocation location) throws IOException { + public ConfigData load(ConfigDataLoaderContext context, ConfigDataResource resource) throws IOException { throw new AssertionError("Unexpected call"); } } - static class BootstrappingConfigDataLoader implements ConfigDataLoader { + static class BootstrappingConfigDataLoader implements ConfigDataLoader { BootstrappingConfigDataLoader(ConfigurableBootstrapContext configurableBootstrapContext, BootstrapRegistry bootstrapRegistry, BootstrapContext bootstrapContext) { @@ -159,17 +156,17 @@ class ConfigDataLoadersTests { } @Override - public ConfigData load(ConfigDataLoaderContext context, ConfigDataLocation location) throws IOException { + public ConfigData load(ConfigDataLoaderContext context, ConfigDataResource resource) throws IOException { throw new AssertionError("Unexpected call"); } } - static class TestConfigDataLoader implements ConfigDataLoader { + static class TestConfigDataLoader implements ConfigDataLoader { @Override - public ConfigData load(ConfigDataLoaderContext context, ConfigDataLocation location) throws IOException { - return createConfigData(this, location); + public ConfigData load(ConfigDataLoaderContext context, ConfigDataResource resource) throws IOException { + return createConfigData(this, resource); } } @@ -177,25 +174,25 @@ class ConfigDataLoadersTests { static class NonLoadableConfigDataLoader extends TestConfigDataLoader { @Override - public boolean isLoadable(ConfigDataLoaderContext context, ConfigDataLocation location) { + public boolean isLoadable(ConfigDataLoaderContext context, ConfigDataResource resource) { return false; } } - static class SpecificConfigDataLoader implements ConfigDataLoader { + static class SpecificConfigDataLoader implements ConfigDataLoader { @Override - public ConfigData load(ConfigDataLoaderContext context, TestConfigDataLocation location) throws IOException { + public ConfigData load(ConfigDataLoaderContext context, TestConfigDataResource location) throws IOException { return createConfigData(this, location); } } - static class OtherConfigDataLoader implements ConfigDataLoader { + static class OtherConfigDataLoader implements ConfigDataLoader { @Override - public ConfigData load(ConfigDataLoaderContext context, OtherConfigDataLocation location) throws IOException { + public ConfigData load(ConfigDataLoaderContext context, OtherConfigDataResource location) throws IOException { return createConfigData(this, location); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationBindHandlerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationBindHandlerTests.java new file mode 100644 index 0000000000..b7b12405d6 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationBindHandlerTests.java @@ -0,0 +1,121 @@ +/* + * 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 ARRAY = Bindable.of(ConfigDataLocation[].class); + + private static final Bindable 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 locations; + + ValueObject(List locations) { + this.locations = locations; + } + + ConfigDataLocation getLocation(int index) { + return this.locations.get(index); + } + + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationNotFoundExceptionTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationNotFoundExceptionTests.java index 8c57ae897e..ff1ffbcd01 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationNotFoundExceptionTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationNotFoundExceptionTests.java @@ -16,19 +16,12 @@ 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 org.springframework.boot.origin.Origin; 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; /** @@ -38,98 +31,37 @@ import static org.mockito.Mockito.mock; */ class ConfigDataLocationNotFoundExceptionTests { - private ConfigDataLocation location = mock(ConfigDataLocation.class); - - private Throwable cause = new RuntimeException(); - - private String message = "message"; - - private File exists; + private Origin origin = mock(Origin.class); - private File missing; + private final ConfigDataLocation location = ConfigDataLocation.of("optional:test").withOrigin(this.origin); - @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()); - } - } + private final ConfigDataLocationNotFoundException exception = new ConfigDataLocationNotFoundException( + this.location); @Test - void createWithLocationCreatesInstance() { - ConfigDataLocationNotFoundException exception = new ConfigDataLocationNotFoundException(this.location); - assertThat(exception.getLocation()).isSameAs(this.location); - } - - @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); + void createWhenLocationIsNullThrowsException() { + assertThatIllegalArgumentException().isThrownBy(() -> new ConfigDataLocationNotFoundException(null)) + .withMessage("Location must not be null"); } @Test void getLocationReturnsLocation() { - ConfigDataLocationNotFoundException exception = new ConfigDataLocationNotFoundException(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); - + assertThat(this.exception.getLocation()).isSameAs(this.location); } @Test - void throwIfDoesNotExistWhenFileDoesNotExistThrowsException() { - assertThatExceptionOfType(ConfigDataLocationNotFoundException.class) - .isThrownBy(() -> ConfigDataLocationNotFoundException.throwIfDoesNotExist(this.location, this.missing)); + void getOriginReturnsLocationOrigin() { + assertThat(this.exception.getOrigin()).isSameAs(this.origin); } @Test - void throwIfDoesNotExistWhenResourceExistsDoesNothing() { - ConfigDataLocationNotFoundException.throwIfDoesNotExist(this.location, new FileSystemResource(this.exists)); + void getReferenceDescriptionReturnsLocationString() { + assertThat(this.exception.getReferenceDescription()).isEqualTo("location 'optional:test'"); } @Test - void throwIfDoesNotExistWhenResourceDoesNotExistThrowsException() { - assertThatExceptionOfType(ConfigDataLocationNotFoundException.class) - .isThrownBy(() -> ConfigDataLocationNotFoundException.throwIfDoesNotExist(this.location, - new FileSystemResource(this.missing))); + void getMessageReturnsMessage() { + assertThat(this.exception).hasMessage("Config data location 'optional:test' cannot be found"); } } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationResolverTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationResolverTests.java index 0994ba26ce..0c839e06d6 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationResolverTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationResolverTests.java @@ -37,19 +37,19 @@ class ConfigDataLocationResolverTests { @Test 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 { + static class TestConfigDataLocationResolver implements ConfigDataLocationResolver { @Override - public boolean isResolvable(ConfigDataLocationResolverContext context, String location) { + public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location) { return true; } @Override - public List resolve(ConfigDataLocationResolverContext context, String location, - boolean optional) { + public List resolve(ConfigDataLocationResolverContext context, + ConfigDataLocation location) { return null; } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationResolversTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationResolversTests.java index 7e0171be6a..784e723c15 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationResolversTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationResolversTests.java @@ -71,8 +71,7 @@ class ConfigDataLocationResolversTests { @Test void createWhenInjectingBinderCreatesResolver() { ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext, - ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader, - Collections.singletonList(TestBoundResolver.class.getName())); + this.binder, this.resourceLoader, Collections.singletonList(TestBoundResolver.class.getName())); assertThat(resolvers.getResolvers()).hasSize(1); assertThat(resolvers.getResolvers().get(0)).isExactlyInstanceOf(TestBoundResolver.class); assertThat(((TestBoundResolver) resolvers.getResolvers().get(0)).getBinder()).isSameAs(this.binder); @@ -81,25 +80,23 @@ class ConfigDataLocationResolversTests { @Test void createWhenNotInjectingBinderCreatesResolver() { ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext, - ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader, - Collections.singletonList(TestResolver.class.getName())); + this.binder, this.resourceLoader, Collections.singletonList(TestResolver.class.getName())); assertThat(resolvers.getResolvers()).hasSize(1); assertThat(resolvers.getResolvers().get(0)).isExactlyInstanceOf(TestResolver.class); } @Test void createWhenResolverHasBootstrapParametersInjectsBootstrapContext() { - new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext, ConfigDataLocationNotFoundAction.FAIL, - this.binder, this.resourceLoader, Collections.singletonList(TestBootstrappingResolver.class.getName())); + new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext, this.binder, this.resourceLoader, + Collections.singletonList(TestBootstrappingResolver.class.getName())); assertThat(this.bootstrapContext.get(String.class)).isEqualTo("boot"); } @Test void createWhenNameIsNotConfigDataLocationResolverThrowsException() { assertThatIllegalArgumentException() - .isThrownBy(() -> new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext, - ConfigDataLocationNotFoundAction.FAIL, this.binder, this.resourceLoader, - Collections.singletonList(InputStream.class.getName()))) + .isThrownBy(() -> new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext, this.binder, + this.resourceLoader, Collections.singletonList(InputStream.class.getName()))) .withMessageContaining("Unable to instantiate").havingCause().withMessageContaining("not assignable"); } @@ -110,73 +107,75 @@ class ConfigDataLocationResolversTests { names.add(LowestTestResolver.class.getName()); names.add(HighestTestResolver.class.getName()); 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(1)).isExactlyInstanceOf(TestResolver.class); assertThat(resolvers.getResolvers().get(2)).isExactlyInstanceOf(LowestTestResolver.class); } @Test - void resolveAllResolvesUsingFirstSupportedResolver() { + void resolveResolvesUsingFirstSupportedResolver() { 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())); - List resolved = resolvers.resolveAll(this.context, - Collections.singletonList("LowestTestResolver:test"), null); + ConfigDataLocation location = ConfigDataLocation.of("LowestTestResolver:test"); + List resolved = resolvers.resolve(this.context, location, null); assertThat(resolved).hasSize(1); - TestConfigDataLocation location = (TestConfigDataLocation) resolved.get(0); - assertThat(location.getResolver()).isInstanceOf(LowestTestResolver.class); - assertThat(location.getLocation()).isEqualTo("LowestTestResolver:test"); - assertThat(location.isProfileSpecific()).isFalse(); + TestConfigDataResource resource = (TestConfigDataResource) resolved.get(0).getResource(); + assertThat(resource.getResolver()).isInstanceOf(LowestTestResolver.class); + assertThat(resource.getLocation()).isEqualTo(location); + assertThat(resource.isProfileSpecific()).isFalse(); } @Test - void resolveAllWhenProfileMergesResolvedLocations() { + void resolveWhenProfileMergesResolvedLocations() { 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())); - List resolved = resolvers.resolveAll(this.context, - Collections.singletonList("LowestTestResolver:test"), this.profiles); + ConfigDataLocation location = ConfigDataLocation.of("LowestTestResolver:test"); + List resolved = resolvers.resolve(this.context, location, this.profiles); assertThat(resolved).hasSize(2); - TestConfigDataLocation location = (TestConfigDataLocation) resolved.get(0); - assertThat(location.getResolver()).isInstanceOf(LowestTestResolver.class); - assertThat(location.getLocation()).isEqualTo("LowestTestResolver:test"); - assertThat(location.isProfileSpecific()).isFalse(); - TestConfigDataLocation profileLocation = (TestConfigDataLocation) resolved.get(1); - assertThat(profileLocation.getResolver()).isInstanceOf(LowestTestResolver.class); - assertThat(profileLocation.getLocation()).isEqualTo("LowestTestResolver:test"); - assertThat(profileLocation.isProfileSpecific()).isTrue(); + TestConfigDataResource resource = (TestConfigDataResource) resolved.get(0).getResource(); + assertThat(resource.getResolver()).isInstanceOf(LowestTestResolver.class); + assertThat(resource.getLocation()).isEqualTo(location); + assertThat(resource.isProfileSpecific()).isFalse(); + TestConfigDataResource profileResource = (TestConfigDataResource) resolved.get(1).getResource(); + assertThat(profileResource.getResolver()).isInstanceOf(LowestTestResolver.class); + assertThat(profileResource.getLocation()).isEqualTo(location); + assertThat(profileResource.isProfileSpecific()).isTrue(); } @Test void resolveWhenNoResolverThrowsException() { 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())); + ConfigDataLocation location = ConfigDataLocation.of("Missing:test"); assertThatExceptionOfType(UnsupportedConfigDataLocationException.class) - .isThrownBy(() -> resolvers.resolveAll(this.context, Collections.singletonList("Missing:test"), null)) - .satisfies((ex) -> assertThat(ex.getLocation()).isEqualTo("Missing:test")); + .isThrownBy(() -> resolvers.resolve(this.context, location, null)) + .satisfies((ex) -> assertThat(ex.getLocation()).isEqualTo(location)); } - static class TestResolver implements ConfigDataLocationResolver { + static class TestResolver implements ConfigDataLocationResolver { @Override - public boolean isResolvable(ConfigDataLocationResolverContext context, String location) { + public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location) { String name = getClass().getName(); name = name.substring(name.lastIndexOf("$") + 1); - return location.startsWith(name + ":"); + return location.hasPrefix(name + ":"); } @Override - public List resolve(ConfigDataLocationResolverContext context, String location, - boolean optional) { - return Collections.singletonList(new TestConfigDataLocation(this, location, false)); + public List resolve(ConfigDataLocationResolverContext context, + ConfigDataLocation location) + throws ConfigDataLocationNotFoundException, ConfigDataResourceNotFoundException { + return Collections.singletonList(new TestConfigDataResource(this, location, false)); } @Override - public List resolveProfileSpecific(ConfigDataLocationResolverContext context, - String location, boolean optional, Profiles profiles) { - return Collections.singletonList(new TestConfigDataLocation(this, location, true)); + public List resolveProfileSpecific(ConfigDataLocationResolverContext context, + ConfigDataLocation location, Profiles profiles) throws ConfigDataLocationNotFoundException { + return Collections.singletonList(new TestConfigDataResource(this, location, true)); } } @@ -218,15 +217,15 @@ class ConfigDataLocationResolversTests { } - static class TestConfigDataLocation extends ConfigDataLocation { + static class TestConfigDataResource extends ConfigDataResource { private final TestResolver resolver; - private final String location; + private final ConfigDataLocation location; private final boolean profileSpecific; - TestConfigDataLocation(TestResolver resolver, String location, boolean profileSpecific) { + TestConfigDataResource(TestResolver resolver, ConfigDataLocation location, boolean profileSpecific) { this.resolver = resolver; this.location = location; this.profileSpecific = profileSpecific; @@ -236,7 +235,7 @@ class ConfigDataLocationResolversTests { return this.resolver; } - String getLocation() { + ConfigDataLocation getLocation() { return this.location; } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationTests.java new file mode 100644 index 0000000000..491ee39d2d --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataLocationTests.java @@ -0,0 +1,137 @@ +/* + * 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"); + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataPropertiesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataPropertiesTests.java index ba68719b4c..9622287a00 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataPropertiesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataPropertiesTests.java @@ -43,13 +43,16 @@ class ConfigDataPropertiesTests { private static final Profiles NULL_PROFILES = null; - private static final List NO_IMPORTS = Collections.emptyList(); + private static final List NO_IMPORTS = Collections.emptyList(); @Test void getImportsReturnsImports() { - List imports = Arrays.asList("one", "two", "three"); + ConfigDataLocation l1 = ConfigDataLocation.of("one"); + ConfigDataLocation l2 = ConfigDataLocation.of("two"); + ConfigDataLocation l3 = ConfigDataLocation.of("three"); + List imports = Arrays.asList(l1, l2, l3); ConfigDataProperties properties = new ConfigDataProperties(imports, null); - assertThat(properties.getImports()).containsExactly("one", "two", "three"); + assertThat(properties.getImports()).containsExactly(l1, l2, l3); } @Test @@ -164,7 +167,8 @@ class ConfigDataPropertiesTests { ConfigDataProperties properties = ConfigDataProperties.get(binder); ConfigDataActivationContext context = new ConfigDataActivationContext(CloudPlatform.KUBERNETES, 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(); } @@ -195,7 +199,7 @@ class ConfigDataPropertiesTests { source.put("spring.config.import", "one,two,three"); Binder binder = new Binder(source); ConfigDataProperties properties = ConfigDataProperties.get(binder); - assertThat(properties.getImportOrigin("two")) + assertThat(properties.getImports().get(1).getOrigin()) .hasToString("\"spring.config.import\" from property source \"source\""); } @@ -207,7 +211,7 @@ class ConfigDataPropertiesTests { source.put("spring.config.import[2]", "three"); Binder binder = new Binder(source); ConfigDataProperties properties = ConfigDataProperties.get(binder); - assertThat(properties.getImportOrigin("two")) + assertThat(properties.getImports().get(1).getOrigin()) .hasToString("\"spring.config.import[1]\" from property source \"source\""); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataResourceNotFoundExceptionTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataResourceNotFoundExceptionTests.java new file mode 100644 index 0000000000..14cb46447c --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataResourceNotFoundExceptionTests.java @@ -0,0 +1,167 @@ +/* + * 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"; + } + + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigTreeConfigDataLoaderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigTreeConfigDataLoaderTests.java index 510b585a5e..206c4b3e10 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigTreeConfigDataLoaderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigTreeConfigDataLoaderTests.java @@ -51,7 +51,7 @@ public class ConfigTreeConfigDataLoaderTests { File file = this.directory.resolve("hello").toFile(); file.getParentFile().mkdirs(); 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); assertThat(configData.getPropertySources().size()).isEqualTo(1); PropertySource source = configData.getPropertySources().get(0); @@ -62,8 +62,8 @@ public class ConfigTreeConfigDataLoaderTests { @Test void loadWhenPathDoesNotExistThrowsException() { File missing = this.directory.resolve("missing").toFile(); - ConfigTreeConfigDataLocation location = new ConfigTreeConfigDataLocation(missing.toString()); - assertThatExceptionOfType(ConfigDataLocationNotFoundException.class) + ConfigTreeConfigDataResource location = new ConfigTreeConfigDataResource(missing.toString()); + assertThatExceptionOfType(ConfigDataResourceNotFoundException.class) .isThrownBy(() -> this.loader.load(this.loaderContext, location)); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigTreeConfigDataLocationResolverTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigTreeConfigDataLocationResolverTests.java index ac532a1470..b3eaacf457 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigTreeConfigDataLocationResolverTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigTreeConfigDataLocationResolverTests.java @@ -38,19 +38,19 @@ class ConfigTreeConfigDataLocationResolverTests { @Test 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 void isResolvableWhenPrefixDoesNotMatchReturnsFalse() { - assertThat(this.resolver.isResolvable(this.context, "http://etc/config")).isFalse(); - assertThat(this.resolver.isResolvable(this.context, "/etc/config")).isFalse(); + assertThat(this.resolver.isResolvable(this.context, ConfigDataLocation.of("http://etc/config"))).isFalse(); + assertThat(this.resolver.isResolvable(this.context, ConfigDataLocation.of("/etc/config"))).isFalse(); } @Test void resolveReturnsConfigVolumeMountLocation() { - List locations = this.resolver.resolve(this.context, "configtree:/etc/config", - false); + List locations = this.resolver.resolve(this.context, + ConfigDataLocation.of("configtree:/etc/config")); assertThat(locations.size()).isEqualTo(1); assertThat(locations).extracting(Object::toString) .containsExactly("config tree [" + new File("/etc/config").getAbsolutePath() + "]"); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigTreeConfigDataLocationTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigTreeConfigDataResourceTests.java similarity index 71% rename from spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigTreeConfigDataLocationTests.java rename to spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigTreeConfigDataResourceTests.java index 6852c89bbd..39ac16b99b 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigTreeConfigDataLocationTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigTreeConfigDataResourceTests.java @@ -24,36 +24,36 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** - * Tests for {@link ConfigTreeConfigDataLocation}. + * Tests for {@link ConfigTreeConfigDataResource}. * * @author Madhura Bhave * @author Phillip Webb */ -public class ConfigTreeConfigDataLocationTests { +public class ConfigTreeConfigDataResourceTests { @Test void constructorWhenPathIsNullThrowsException() { - assertThatIllegalArgumentException().isThrownBy(() -> new ConfigTreeConfigDataLocation(null)) + assertThatIllegalArgumentException().isThrownBy(() -> new ConfigTreeConfigDataResource(null)) .withMessage("Path must not be null"); } @Test void equalsWhenPathIsTheSameReturnsTrue() { - ConfigTreeConfigDataLocation location = new ConfigTreeConfigDataLocation("/etc/config"); - ConfigTreeConfigDataLocation other = new ConfigTreeConfigDataLocation("/etc/config"); + ConfigTreeConfigDataResource location = new ConfigTreeConfigDataResource("/etc/config"); + ConfigTreeConfigDataResource other = new ConfigTreeConfigDataResource("/etc/config"); assertThat(location).isEqualTo(other); } @Test void equalsWhenPathIsDifferentReturnsFalse() { - ConfigTreeConfigDataLocation location = new ConfigTreeConfigDataLocation("/etc/config"); - ConfigTreeConfigDataLocation other = new ConfigTreeConfigDataLocation("other-location"); + ConfigTreeConfigDataResource location = new ConfigTreeConfigDataResource("/etc/config"); + ConfigTreeConfigDataResource other = new ConfigTreeConfigDataResource("other-location"); assertThat(location).isNotEqualTo(other); } @Test 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() + "]"); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/InactiveConfigDataAccessExceptionTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/InactiveConfigDataAccessExceptionTests.java index 2460ad6e49..8332a7b4f8 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/InactiveConfigDataAccessExceptionTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/InactiveConfigDataAccessExceptionTests.java @@ -40,7 +40,7 @@ class InactiveConfigDataAccessExceptionTests { private MockPropertySource propertySource = new MockPropertySource(); - private ConfigDataLocation location = new TestConfigDataLocation(); + private ConfigDataResource resource = new TestConfigDataResource(); private String propertyName = "spring"; @@ -49,7 +49,7 @@ class InactiveConfigDataAccessExceptionTests { @Test void createHasCorrectMessage() { 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' " + "cannot contain property 'spring' [origin: \"spring\" from property source \"mockProperties\"]"); } @@ -65,7 +65,7 @@ class InactiveConfigDataAccessExceptionTests { @Test void createWhenNoOriginHasCorrectMessage() { 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' " + "cannot contain property 'spring'"); } @@ -73,28 +73,28 @@ class InactiveConfigDataAccessExceptionTests { @Test void getPropertySourceReturnsPropertySource() { InactiveConfigDataAccessException exception = new InactiveConfigDataAccessException(this.propertySource, - this.location, this.propertyName, this.origin); + this.resource, this.propertyName, this.origin); assertThat(exception.getPropertySource()).isSameAs(this.propertySource); } @Test void getLocationReturnsLocation() { InactiveConfigDataAccessException exception = new InactiveConfigDataAccessException(this.propertySource, - this.location, this.propertyName, this.origin); - assertThat(exception.getLocation()).isSameAs(this.location); + this.resource, this.propertyName, this.origin); + assertThat(exception.getLocation()).isSameAs(this.resource); } @Test void getPropertyNameReturnsPropertyName() { InactiveConfigDataAccessException exception = new InactiveConfigDataAccessException(this.propertySource, - this.location, this.propertyName, this.origin); + this.resource, this.propertyName, this.origin); assertThat(exception.getPropertyName()).isSameAs(this.propertyName); } @Test void getOriginReturnsOrigin() { InactiveConfigDataAccessException exception = new InactiveConfigDataAccessException(this.propertySource, - this.location, this.propertyName, this.origin); + this.resource, this.propertyName, this.origin); assertThat(exception.getOrigin()).isSameAs(this.origin); } @@ -121,7 +121,7 @@ class InactiveConfigDataAccessExceptionTests { ConfigurationPropertySource configurationPropertySource = ConfigurationPropertySource.from(this.propertySource); given(contributor.getConfigurationPropertySource()).willReturn(configurationPropertySource); given(contributor.getPropertySource()).willReturn((PropertySource) this.propertySource); - given(contributor.getLocation()).willReturn(this.location); + given(contributor.getResource()).willReturn(this.resource); assertThatExceptionOfType(InactiveConfigDataAccessException.class) .isThrownBy(() -> InactiveConfigDataAccessException.throwIfPropertyFound(contributor, ConfigurationPropertyName.of("spring"))) @@ -129,7 +129,7 @@ class InactiveConfigDataAccessExceptionTests { + "cannot contain property 'spring' [origin: \"spring\" from property source \"mockProperties\"]"); } - private static class TestConfigDataLocation extends ConfigDataLocation { + private static class TestConfigDataResource extends ConfigDataResource { @Override public String toString() { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/InvalidConfigDataPropertyExceptionTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/InvalidConfigDataPropertyExceptionTests.java index c1375a58da..69bb6fadee 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/InvalidConfigDataPropertyExceptionTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/InvalidConfigDataPropertyExceptionTests.java @@ -38,7 +38,7 @@ import static org.mockito.Mockito.verify; */ class InvalidConfigDataPropertyExceptionTests { - private ConfigDataLocation location = new TestConfigDataLocation(); + private ConfigDataResource resource = new TestConfigDataResource(); private ConfigurationPropertyName replacement = ConfigurationPropertyName.of("replacement"); @@ -50,7 +50,7 @@ class InvalidConfigDataPropertyExceptionTests { @Test 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]"); } @@ -62,35 +62,35 @@ class InvalidConfigDataPropertyExceptionTests { @Test 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]"); } @Test void createWhenNoOriginHasCorrectMessage() { 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'"); } @Test void getPropertyReturnsProperty() { InvalidConfigDataPropertyException exception = new InvalidConfigDataPropertyException(this.property, - this.replacement, this.location); + this.replacement, this.resource); assertThat(exception.getProperty()).isEqualTo(this.property); } @Test void getLocationReturnsLocation() { InvalidConfigDataPropertyException exception = new InvalidConfigDataPropertyException(this.property, - this.replacement, this.location); - assertThat(exception.getLocation()).isEqualTo(this.location); + this.replacement, this.resource); + assertThat(exception.getLocation()).isEqualTo(this.resource); } @Test void getReplacementReturnsReplacement() { InvalidConfigDataPropertyException exception = new InvalidConfigDataPropertyException(this.property, - this.replacement, this.location); + this.replacement, this.resource); assertThat(exception.getReplacement()).isEqualTo(this.replacement); } @@ -123,7 +123,7 @@ class InvalidConfigDataPropertyExceptionTests { + "'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 public String toString() { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/OptionalConfigDataLocationTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/OptionalConfigDataLocationTests.java deleted file mode 100644 index 0216337f2d..0000000000 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/OptionalConfigDataLocationTests.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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 locations = Collections.singletonList(this.location); - List 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); - } - -} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ResourceConfigDataLoaderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/StandardConfigDataLoaderTests.java similarity index 58% rename from spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ResourceConfigDataLoaderTests.java rename to spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/StandardConfigDataLoaderTests.java index 2168e2810b..ee69d6a545 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ResourceConfigDataLoaderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/StandardConfigDataLoaderTests.java @@ -29,36 +29,43 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; /** - * Tests for {@link ResourceConfigDataLoader}. + * Tests for {@link StandardConfigDataLoader}. * * @author Madhura Bhave * @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); @Test void loadWhenLocationResultsInMultiplePropertySourcesAddsAllToConfigData() throws IOException { - ResourceConfigDataLocation location = new ResourceConfigDataLocation("application.yml", - new ClassPathResource("configdata/yaml/application.yml"), null, new YamlPropertySourceLoader()); + ClassPathResource resource = new ClassPathResource("configdata/yaml/application.yml"); + 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); assertThat(configData.getPropertySources().size()).isEqualTo(2); PropertySource source1 = configData.getPropertySources().get(0); 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(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"); } @Test void loadWhenPropertySourceIsEmptyAddsNothingToConfigData() throws IOException { - ResourceConfigDataLocation location = new ResourceConfigDataLocation("testproperties.properties", - new ClassPathResource("config/0-empty/testproperties.properties"), null, - new PropertiesPropertySourceLoader()); + ClassPathResource resource = new ClassPathResource("config/0-empty/testproperties.properties"); + StandardConfigDataReference reference = new StandardConfigDataReference( + 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); assertThat(configData.getPropertySources().size()).isEqualTo(0); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ResourceConfigDataLocationResolverTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/StandardConfigDataLocationResolverTests.java similarity index 64% rename from spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ResourceConfigDataLocationResolverTests.java rename to spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/StandardConfigDataLocationResolverTests.java index 6ed35ee72b..f60ee7a9c9 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ResourceConfigDataLocationResolverTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/StandardConfigDataLocationResolverTests.java @@ -38,14 +38,14 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; /** - * Tests for {@link ResourceConfigDataLocationResolver}. + * Tests for {@link StandardConfigDataLocationResolver}. * * @author Madhura Bhave * @author Phillip Webb */ -public class ResourceConfigDataLocationResolverTests { +public class StandardConfigDataLocationResolverTests { - private ResourceConfigDataLocationResolver resolver; + private StandardConfigDataLocationResolver resolver; private ConfigDataLocationResolverContext context = mock(ConfigDataLocationResolverContext.class); @@ -59,19 +59,19 @@ public class ResourceConfigDataLocationResolverTests { void setup() { this.environment = new MockEnvironment(); this.environmentBinder = Binder.get(this.environment); - this.resolver = new ResourceConfigDataLocationResolver(new DeferredLog(), this.environmentBinder, + this.resolver = new StandardConfigDataLocationResolver(new DeferredLog(), this.environmentBinder, this.resourceLoader); } @Test void isResolvableAlwaysReturnsTrue() { - assertThat(this.resolver.isResolvable(this.context, "test")).isTrue(); + assertThat(this.resolver.isResolvable(this.context, ConfigDataLocation.of("test"))).isTrue(); } @Test void resolveWhenLocationIsDirectoryResolvesAllMatchingFilesInDirectory() { - String location = "classpath:/configdata/properties/"; - List locations = this.resolver.resolve(this.context, location, true); + ConfigDataLocation location = ConfigDataLocation.of("classpath:/configdata/properties/"); + List locations = this.resolver.resolve(this.context, location); assertThat(locations.size()).isEqualTo(1); assertThat(locations).extracting(Object::toString) .containsExactly("class path resource [configdata/properties/application.properties]"); @@ -79,8 +79,9 @@ public class ResourceConfigDataLocationResolverTests { @Test void resolveWhenLocationIsFileResolvesFile() { - String location = "file:src/test/resources/configdata/properties/application.properties"; - List locations = this.resolver.resolve(this.context, location, true); + ConfigDataLocation location = ConfigDataLocation + .of("file:src/test/resources/configdata/properties/application.properties"); + List locations = this.resolver.resolve(this.context, location); assertThat(locations.size()).isEqualTo(1); assertThat(locations).extracting(Object::toString).containsExactly( filePath("src", "test", "resources", "configdata", "properties", "application.properties")); @@ -88,23 +89,24 @@ public class ResourceConfigDataLocationResolverTests { @Test void resolveWhenLocationIsFileAndNoMatchingLoaderThrowsException() { - String location = "file:src/test/resources/configdata/properties/application.unknown"; - assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location, true)) + ConfigDataLocation location = ConfigDataLocation + .of("file:src/test/resources/configdata/properties/application.unknown"); + assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location)) .withMessageStartingWith("Unable to load config data from") .satisfies((ex) -> assertThat(ex.getCause()).hasMessageStartingWith("File extension is not known")); } @Test void resolveWhenLocationWildcardIsSpecifiedForClasspathLocationThrowsException() { - String location = "classpath*:application.properties"; - assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location, true)) + ConfigDataLocation location = ConfigDataLocation.of("classpath*:application.properties"); + assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location)) .withMessageContaining("Classpath wildcard patterns cannot be used as a search location"); } @Test void resolveWhenLocationWildcardIsNotBeforeLastSlashThrowsException() { - String location = "file:src/test/resources/*/config/"; - assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location, true)) + ConfigDataLocation location = ConfigDataLocation.of("file:src/test/resources/*/config/"); + assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location)) .withMessageStartingWith("Search location '").withMessageEndingWith("' must end with '*/'"); } @@ -113,24 +115,24 @@ public class ResourceConfigDataLocationResolverTests { this.environment.setProperty("spring.config.name", "*/application"); assertThatIllegalStateException() .isThrownBy( - () -> new ResourceConfigDataLocationResolver(null, this.environmentBinder, this.resourceLoader)) + () -> new StandardConfigDataLocationResolver(null, this.environmentBinder, this.resourceLoader)) .withMessageStartingWith("Config name '").withMessageEndingWith("' cannot contain '*'"); } @Test void resolveWhenLocationHasMultipleWildcardsThrowsException() { - String location = "file:src/test/resources/config/**/"; - assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location, true)) + ConfigDataLocation location = ConfigDataLocation.of("file:src/test/resources/config/**/"); + assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location)) .withMessageStartingWith("Search location '") .withMessageEndingWith("' cannot contain multiple wildcards"); } @Test 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.resolver = new ResourceConfigDataLocationResolver(null, this.environmentBinder, this.resourceLoader); - List locations = this.resolver.resolve(this.context, location, true); + this.resolver = new StandardConfigDataLocationResolver(null, this.environmentBinder, this.resourceLoader); + List locations = this.resolver.resolve(this.context, location); assertThat(locations.size()).isEqualTo(3); assertThat(locations).extracting(Object::toString) .contains(filePath("src", "test", "resources", "config", "1-first", "testproperties.properties")) @@ -140,10 +142,10 @@ public class ResourceConfigDataLocationResolverTests { @Test 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.resolver = new ResourceConfigDataLocationResolver(null, this.environmentBinder, this.resourceLoader); - List locations = this.resolver.resolve(this.context, location, true); + this.resolver = new StandardConfigDataLocationResolver(null, this.environmentBinder, this.resourceLoader); + List locations = this.resolver.resolve(this.context, location); assertThat(locations).extracting(Object::toString).containsExactly( filePath("src", "test", "resources", "config", "0-empty", "testproperties.properties"), filePath("src", "test", "resources", "config", "1-first", "testproperties.properties"), @@ -152,8 +154,9 @@ public class ResourceConfigDataLocationResolverTests { @Test void resolveWhenLocationIsWildcardFilesLoadsAllFilesThatMatch() { - String location = "file:src/test/resources/config/*/testproperties.properties"; - List locations = this.resolver.resolve(this.context, location, true); + ConfigDataLocation location = ConfigDataLocation + .of("file:src/test/resources/config/*/testproperties.properties"); + List locations = this.resolver.resolve(this.context, location); assertThat(locations.size()).isEqualTo(3); assertThat(locations).extracting(Object::toString) .contains(filePath("src", "test", "resources", "config", "1-first", "testproperties.properties")) @@ -165,15 +168,17 @@ public class ResourceConfigDataLocationResolverTests { @Test void resolveWhenLocationIsRelativeAndFileResolves() { this.environment.setProperty("spring.config.name", "other"); - String location = "other.properties"; - this.resolver = new ResourceConfigDataLocationResolver(new DeferredLog(), this.environmentBinder, + ConfigDataLocation location = ConfigDataLocation.of("other.properties"); + this.resolver = new StandardConfigDataLocationResolver(new DeferredLog(), this.environmentBinder, this.resourceLoader); - ClassPathResource parentResource = new ClassPathResource("configdata/properties/application.properties"); - ResourceConfigDataLocation parent = new ResourceConfigDataLocation( - "classpath:/configdata/properties/application.properties", parentResource, null, + StandardConfigDataReference parentReference = new StandardConfigDataReference( + ConfigDataLocation.of("classpath:configdata/properties/application.properties"), null, + "classpath:configdata/properties/application", null, "properties", new PropertiesPropertySourceLoader()); + ClassPathResource parentResource = new ClassPathResource("configdata/properties/application.properties"); + StandardConfigDataResource parent = new StandardConfigDataResource(parentReference, parentResource); given(this.context.getParent()).willReturn(parent); - List locations = this.resolver.resolve(this.context, location, true); + List locations = this.resolver.resolve(this.context, location); assertThat(locations.size()).isEqualTo(1); assertThat(locations).extracting(Object::toString) .contains("class path resource [configdata/properties/other.properties]"); @@ -182,14 +187,16 @@ public class ResourceConfigDataLocationResolverTests { @Test void resolveWhenLocationIsRelativeAndDirectoryResolves() { this.environment.setProperty("spring.config.name", "testproperties"); - String location = "nested/3-third/"; - this.resolver = new ResourceConfigDataLocationResolver(new DeferredLog(), this.environmentBinder, + ConfigDataLocation location = ConfigDataLocation.of("nested/3-third/"); + this.resolver = new StandardConfigDataLocationResolver(new DeferredLog(), this.environmentBinder, 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"); - ResourceConfigDataLocation parent = new ResourceConfigDataLocation("classpath:/config/specific.properties", - parentResource, null, new PropertiesPropertySourceLoader()); + StandardConfigDataResource parent = new StandardConfigDataResource(parentReference, parentResource); given(this.context.getParent()).willReturn(parent); - List locations = this.resolver.resolve(this.context, location, true); + List locations = this.resolver.resolve(this.context, location); assertThat(locations.size()).isEqualTo(1); assertThat(locations).extracting(Object::toString) .contains("class path resource [config/nested/3-third/testproperties.properties]"); @@ -197,34 +204,36 @@ public class ResourceConfigDataLocationResolverTests { @Test void resolveWhenLocationIsRelativeAndNoMatchingLoaderThrowsException() { - String location = "application.other"; - ClassPathResource parentResource = new ClassPathResource("configdata/application.properties"); - ResourceConfigDataLocation parent = new ResourceConfigDataLocation( - "classpath:/configdata/application.properties", parentResource, null, - new PropertiesPropertySourceLoader()); + ConfigDataLocation location = ConfigDataLocation.of("application.other"); + StandardConfigDataReference parentReference = new StandardConfigDataReference( + ConfigDataLocation.of("classpath:configdata/properties/application.properties"), null, + "configdata/properties/application", null, "properties", new PropertiesPropertySourceLoader()); + ClassPathResource parentResource = new ClassPathResource("configdata/properties/application.properties"); + StandardConfigDataResource parent = new StandardConfigDataResource(parentReference, parentResource); 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'") .satisfies((ex) -> assertThat(ex.getCause()).hasMessageStartingWith("File extension is not known")); } @Test void resolveWhenLocationUsesOptionalExtensionSyntaxResolves() throws Exception { - String location = "classpath:/application-props-no-extension[.properties]"; - List locations = this.resolver.resolve(this.context, location, true); + ConfigDataLocation location = ConfigDataLocation.of("classpath:/application-props-no-extension[.properties]"); + List locations = this.resolver.resolve(this.context, location); assertThat(locations.size()).isEqualTo(1); - ResourceConfigDataLocation resolved = locations.get(0); + StandardConfigDataResource resolved = locations.get(0); 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"); } @Test void resolveProfileSpecificReturnsProfileSpecificFiles() { - String location = "classpath:/configdata/properties/"; + ConfigDataLocation location = ConfigDataLocation.of("classpath:/configdata/properties/"); Profiles profiles = mock(Profiles.class); given(profiles.iterator()).willReturn(Collections.singletonList("dev").iterator()); - List locations = this.resolver.resolveProfileSpecific(this.context, location, true, + List locations = this.resolver.resolveProfileSpecific(this.context, location, profiles); assertThat(locations.size()).isEqualTo(1); assertThat(locations).extracting(Object::toString) @@ -233,11 +242,11 @@ public class ResourceConfigDataLocationResolverTests { @Test void resolveProfileSpecificWhenLocationIsFileReturnsEmptyList() { - String location = "classpath:/configdata/properties/application.properties"; + ConfigDataLocation location = ConfigDataLocation.of("classpath:/configdata/properties/application.properties"); Profiles profiles = mock(Profiles.class); given(profiles.iterator()).willReturn(Collections.emptyIterator()); given(profiles.getActive()).willReturn(Collections.singletonList("dev")); - List locations = this.resolver.resolveProfileSpecific(this.context, location, true, + List locations = this.resolver.resolveProfileSpecific(this.context, location, profiles); assertThat(locations).isEmpty(); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ResourceConfigDataLocationTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/StandardConfigDataResourceTests.java similarity index 50% rename from spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ResourceConfigDataLocationTests.java rename to spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/StandardConfigDataResourceTests.java index 538f73b6fc..9266af6e06 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ResourceConfigDataLocationTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/StandardConfigDataResourceTests.java @@ -18,7 +18,6 @@ package org.springframework.boot.context.config; import org.junit.jupiter.api.Test; -import org.springframework.boot.env.PropertySourceLoader; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; @@ -27,47 +26,34 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException import static org.mockito.Mockito.mock; /** - * Tests for {@link ResourceConfigDataLocation}. + * Tests for {@link StandardConfigDataResource}. * * @author Madhura Bhave * @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 PropertySourceLoader propertySourceLoader = mock(PropertySourceLoader.class); - @Test - void constructorWhenNameIsNullThrowsException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new ResourceConfigDataLocation(null, this.resource, null, this.propertySourceLoader)) - .withMessage("Name must not be null"); + void createWhenReferenceIsNullThrowsException() { + assertThatIllegalArgumentException().isThrownBy(() -> new StandardConfigDataResource(null, this.resource)) + .withMessage("Reference must not be null"); } @Test - void constructorWhenResourceIsNullThrowsException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new ResourceConfigDataLocation(this.location, null, null, this.propertySourceLoader)) + void createWhenResourceIsNullThrowsException() { + assertThatIllegalArgumentException().isThrownBy(() -> new StandardConfigDataResource(this.reference, 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 void equalsWhenResourceIsTheSameReturnsTrue() { Resource resource = new ClassPathResource("config/"); - ResourceConfigDataLocation location = new ResourceConfigDataLocation("my-location", resource, null, - this.propertySourceLoader); - ResourceConfigDataLocation other = new ResourceConfigDataLocation("other-location", resource, null, - this.propertySourceLoader); + StandardConfigDataResource location = new StandardConfigDataResource(this.reference, resource); + StandardConfigDataResource other = new StandardConfigDataResource(this.reference, resource); assertThat(location).isEqualTo(other); } @@ -75,10 +61,8 @@ public class ResourceConfigDataLocationTests { void equalsWhenResourceIsDifferentReturnsFalse() { Resource resource1 = new ClassPathResource("config/"); Resource resource2 = new ClassPathResource("configdata/"); - ResourceConfigDataLocation location = new ResourceConfigDataLocation("my-location", resource1, null, - this.propertySourceLoader); - ResourceConfigDataLocation other = new ResourceConfigDataLocation("other-location", resource2, null, - this.propertySourceLoader); + StandardConfigDataResource location = new StandardConfigDataResource(this.reference, resource1); + StandardConfigDataResource other = new StandardConfigDataResource(this.reference, resource2); assertThat(location).isNotEqualTo(other); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/TestConfigDataBootstrap.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/TestConfigDataBootstrap.java index adec00f15f..e3e0be4f22 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/TestConfigDataBootstrap.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/TestConfigDataBootstrap.java @@ -37,27 +37,27 @@ import org.springframework.core.env.MapPropertySource; */ class TestConfigDataBootstrap { - static class LocationResolver implements ConfigDataLocationResolver { + static class LocationResolver implements ConfigDataLocationResolver { @Override - public boolean isResolvable(ConfigDataLocationResolverContext context, String location) { - return location.startsWith("testbootstrap:"); + public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location) { + return location.hasPrefix("testbootstrap:"); } @Override - public List resolve(ConfigDataLocationResolverContext context, String location, boolean optional) { + public List resolve(ConfigDataLocationResolverContext context, ConfigDataLocation location) { context.getBootstrapContext().registerIfAbsent(ResolverHelper.class, InstanceSupplier.from(() -> new ResolverHelper(location))); ResolverHelper helper = context.getBootstrapContext().get(ResolverHelper.class); - return Collections.singletonList(new Location(helper)); + return Collections.singletonList(new Resource(helper)); } } - static class Loader implements ConfigDataLoader { + static class Loader implements ConfigDataLoader { @Override - public ConfigData load(ConfigDataLoaderContext context, Location location) throws IOException { + public ConfigData load(ConfigDataLoaderContext context, Resource location) throws IOException { context.getBootstrapContext().registerIfAbsent(LoaderHelper.class, (bootstrapContext) -> new LoaderHelper(location, () -> bootstrapContext.get(Binder.class))); LoaderHelper helper = context.getBootstrapContext().get(LoaderHelper.class); @@ -68,11 +68,11 @@ class TestConfigDataBootstrap { } - static class Location extends ConfigDataLocation { + static class Resource extends ConfigDataResource { private final ResolverHelper resolverHelper; - Location(ResolverHelper resolverHelper) { + Resource(ResolverHelper resolverHelper) { this.resolverHelper = resolverHelper; } @@ -89,13 +89,13 @@ class TestConfigDataBootstrap { static class ResolverHelper { - private final String location; + private final ConfigDataLocation location; - ResolverHelper(String location) { + ResolverHelper(ConfigDataLocation location) { this.location = location; } - String getLocation() { + ConfigDataLocation getLocation() { return this.location; } @@ -103,16 +103,16 @@ class TestConfigDataBootstrap { static class LoaderHelper implements ApplicationListener { - private final Location location; + private final Resource location; private final Supplier binder; - LoaderHelper(Location location, Supplier binder) { + LoaderHelper(Resource location, Supplier binder) { this.location = location; this.binder = binder; } - Location getLocation() { + Resource getLocation() { return this.location; } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/UnsupportedConfigDataLocationExceptionTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/UnsupportedConfigDataLocationExceptionTests.java index 5cbe6a940f..b418edd94a 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/UnsupportedConfigDataLocationExceptionTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/UnsupportedConfigDataLocationExceptionTests.java @@ -30,14 +30,16 @@ class UnsupportedConfigDataLocationExceptionTests { @Test void createSetsMessage() { - UnsupportedConfigDataLocationException exception = new UnsupportedConfigDataLocationException("test"); + UnsupportedConfigDataLocationException exception = new UnsupportedConfigDataLocationException( + ConfigDataLocation.of("test")); assertThat(exception).hasMessage("Unsupported config data location 'test'"); } @Test void getLocationReturnsLocation() { - UnsupportedConfigDataLocationException exception = new UnsupportedConfigDataLocationException("test"); - assertThat(exception.getLocation()).isEqualTo("test"); + ConfigDataLocation location = ConfigDataLocation.of("test"); + UnsupportedConfigDataLocationException exception = new UnsupportedConfigDataLocationException(location); + assertThat(exception.getLocation()).isEqualTo(location); } } diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/SubversionConfigDataLoader.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/SubversionConfigDataLoader.java index 6ef1bca623..19009571b4 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/SubversionConfigDataLoader.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/SubversionConfigDataLoader.java @@ -36,7 +36,7 @@ import org.springframework.core.env.PropertySource; * * @author Phillip Webb */ -class SubversionConfigDataLoader implements ConfigDataLoader { +class SubversionConfigDataLoader implements ConfigDataLoader { private static final ApplicationListener closeListener = SubversionConfigDataLoader::onBootstrapContextClosed; @@ -50,12 +50,12 @@ class SubversionConfigDataLoader implements ConfigDataLoader propertySource = new MapPropertySource("svn", Collections.singletonMap("svn", loaded)); return new ConfigData(Collections.singleton(propertySource)); } diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/SubversionConfigDataLocationResolver.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/SubversionConfigDataLocationResolver.java index 74ea940a35..beac974635 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/SubversionConfigDataLocationResolver.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/SubversionConfigDataLocationResolver.java @@ -19,30 +19,33 @@ package smoketest.bootstrapregistry.external.svn; import java.util.Collections; import java.util.List; +import org.springframework.boot.context.config.ConfigDataLocation; import org.springframework.boot.context.config.ConfigDataLocationNotFoundException; import org.springframework.boot.context.config.ConfigDataLocationResolver; import org.springframework.boot.context.config.ConfigDataLocationResolverContext; +import org.springframework.boot.context.config.ConfigDataResourceNotFoundException; /** * {@link ConfigDataLocationResolver} for subversion. * * @author Phillip Webb */ -class SubversionConfigDataLocationResolver implements ConfigDataLocationResolver { +class SubversionConfigDataLocationResolver implements ConfigDataLocationResolver { private static final String PREFIX = "svn:"; @Override - public boolean isResolvable(ConfigDataLocationResolverContext context, String location) { - return location.startsWith(PREFIX); + public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location) { + return location.hasPrefix(PREFIX); } @Override - public List resolve(ConfigDataLocationResolverContext context, String location, - boolean optional) throws ConfigDataLocationNotFoundException { + public List resolve(ConfigDataLocationResolverContext context, + ConfigDataLocation location) + throws ConfigDataLocationNotFoundException, ConfigDataResourceNotFoundException { String serverCertificate = context.getBinder().bind("spring.svn.server.certificate", String.class).orElse(null); return Collections.singletonList( - new SubversionConfigDataLocation(location.substring(PREFIX.length()), serverCertificate)); + new SubversionConfigDataResource(location.getNonPrefixedValue(PREFIX), serverCertificate)); } } diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/SubversionConfigDataLocation.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/SubversionConfigDataResource.java similarity index 81% rename from spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/SubversionConfigDataLocation.java rename to spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/SubversionConfigDataResource.java index d812af0ac3..4a2b30f811 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/SubversionConfigDataLocation.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-bootstrap-registry/src/main/java/smoketest/bootstrapregistry/external/svn/SubversionConfigDataResource.java @@ -16,20 +16,20 @@ 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 */ -class SubversionConfigDataLocation extends ConfigDataLocation { +class SubversionConfigDataResource extends ConfigDataResource { private final String location; private final SubversionServerCertificate serverCertificate; - SubversionConfigDataLocation(String location, String serverCertificate) { + SubversionConfigDataResource(String location, String serverCertificate) { this.location = location; this.serverCertificate = SubversionServerCertificate.of(serverCertificate); } @@ -50,7 +50,7 @@ class SubversionConfigDataLocation extends ConfigDataLocation { if (obj == null || getClass() != obj.getClass()) { return false; } - SubversionConfigDataLocation other = (SubversionConfigDataLocation) obj; + SubversionConfigDataResource other = (SubversionConfigDataResource) obj; return this.location.equals(other.location); }