Throw exception if import locations are not found
Update config data processing code so that import locations are mandatory by default. Any import request will now throw a `ConfigDataLocationNotFoundException` if the specified import location cannot be found. For optional imports, the user can use the `optional:` prefix to indicate that the application should continue to start, even if the location does not exist. Closes gh-23032
This commit is contained in:
@@ -570,9 +570,11 @@ The following example shows how to specify two locations:
|
||||
|
||||
[indent=0]
|
||||
----
|
||||
$ java -jar myproject.jar --spring.config.location=classpath:/default.properties,classpath:/override.properties
|
||||
$ java -jar myproject.jar --spring.config.location=optional:classpath:/default.properties,optional:classpath:/override.properties
|
||||
----
|
||||
|
||||
TIP: Use the prefix `optional:` if the <<boot-features-external-config-optional-prefix,locations are optional>> and you don't mind if they don't exist.
|
||||
|
||||
WARNING: `spring.config.name` and `spring.config.location` are used very early to determine which files have to be loaded.
|
||||
They must be defined as an environment property (typically an OS environment variable, a system property, or a command-line argument).
|
||||
|
||||
@@ -584,22 +586,22 @@ Typical extensions that are supported out-of-the-box are `.properties`, `.yaml`,
|
||||
When multiple locations are specified, the later ones can override the values of earlier ones.
|
||||
|
||||
Locations configured by using `spring.config.location` replace the default locations.
|
||||
For example, if `spring.config.location` is configured with the value `classpath:/custom-config/,file:./custom-config/`, the complete set of locations considered is:
|
||||
For example, if `spring.config.location` is configured with the value `optional:classpath:/custom-config/,optional:file:./custom-config/`, the complete set of locations considered is:
|
||||
|
||||
. `classpath:custom-config/`
|
||||
. `file:./custom-config/`
|
||||
. `optional:classpath:custom-config/`
|
||||
. `optional:file:./custom-config/`
|
||||
|
||||
If you prefer to add addition locations, rather than replacing them, you can use `spring.config.additional-location`.
|
||||
Properties loaded from additional locations can override those in the default locations.
|
||||
For example, if `spring.config.additional-location` is configured with the value `classpath:/custom-config/,file:./custom-config/`, the complete the complete set of locations considered is:
|
||||
For example, if `spring.config.additional-location` is configured with the value `optional:classpath:/custom-config/,optional:file:./custom-config/`, the complete the complete set of locations considered is:
|
||||
|
||||
. `classpath:/`
|
||||
. `classpath:/config/`
|
||||
. `file:./`
|
||||
. `file:./config/*/`
|
||||
. `file:./config/`
|
||||
. `classpath:custom-config/`
|
||||
. `file:./custom-config/`
|
||||
. `optional:classpath:/`
|
||||
. `optional:classpath:/config/`
|
||||
. `optional:file:./`
|
||||
. `optional:file:./config/*/`
|
||||
. `optional:file:./config/`
|
||||
. `optional:classpath:custom-config/`
|
||||
. `optional:file:./custom-config/`
|
||||
|
||||
This search ordering lets you specify default values in one configuration file and then selectively override those values in another.
|
||||
You can provide default values for your application in `application.properties` (or whatever other basename you choose with `spring.config.name`) in one of the default locations.
|
||||
@@ -612,6 +614,17 @@ NOTE: If your application runs in a servlet container or application server, the
|
||||
|
||||
|
||||
|
||||
[[boot-features-external-config-optional-prefix]]
|
||||
==== Optional Locations
|
||||
By default, when a specified config data location does not exist, Spring Boot will throw a `ConfigDataLocationNotFoundException` and your application will not start.
|
||||
|
||||
If you want to specify a location, but you don't mind if it doesn't always exist, you can use the `optional:` prefix.
|
||||
You can use this prefix with the `spring.config.location` and `spring.config.additional-location` properties, as well as with <<boot-features-external-config-files-importing, `spring.config.import`>> declarations.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
|
||||
[[boot-features-external-config-files-wildcards]]
|
||||
==== Wildcard Locations
|
||||
If a config file location includes the `{asterisk}` character for the last path segment, it is considered a wildcard location.
|
||||
@@ -662,7 +675,7 @@ For example, you might have the following in your classpath `application.propert
|
||||
[source,properties,indent=0]
|
||||
----
|
||||
spring.application.name=myapp
|
||||
spring.config.import=file:./dev.properties
|
||||
spring.config.import=optional:file:./dev.properties
|
||||
----
|
||||
|
||||
This will trigger the import of a `dev.properties` file in current directory (if such a file exists).
|
||||
@@ -718,7 +731,7 @@ To import these properties, you can add the following to your `application.prope
|
||||
|
||||
[source,properties,indent=0]
|
||||
----
|
||||
spring.config.import=configtree:/etc/config
|
||||
spring.config.import=optional:configtree:/etc/config
|
||||
----
|
||||
|
||||
You can then access or inject `myapp.username` and `myapp.password` properties from the `Environment` in the usual way.
|
||||
|
||||
@@ -69,8 +69,8 @@ class ConfigDataEnvironment {
|
||||
/**
|
||||
* Default search locations used if not {@link #LOCATION_PROPERTY} is found.
|
||||
*/
|
||||
static final String[] DEFAULT_SEARCH_LOCATIONS = { "classpath:/", "classpath:/config/", "file:./",
|
||||
"file:./config/*/", "file:./config/" };
|
||||
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];
|
||||
|
||||
|
||||
@@ -77,7 +77,10 @@ class ConfigDataImporter {
|
||||
for (int i = locations.size() - 1; i >= 0; i--) {
|
||||
ConfigDataLocation location = locations.get(i);
|
||||
if (this.loadedLocations.add(location)) {
|
||||
result.put(location, this.loaders.load(loaderContext, location));
|
||||
ConfigData loaded = this.loaders.load(loaderContext, location);
|
||||
if (loaded != null) {
|
||||
result.put(location, loaded);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableMap(result);
|
||||
|
||||
@@ -54,7 +54,9 @@ public interface ConfigDataLoader<L extends ConfigDataLocation> {
|
||||
* @param location the location 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
|
||||
*/
|
||||
ConfigData load(ConfigDataLoaderContext context, L location) throws IOException;
|
||||
ConfigData load(ConfigDataLoaderContext context, L location)
|
||||
throws IOException, ConfigDataLocationNotFoundException;
|
||||
|
||||
}
|
||||
|
||||
@@ -86,9 +86,25 @@ class ConfigDataLoaders {
|
||||
* @throws IOException on IO error
|
||||
*/
|
||||
<L extends ConfigDataLocation> 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 <L extends ConfigDataLocation> ConfigData load(ConfigDataLoaderContext context, boolean optional,
|
||||
L location) throws IOException {
|
||||
ConfigDataLoader<L> loader = getLoader(context, location);
|
||||
this.logger.trace(LogMessage.of(() -> "Loading " + location + " using loader " + loader.getClass().getName()));
|
||||
return loader.load(context, location);
|
||||
try {
|
||||
return loader.load(context, location);
|
||||
}
|
||||
catch (ConfigDataLocationNotFoundException ex) {
|
||||
if (!optional) {
|
||||
throw ex;
|
||||
}
|
||||
this.logger.trace(LogMessage.format("Skipping missing resource from optional location %s", location));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
@@ -27,4 +27,9 @@ package org.springframework.boot.context.config;
|
||||
*/
|
||||
public abstract class ConfigDataLocation {
|
||||
|
||||
/**
|
||||
* Prefix used to indicate that a {@link ConfigDataLocation} is optional.
|
||||
*/
|
||||
public static final String OPTIONAL_PREFIX = "optional:";
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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.core.io.Resource;
|
||||
|
||||
/**
|
||||
* Exception thrown when a config data location cannot be found.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public class ConfigDataLocationNotFoundException extends ConfigDataException {
|
||||
|
||||
private final ConfigDataLocation location;
|
||||
|
||||
/**
|
||||
* Create a new {@link ConfigDataLocationNotFoundException} instance.
|
||||
* @param location the location that was not found
|
||||
*/
|
||||
public ConfigDataLocationNotFoundException(ConfigDataLocation location) {
|
||||
this(location, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link ConfigDataLocationNotFoundException} instance.
|
||||
* @param location the location that was not found
|
||||
* @param cause the cause of the exception
|
||||
*/
|
||||
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);
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the location that could not be found.
|
||||
* @return the location that could not be found.
|
||||
*/
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 void throwIfDoesNotExist(ConfigDataLocation location, boolean exists) {
|
||||
if (!exists) {
|
||||
throw new ConfigDataLocationNotFoundException(location);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -60,11 +60,14 @@ public interface ConfigDataLocationResolver<L extends ConfigDataLocation> {
|
||||
* Resolve a location string into one or more {@link ConfigDataLocation} 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.
|
||||
*
|
||||
* @throws ConfigDataLocationNotFoundException on a non-optional location that cannot
|
||||
* be found
|
||||
*/
|
||||
List<L> resolve(ConfigDataLocationResolverContext context, String location);
|
||||
List<L> resolve(ConfigDataLocationResolverContext context, String location, boolean optional)
|
||||
throws ConfigDataLocationNotFoundException;
|
||||
|
||||
/**
|
||||
* Resolve a location string into one or more {@link ConfigDataLocation} instances
|
||||
@@ -72,12 +75,15 @@ public interface ConfigDataLocationResolver<L extends ConfigDataLocation> {
|
||||
* 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.
|
||||
* @throws ConfigDataLocationNotFoundException on a non-optional location that cannot
|
||||
* be found
|
||||
*/
|
||||
default List<L> resolveProfileSpecific(ConfigDataLocationResolverContext context, String location,
|
||||
Profiles profiles) {
|
||||
default List<L> resolveProfileSpecific(ConfigDataLocationResolverContext context, String location, boolean optional,
|
||||
Profiles profiles) throws ConfigDataLocationNotFoundException {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.springframework.boot.context.config;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
|
||||
@@ -28,6 +29,7 @@ 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;
|
||||
|
||||
/**
|
||||
@@ -39,6 +41,8 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
class ConfigDataLocationResolvers {
|
||||
|
||||
private final Log logger;
|
||||
|
||||
private final List<ConfigDataLocationResolver<?>> resolvers;
|
||||
|
||||
/**
|
||||
@@ -61,6 +65,7 @@ class ConfigDataLocationResolvers {
|
||||
*/
|
||||
ConfigDataLocationResolvers(DeferredLogFactory logFactory, Binder binder, ResourceLoader resourceLoader,
|
||||
List<String> names) {
|
||||
this.logger = logFactory.getLog(getClass());
|
||||
Instantiator<ConfigDataLocationResolver<?>> instantiator = new Instantiator<>(ConfigDataLocationResolver.class,
|
||||
(availableParameters) -> {
|
||||
availableParameters.add(Log.class, logFactory::getLog);
|
||||
@@ -106,28 +111,49 @@ class ConfigDataLocationResolvers {
|
||||
|
||||
private List<ConfigDataLocation> resolveAll(ConfigDataLocationResolverContext context, String location,
|
||||
Profiles profiles) {
|
||||
boolean optional = location != null && location.startsWith(ConfigDataLocation.OPTIONAL_PREFIX);
|
||||
location = (!optional) ? location : location.substring(ConfigDataLocation.OPTIONAL_PREFIX.length());
|
||||
if (!StringUtils.hasText(location)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
for (ConfigDataLocationResolver<?> resolver : getResolvers()) {
|
||||
if (resolver.isResolvable(context, location)) {
|
||||
return resolve(resolver, context, location, profiles);
|
||||
return resolve(resolver, context, optional, location, profiles);
|
||||
}
|
||||
}
|
||||
throw new UnsupportedConfigDataLocationException(location);
|
||||
}
|
||||
|
||||
private List<ConfigDataLocation> resolve(ConfigDataLocationResolver<?> resolver,
|
||||
ConfigDataLocationResolverContext context, String location, Profiles profiles) {
|
||||
List<ConfigDataLocation> resolved = nonNullList(resolver.resolve(context, location));
|
||||
ConfigDataLocationResolverContext context, boolean optional, String location, Profiles profiles) {
|
||||
List<ConfigDataLocation> resolved = resolve(location, optional,
|
||||
() -> resolver.resolve(context, location, optional));
|
||||
if (profiles == null) {
|
||||
return resolved;
|
||||
}
|
||||
List<ConfigDataLocation> profileSpecific = nonNullList(
|
||||
resolver.resolveProfileSpecific(context, location, profiles));
|
||||
List<ConfigDataLocation> profileSpecific = resolve(location, optional,
|
||||
() -> resolver.resolveProfileSpecific(context, location, optional, profiles));
|
||||
return merge(resolved, profileSpecific);
|
||||
}
|
||||
|
||||
private List<ConfigDataLocation> resolve(String location, boolean optional,
|
||||
Supplier<List<? extends ConfigDataLocation>> resolveAction) {
|
||||
try {
|
||||
List<ConfigDataLocation> resolved = nonNullList(resolveAction.get());
|
||||
if (!resolved.isEmpty() && optional) {
|
||||
resolved = OptionalConfigDataLocation.wrapAll(resolved);
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
catch (ConfigDataLocationNotFoundException ex) {
|
||||
if (!optional) {
|
||||
throw ex;
|
||||
}
|
||||
this.logger.trace(LogMessage.format("Skipping missing resource from optional location %s", location));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> List<T> nonNullList(List<? extends T> list) {
|
||||
return (list != null) ? (List<T>) list : Collections.emptyList();
|
||||
|
||||
@@ -428,10 +428,23 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
|
||||
getSearchLocations().forEach((location) -> {
|
||||
boolean isDirectory = location.endsWith("/");
|
||||
Set<String> names = isDirectory ? getSearchNames() : NO_SEARCH_NAMES;
|
||||
names.forEach((name) -> load(location, name, profile, filterFactory, consumer));
|
||||
names.forEach((name) -> load(stripOptionalPrefix(location), 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)) {
|
||||
|
||||
@@ -32,6 +32,7 @@ class ConfigTreeConfigDataLoader implements ConfigDataLoader<ConfigTreeConfigDat
|
||||
|
||||
@Override
|
||||
public ConfigData load(ConfigDataLoaderContext context, ConfigTreeConfigDataLocation location) throws IOException {
|
||||
ConfigDataLocationNotFoundException.throwIfDoesNotExist(location, location.getPath());
|
||||
Path path = location.getPath();
|
||||
String name = "Config tree '" + path + "'";
|
||||
ConfigTreePropertySource source = new ConfigTreePropertySource(name, path);
|
||||
|
||||
@@ -35,7 +35,8 @@ class ConfigTreeConfigDataLocationResolver implements ConfigDataLocationResolver
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ConfigTreeConfigDataLocation> resolve(ConfigDataLocationResolverContext context, String location) {
|
||||
public List<ConfigTreeConfigDataLocation> resolve(ConfigDataLocationResolverContext context, String location,
|
||||
boolean optional) {
|
||||
ConfigTreeConfigDataLocation resolved = new ConfigTreeConfigDataLocation(location.substring(PREFIX.length()));
|
||||
return Collections.singletonList(resolved);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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<ConfigDataLocation> wrapAll(List<ConfigDataLocation> locations) {
|
||||
List<ConfigDataLocation> wrapped = new ArrayList<>(locations.size());
|
||||
locations.forEach((location) -> wrapped.add(new OptionalConfigDataLocation(location)));
|
||||
return wrapped;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static <L extends ConfigDataLocation> L unwrap(ConfigDataLocation wrapped) {
|
||||
return (L) ((OptionalConfigDataLocation) wrapped).getLocation();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -30,6 +30,7 @@ class ResourceConfigDataLoader implements ConfigDataLoader<ResourceConfigDataLoc
|
||||
|
||||
@Override
|
||||
public ConfigData load(ConfigDataLoaderContext context, ResourceConfigDataLocation location) throws IOException {
|
||||
ConfigDataLocationNotFoundException.throwIfDoesNotExist(location, location.getResource());
|
||||
return new ConfigData(location.load());
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,10 @@ class ResourceConfigDataLocation extends ConfigDataLocation {
|
||||
this.propertySourceLoader = propertySourceLoader;
|
||||
}
|
||||
|
||||
Resource getResource() {
|
||||
return this.resource;
|
||||
}
|
||||
|
||||
String getLocation() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@@ -110,23 +110,25 @@ class ResourceConfigDataLocationResolver implements ConfigDataLocationResolver<R
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ResourceConfigDataLocation> resolve(ConfigDataLocationResolverContext context, String location) {
|
||||
return resolve(location, getResolvables(context, location));
|
||||
public List<ResourceConfigDataLocation> resolve(ConfigDataLocationResolverContext context, String location,
|
||||
boolean optional) {
|
||||
return resolve(location, getResolvables(context, location, optional));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ResourceConfigDataLocation> resolveProfileSpecific(ConfigDataLocationResolverContext context,
|
||||
String location, Profiles profiles) {
|
||||
return resolve(location, getProfileSpecificResolvables(context, location, profiles));
|
||||
String location, boolean optional, Profiles profiles) {
|
||||
return resolve(location, getProfileSpecificResolvables(context, location, optional, profiles));
|
||||
}
|
||||
|
||||
private Set<Resolvable> getResolvables(ConfigDataLocationResolverContext context, String location) {
|
||||
private Set<Resolvable> getResolvables(ConfigDataLocationResolverContext context, String location,
|
||||
boolean optional) {
|
||||
String resourceLocation = getResourceLocation(context, location);
|
||||
try {
|
||||
if (isDirectoryLocation(resourceLocation)) {
|
||||
return getResolvablesForDirectory(resourceLocation, NO_PROFILE);
|
||||
return getResolvablesForDirectory(resourceLocation, optional, NO_PROFILE);
|
||||
}
|
||||
return getResolvablesForFile(resourceLocation, NO_PROFILE);
|
||||
return getResolvablesForFile(resourceLocation, optional, NO_PROFILE);
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
throw new IllegalStateException("Unable to load config data from '" + location + "'", ex);
|
||||
@@ -134,11 +136,11 @@ class ResourceConfigDataLocationResolver implements ConfigDataLocationResolver<R
|
||||
}
|
||||
|
||||
private Set<Resolvable> getProfileSpecificResolvables(ConfigDataLocationResolverContext context, String location,
|
||||
Profiles profiles) {
|
||||
boolean optional, Profiles profiles) {
|
||||
Set<Resolvable> resolvables = new LinkedHashSet<>();
|
||||
String resourceLocation = getResourceLocation(context, location);
|
||||
for (String profile : profiles) {
|
||||
resolvables.addAll(getResolvables(resourceLocation, profile));
|
||||
resolvables.addAll(getResolvables(resourceLocation, optional, profile));
|
||||
}
|
||||
return resolvables;
|
||||
}
|
||||
@@ -158,31 +160,34 @@ class ResourceConfigDataLocationResolver implements ConfigDataLocationResolver<R
|
||||
return resourceLocation;
|
||||
}
|
||||
|
||||
private Set<Resolvable> getResolvables(String resourceLocation, String profile) {
|
||||
private Set<Resolvable> getResolvables(String resourceLocation, boolean optional, String profile) {
|
||||
if (isDirectoryLocation(resourceLocation)) {
|
||||
return getResolvablesForDirectory(resourceLocation, profile);
|
||||
return getResolvablesForDirectory(resourceLocation, optional, profile);
|
||||
}
|
||||
return getResolvablesForFile(resourceLocation, profile);
|
||||
return getResolvablesForFile(resourceLocation, optional, profile);
|
||||
}
|
||||
|
||||
private Set<Resolvable> getResolvablesForDirectory(String resourceLocation, String profile) {
|
||||
private Set<Resolvable> getResolvablesForDirectory(String directoryLocation, boolean optional, String profile) {
|
||||
Set<Resolvable> resolvables = new LinkedHashSet<>();
|
||||
for (String name : this.configNames) {
|
||||
String rootLocation = directoryLocation + name;
|
||||
for (PropertySourceLoader loader : this.propertySourceLoaders) {
|
||||
for (String extension : loader.getFileExtensions()) {
|
||||
resolvables.add(new Resolvable(resourceLocation + name, profile, extension, loader));
|
||||
Resolvable resolvable = new Resolvable(directoryLocation, rootLocation, optional, profile,
|
||||
extension, loader);
|
||||
resolvables.add(resolvable);
|
||||
}
|
||||
}
|
||||
}
|
||||
return resolvables;
|
||||
}
|
||||
|
||||
private Set<Resolvable> getResolvablesForFile(String resourceLocation, String profile) {
|
||||
private Set<Resolvable> getResolvablesForFile(String fileLocation, boolean optional, String profile) {
|
||||
for (PropertySourceLoader loader : this.propertySourceLoaders) {
|
||||
String extension = getLoadableFileExtension(loader, resourceLocation);
|
||||
String extension = getLoadableFileExtension(loader, fileLocation);
|
||||
if (extension != null) {
|
||||
String root = resourceLocation.substring(0, resourceLocation.length() - extension.length() - 1);
|
||||
return Collections.singleton(new Resolvable(root, profile, extension, loader));
|
||||
String root = fileLocation.substring(0, fileLocation.length() - extension.length() - 1);
|
||||
return Collections.singleton(new Resolvable(null, root, optional, profile, extension, loader));
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("File extension is not known to any PropertySourceLoader. "
|
||||
@@ -207,9 +212,23 @@ class ResourceConfigDataLocationResolver implements ConfigDataLocationResolver<R
|
||||
for (Resolvable resolvable : resolvables) {
|
||||
resolved.addAll(resolve(location, resolvable));
|
||||
}
|
||||
if (resolved.isEmpty()) {
|
||||
assertNonOptionalDirectories(location, resolvables);
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
private void assertNonOptionalDirectories(String location, Set<Resolvable> resolvables) {
|
||||
for (Resolvable resolvable : resolvables) {
|
||||
if (resolvable.isNonOptionalDirectory()) {
|
||||
Resource resource = this.resourceLoader.getResource(resolvable.getDirectory());
|
||||
ResourceConfigDataLocation resourceLocation = createConfigResourceLocation(location, resolvable,
|
||||
resource);
|
||||
ConfigDataLocationNotFoundException.throwIfDoesNotExist(resourceLocation, resource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<ResourceConfigDataLocation> resolve(String location, Resolvable resolvable) {
|
||||
if (!resolvable.isPatternLocation()) {
|
||||
return resolveNonPattern(location, resolvable);
|
||||
@@ -219,23 +238,22 @@ class ResourceConfigDataLocationResolver implements ConfigDataLocationResolver<R
|
||||
|
||||
private List<ResourceConfigDataLocation> resolveNonPattern(String location, Resolvable resolvable) {
|
||||
Resource resource = loadResource(resolvable.getResourceLocation());
|
||||
if (resource.exists()) {
|
||||
ResourceConfigDataLocation resolved = createConfigResourceLocation(location, resolvable, resource);
|
||||
return Collections.singletonList(resolved);
|
||||
if (!resource.exists() && resolvable.isSkippable()) {
|
||||
logSkippingResource(resolvable);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
logSkippingResource(resolvable);
|
||||
return Collections.emptyList();
|
||||
return Collections.singletonList(createConfigResourceLocation(location, resolvable, resource));
|
||||
}
|
||||
|
||||
private List<ResourceConfigDataLocation> resolvePattern(String location, Resolvable resolvable) {
|
||||
validatePatternLocation(resolvable.getResourceLocation());
|
||||
List<ResourceConfigDataLocation> resolved = new ArrayList<>();
|
||||
for (Resource resource : getResourcesFromResourceLocationPattern(resolvable.getResourceLocation())) {
|
||||
if (resource.exists()) {
|
||||
resolved.add(createConfigResourceLocation(location, resolvable, resource));
|
||||
if (!resource.exists() && resolvable.isSkippable()) {
|
||||
logSkippingResource(resolvable);
|
||||
}
|
||||
else {
|
||||
logSkippingResource(resolvable);
|
||||
resolved.add(createConfigResourceLocation(location, resolvable, resource));
|
||||
}
|
||||
}
|
||||
return resolved;
|
||||
@@ -267,7 +285,7 @@ class ResourceConfigDataLocationResolver implements ConfigDataLocationResolver<R
|
||||
String fileName = resourceLocationPattern.substring(resourceLocationPattern.lastIndexOf("/") + 1);
|
||||
Resource directoryResource = loadResource(directoryPath);
|
||||
if (!directoryResource.exists()) {
|
||||
return EMPTY_RESOURCES;
|
||||
return new Resource[] { directoryResource };
|
||||
}
|
||||
File directory = getDirectory(resourceLocationPattern, directoryResource);
|
||||
File[] subDirectories = directory.listFiles(File::isDirectory);
|
||||
@@ -311,16 +329,38 @@ class ResourceConfigDataLocationResolver implements ConfigDataLocationResolver<R
|
||||
*/
|
||||
private static class Resolvable {
|
||||
|
||||
private final String directory;
|
||||
|
||||
private final String resourceLocation;
|
||||
|
||||
private final boolean optional;
|
||||
|
||||
private final String profile;
|
||||
|
||||
private final PropertySourceLoader loader;
|
||||
|
||||
Resolvable(String rootLocation, String profile, String extension, PropertySourceLoader loader) {
|
||||
Resolvable(String directory, String rootLocation, boolean optional, String profile, String extension,
|
||||
PropertySourceLoader loader) {
|
||||
String profileSuffix = (StringUtils.hasText(profile)) ? "-" + profile : "";
|
||||
this.directory = directory;
|
||||
this.resourceLocation = rootLocation + profileSuffix + "." + extension;
|
||||
this.optional = optional;
|
||||
this.profile = profile;
|
||||
this.loader = loader;
|
||||
}
|
||||
|
||||
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("*");
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests {
|
||||
@Test
|
||||
void runWhenOneCustomLocationDoesNotExistLoadsOthers() {
|
||||
ConfigurableApplicationContext context = this.application.run(
|
||||
"--spring.config.location=classpath:application.properties,classpath:testproperties.properties,classpath:nonexistent.properties");
|
||||
"--spring.config.location=classpath:application.properties,classpath:testproperties.properties,optional:classpath:nonexistent.properties");
|
||||
String property = context.getEnvironment().getProperty("the.property");
|
||||
assertThat(property).isEqualTo("frompropertiesfile");
|
||||
}
|
||||
@@ -503,11 +503,18 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void runWhenConfigLocationHasUnknownDirectoryContinuesToLoad() {
|
||||
String location = "classpath:application.unknown/";
|
||||
void runWhenConfigLocationHasOptionalMissingDirectoryContinuesToLoad() {
|
||||
String location = "optional:classpath:application.unknown/";
|
||||
this.application.run("--spring.config.location=" + location);
|
||||
}
|
||||
|
||||
@Test
|
||||
void runWhenConfigLocationHasNonOptionalMissingDirectoryThrowsException() {
|
||||
String location = "classpath:application.unknown/";
|
||||
assertThatExceptionOfType(ConfigDataLocationNotFoundException.class)
|
||||
.isThrownBy(() -> this.application.run("--spring.config.location=" + location));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("Disabled until spring.profiles suppport is dropped")
|
||||
void runWhenUsingInvalidPropertyThrowsException() {
|
||||
@@ -536,6 +543,12 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests {
|
||||
.withCauseInstanceOf(InactiveConfigDataAccessException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void runWhenHasNonOptionalImportThrowsException() {
|
||||
assertThatExceptionOfType(ConfigDataLocationNotFoundException.class).isThrownBy(
|
||||
() -> this.application.run("--spring.config.location=classpath:missing-appplication.properties"));
|
||||
}
|
||||
|
||||
private Condition<ConfigurableEnvironment> matchingPropertySource(final String sourceName) {
|
||||
return new Condition<ConfigurableEnvironment>("environment containing property source " + sourceName) {
|
||||
|
||||
|
||||
@@ -198,7 +198,7 @@ class ConfigDataEnvironmentTests {
|
||||
}
|
||||
|
||||
private String getConfigLocation(TestInfo info) {
|
||||
return "classpath:" + info.getTestClass().get().getName().replace('.', '/') + "-"
|
||||
return "optional:classpath:" + info.getTestClass().get().getName().replace('.', '/') + "-"
|
||||
+ info.getTestMethod().get().getName() + ".properties";
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link ConfigDataLocationNotFoundException}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class ConfigDataLocationNotFoundExceptionTests {
|
||||
|
||||
private ConfigDataLocation location = mock(ConfigDataLocation.class);
|
||||
|
||||
private Throwable cause = new RuntimeException();
|
||||
|
||||
private String message = "message";
|
||||
|
||||
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 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);
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void throwIfDoesNotExistWhenFileDoesNotExistThrowsException() {
|
||||
assertThatExceptionOfType(ConfigDataLocationNotFoundException.class)
|
||||
.isThrownBy(() -> ConfigDataLocationNotFoundException.throwIfDoesNotExist(this.location, this.missing));
|
||||
}
|
||||
|
||||
@Test
|
||||
void throwIfDoesNotExistWhenResourceExistsDoesNothing() {
|
||||
ConfigDataLocationNotFoundException.throwIfDoesNotExist(this.location, new FileSystemResource(this.exists));
|
||||
}
|
||||
|
||||
@Test
|
||||
void throwIfDoesNotExistWhenResourceDoesNotExistThrowsException() {
|
||||
assertThatExceptionOfType(ConfigDataLocationNotFoundException.class)
|
||||
.isThrownBy(() -> ConfigDataLocationNotFoundException.throwIfDoesNotExist(this.location,
|
||||
new FileSystemResource(this.missing)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -37,7 +37,7 @@ class ConfigDataLocationResolverTests {
|
||||
|
||||
@Test
|
||||
void resolveProfileSpecificReturnsEmptyList() {
|
||||
assertThat(this.resolver.resolveProfileSpecific(this.context, null, null)).isEmpty();
|
||||
assertThat(this.resolver.resolveProfileSpecific(this.context, null, true, null)).isEmpty();
|
||||
}
|
||||
|
||||
static class TestConfigDataLocationResolver implements ConfigDataLocationResolver<ConfigDataLocation> {
|
||||
@@ -48,7 +48,8 @@ class ConfigDataLocationResolverTests {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ConfigDataLocation> resolve(ConfigDataLocationResolverContext context, String location) {
|
||||
public List<ConfigDataLocation> resolve(ConfigDataLocationResolverContext context, String location,
|
||||
boolean optional) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -151,13 +151,14 @@ class ConfigDataLocationResolversTests {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TestConfigDataLocation> resolve(ConfigDataLocationResolverContext context, String location) {
|
||||
public List<TestConfigDataLocation> resolve(ConfigDataLocationResolverContext context, String location,
|
||||
boolean optional) {
|
||||
return Collections.singletonList(new TestConfigDataLocation(this, location, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TestConfigDataLocation> resolveProfileSpecific(ConfigDataLocationResolverContext context,
|
||||
String location, Profiles profiles) {
|
||||
String location, boolean optional, Profiles profiles) {
|
||||
return Collections.singletonList(new TestConfigDataLocation(this, location, true));
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
@@ -58,4 +59,12 @@ public class ConfigTreeConfigDataLoaderTests {
|
||||
assertThat(source.getProperty("hello").toString()).isEqualTo("world");
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadWhenPathDoesNotExistThrowsException() {
|
||||
File missing = this.directory.resolve("missing").toFile();
|
||||
ConfigTreeConfigDataLocation location = new ConfigTreeConfigDataLocation(missing.toString());
|
||||
assertThatExceptionOfType(ConfigDataLocationNotFoundException.class)
|
||||
.isThrownBy(() -> this.loader.load(this.loaderContext, location));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -49,7 +49,8 @@ class ConfigTreeConfigDataLocationResolverTests {
|
||||
|
||||
@Test
|
||||
void resolveReturnsConfigVolumeMountLocation() {
|
||||
List<ConfigTreeConfigDataLocation> locations = this.resolver.resolve(this.context, "configtree:/etc/config");
|
||||
List<ConfigTreeConfigDataLocation> locations = this.resolver.resolve(this.context, "configtree:/etc/config",
|
||||
false);
|
||||
assertThat(locations.size()).isEqualTo(1);
|
||||
assertThat(locations).extracting(Object::toString)
|
||||
.containsExactly("config tree [" + new File("/etc/config").getAbsolutePath() + "]");
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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"), mock(PropertySourceLoader.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWrapsLocation() {
|
||||
OptionalConfigDataLocation optionalLocation = new OptionalConfigDataLocation(this.location);
|
||||
assertThat(optionalLocation.getLocation()).isSameAs(this.location);
|
||||
}
|
||||
|
||||
@Test
|
||||
void equalsAndHashCode() {
|
||||
OptionalConfigDataLocation optionalLocation1 = new OptionalConfigDataLocation(this.location);
|
||||
OptionalConfigDataLocation optionalLocation2 = new OptionalConfigDataLocation(this.location);
|
||||
assertThat(optionalLocation1.hashCode()).isEqualTo(optionalLocation2.hashCode());
|
||||
assertThat(optionalLocation1).isEqualTo(optionalLocation1).isEqualTo(optionalLocation2)
|
||||
.isNotEqualTo(this.location);
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringReturnsLocationString() {
|
||||
OptionalConfigDataLocation optionalLocation = new OptionalConfigDataLocation(this.location);
|
||||
assertThat(optionalLocation).hasToString(this.location.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void wrapAllWrapsList() {
|
||||
List<ConfigDataLocation> locations = Collections.singletonList(this.location);
|
||||
List<ConfigDataLocation> optionalLocations = OptionalConfigDataLocation.wrapAll(locations);
|
||||
assertThat(optionalLocations).hasSize(1);
|
||||
assertThat(optionalLocations.get(0)).isInstanceOf(OptionalConfigDataLocation.class).extracting("location")
|
||||
.isSameAs(this.location);
|
||||
}
|
||||
|
||||
@Test
|
||||
void unwrapUnwrapps() {
|
||||
ConfigDataLocation optionalLocation = new OptionalConfigDataLocation(this.location);
|
||||
assertThatObject(OptionalConfigDataLocation.unwrap(optionalLocation)).isSameAs(this.location);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -70,7 +70,7 @@ public class ResourceConfigDataLocationResolverTests {
|
||||
@Test
|
||||
void resolveWhenLocationIsDirectoryResolvesAllMatchingFilesInDirectory() {
|
||||
String location = "classpath:/configdata/properties/";
|
||||
List<ResourceConfigDataLocation> locations = this.resolver.resolve(this.context, location);
|
||||
List<ResourceConfigDataLocation> locations = this.resolver.resolve(this.context, location, true);
|
||||
assertThat(locations.size()).isEqualTo(1);
|
||||
assertThat(locations).extracting(Object::toString)
|
||||
.containsExactly("class path resource [configdata/properties/application.properties]");
|
||||
@@ -79,7 +79,7 @@ public class ResourceConfigDataLocationResolverTests {
|
||||
@Test
|
||||
void resolveWhenLocationIsFileResolvesFile() {
|
||||
String location = "file:src/test/resources/configdata/properties/application.properties";
|
||||
List<ResourceConfigDataLocation> locations = this.resolver.resolve(this.context, location);
|
||||
List<ResourceConfigDataLocation> locations = this.resolver.resolve(this.context, location, true);
|
||||
assertThat(locations.size()).isEqualTo(1);
|
||||
assertThat(locations).extracting(Object::toString).containsExactly(
|
||||
filePath("src", "test", "resources", "configdata", "properties", "application.properties"));
|
||||
@@ -88,7 +88,7 @@ public class ResourceConfigDataLocationResolverTests {
|
||||
@Test
|
||||
void resolveWhenLocationIsFileAndNoMatchingLoaderThrowsException() {
|
||||
String location = "file:src/test/resources/configdata/properties/application.unknown";
|
||||
assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location))
|
||||
assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location, true))
|
||||
.withMessageStartingWith("Unable to load config data from")
|
||||
.satisfies((ex) -> assertThat(ex.getCause()).hasMessageStartingWith("File extension is not known"));
|
||||
}
|
||||
@@ -96,14 +96,14 @@ public class ResourceConfigDataLocationResolverTests {
|
||||
@Test
|
||||
void resolveWhenLocationWildcardIsSpecifiedForClasspathLocationThrowsException() {
|
||||
String location = "classpath*:application.properties";
|
||||
assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location))
|
||||
assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location, true))
|
||||
.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))
|
||||
assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location, true))
|
||||
.withMessageStartingWith("Search location '").withMessageEndingWith("' must end with '*/'");
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ public class ResourceConfigDataLocationResolverTests {
|
||||
@Test
|
||||
void resolveWhenLocationHasMultipleWildcardsThrowsException() {
|
||||
String location = "file:src/test/resources/config/**/";
|
||||
assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location))
|
||||
assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location, true))
|
||||
.withMessageStartingWith("Search location '")
|
||||
.withMessageEndingWith("' cannot contain multiple wildcards");
|
||||
}
|
||||
@@ -129,7 +129,7 @@ public class ResourceConfigDataLocationResolverTests {
|
||||
String location = "file:src/test/resources/config/*/";
|
||||
this.environment.setProperty("spring.config.name", "testproperties");
|
||||
this.resolver = new ResourceConfigDataLocationResolver(null, this.environmentBinder, this.resourceLoader);
|
||||
List<ResourceConfigDataLocation> locations = this.resolver.resolve(this.context, location);
|
||||
List<ResourceConfigDataLocation> locations = this.resolver.resolve(this.context, location, true);
|
||||
assertThat(locations.size()).isEqualTo(3);
|
||||
assertThat(locations).extracting(Object::toString)
|
||||
.contains(filePath("src", "test", "resources", "config", "1-first", "testproperties.properties"))
|
||||
@@ -142,7 +142,7 @@ public class ResourceConfigDataLocationResolverTests {
|
||||
String location = "file:src/test/resources/config/*/";
|
||||
this.environment.setProperty("spring.config.name", "testproperties");
|
||||
this.resolver = new ResourceConfigDataLocationResolver(null, this.environmentBinder, this.resourceLoader);
|
||||
List<ResourceConfigDataLocation> locations = this.resolver.resolve(this.context, location);
|
||||
List<ResourceConfigDataLocation> locations = this.resolver.resolve(this.context, location, true);
|
||||
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,7 +152,7 @@ public class ResourceConfigDataLocationResolverTests {
|
||||
@Test
|
||||
void resolveWhenLocationIsWildcardFilesLoadsAllFilesThatMatch() {
|
||||
String location = "file:src/test/resources/config/*/testproperties.properties";
|
||||
List<ResourceConfigDataLocation> locations = this.resolver.resolve(this.context, location);
|
||||
List<ResourceConfigDataLocation> locations = this.resolver.resolve(this.context, location, true);
|
||||
assertThat(locations.size()).isEqualTo(3);
|
||||
assertThat(locations).extracting(Object::toString)
|
||||
.contains(filePath("src", "test", "resources", "config", "1-first", "testproperties.properties"))
|
||||
@@ -172,7 +172,7 @@ public class ResourceConfigDataLocationResolverTests {
|
||||
"classpath:/configdata/properties/application.properties", parentResource,
|
||||
new PropertiesPropertySourceLoader());
|
||||
given(this.context.getParent()).willReturn(parent);
|
||||
List<ResourceConfigDataLocation> locations = this.resolver.resolve(this.context, location);
|
||||
List<ResourceConfigDataLocation> locations = this.resolver.resolve(this.context, location, true);
|
||||
assertThat(locations.size()).isEqualTo(1);
|
||||
assertThat(locations).extracting(Object::toString)
|
||||
.contains("class path resource [configdata/properties/other.properties]");
|
||||
@@ -188,7 +188,7 @@ public class ResourceConfigDataLocationResolverTests {
|
||||
ResourceConfigDataLocation parent = new ResourceConfigDataLocation("classpath:/config/specific.properties",
|
||||
parentResource, new PropertiesPropertySourceLoader());
|
||||
given(this.context.getParent()).willReturn(parent);
|
||||
List<ResourceConfigDataLocation> locations = this.resolver.resolve(this.context, location);
|
||||
List<ResourceConfigDataLocation> locations = this.resolver.resolve(this.context, location, true);
|
||||
assertThat(locations.size()).isEqualTo(1);
|
||||
assertThat(locations).extracting(Object::toString)
|
||||
.contains("class path resource [config/nested/3-third/testproperties.properties]");
|
||||
@@ -201,7 +201,7 @@ public class ResourceConfigDataLocationResolverTests {
|
||||
ResourceConfigDataLocation parent = new ResourceConfigDataLocation(
|
||||
"classpath:/configdata/application.properties", parentResource, new PropertiesPropertySourceLoader());
|
||||
given(this.context.getParent()).willReturn(parent);
|
||||
assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location))
|
||||
assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location, true))
|
||||
.withMessageStartingWith("Unable to load config data from 'application.other'")
|
||||
.satisfies((ex) -> assertThat(ex.getCause()).hasMessageStartingWith("File extension is not known"));
|
||||
}
|
||||
@@ -211,7 +211,7 @@ public class ResourceConfigDataLocationResolverTests {
|
||||
String location = "classpath:/configdata/properties/";
|
||||
Profiles profiles = mock(Profiles.class);
|
||||
given(profiles.iterator()).willReturn(Collections.singletonList("dev").iterator());
|
||||
List<ResourceConfigDataLocation> locations = this.resolver.resolveProfileSpecific(this.context, location,
|
||||
List<ResourceConfigDataLocation> locations = this.resolver.resolveProfileSpecific(this.context, location, true,
|
||||
profiles);
|
||||
assertThat(locations.size()).isEqualTo(1);
|
||||
assertThat(locations).extracting(Object::toString)
|
||||
@@ -224,7 +224,7 @@ public class ResourceConfigDataLocationResolverTests {
|
||||
Profiles profiles = mock(Profiles.class);
|
||||
given(profiles.iterator()).willReturn(Collections.emptyIterator());
|
||||
given(profiles.getActive()).willReturn(Collections.singletonList("dev"));
|
||||
List<ResourceConfigDataLocation> locations = this.resolver.resolveProfileSpecific(this.context, location,
|
||||
List<ResourceConfigDataLocation> locations = this.resolver.resolveProfileSpecific(this.context, location, true,
|
||||
profiles);
|
||||
assertThat(locations).isEmpty();
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ class TestConfigDataBootstrap {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Location> resolve(ConfigDataLocationResolverContext context, String location) {
|
||||
public List<Location> resolve(ConfigDataLocationResolverContext context, String location, boolean optional) {
|
||||
ResolverHelper helper = context.getBootstrapRegistry().get(ResolverHelper.class,
|
||||
() -> new ResolverHelper(location));
|
||||
return Collections.singletonList(new Location(helper));
|
||||
|
||||
Reference in New Issue
Block a user