Support wildcard configtree imports
Update `ConfigTreeConfigDataResource` so that a wildcard suffix can be used to import multiple folders. The pattern logic from `StandardConfigDataLocationResolver` has been extracted into a new `LocationResourceLoader` class so that it can be reused. Closes gh-22958
This commit is contained in:
parent
8b6b0505fb
commit
a0862f9146
|
|
@ -796,13 +796,46 @@ To import these properties, you can add the following to your `application.prope
|
|||
----
|
||||
spring:
|
||||
config:
|
||||
import: "optional:configtree:/etc/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.
|
||||
|
||||
TIP: Configuration tree values can be bound to both string `String` and `byte[]` types depending on the contents expected.
|
||||
|
||||
If you have multiple config trees to import from the same parent folder you can use a wildcard shortcut.
|
||||
Any `configtree:` location that ends with `/*/` will import all immediate children as config trees.
|
||||
|
||||
For example, given the following volume:
|
||||
|
||||
[source,indent=0]
|
||||
----
|
||||
etc/
|
||||
config/
|
||||
dbconfig/
|
||||
db/
|
||||
username
|
||||
password
|
||||
mqconfig/
|
||||
mq/
|
||||
username
|
||||
password
|
||||
----
|
||||
|
||||
You can use `configtree:/etc/config/*/` as the import location:
|
||||
|
||||
[source,yaml,indent=0,configprops,configblocks]
|
||||
----
|
||||
spring:
|
||||
config:
|
||||
import: "optional:configtree:/etc/config/*/"
|
||||
----
|
||||
|
||||
This will add `db.username`, `db.password`, `mq.username` and `mq.password` properties.
|
||||
|
||||
NOTE: Directories loaded using a wildcard are sorted alphabetically.
|
||||
If you need a different order, then you should list each location as a separate import
|
||||
|
||||
|
||||
|
||||
[[boot-features-external-config-placeholders-in-properties]]
|
||||
|
|
|
|||
|
|
@ -16,9 +16,16 @@
|
|||
|
||||
package org.springframework.boot.context.config;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.context.config.LocationResourceLoader.ResourceType;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link ConfigDataLocationResolver} for config tree locations.
|
||||
*
|
||||
|
|
@ -30,6 +37,12 @@ public class ConfigTreeConfigDataLocationResolver implements ConfigDataLocationR
|
|||
|
||||
private static final String PREFIX = "configtree:";
|
||||
|
||||
private final LocationResourceLoader resourceLoader;
|
||||
|
||||
public ConfigTreeConfigDataLocationResolver(ResourceLoader resourceLoader) {
|
||||
this.resourceLoader = new LocationResourceLoader(resourceLoader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location) {
|
||||
return location.hasPrefix(PREFIX);
|
||||
|
|
@ -38,8 +51,27 @@ public class ConfigTreeConfigDataLocationResolver implements ConfigDataLocationR
|
|||
@Override
|
||||
public List<ConfigTreeConfigDataResource> resolve(ConfigDataLocationResolverContext context,
|
||||
ConfigDataLocation location) {
|
||||
ConfigTreeConfigDataResource resolved = new ConfigTreeConfigDataResource(location.getNonPrefixedValue(PREFIX));
|
||||
return Collections.singletonList(resolved);
|
||||
try {
|
||||
return resolve(context, location.getNonPrefixedValue(PREFIX));
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new ConfigDataLocationNotFoundException(location, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private List<ConfigTreeConfigDataResource> resolve(ConfigDataLocationResolverContext context, String location)
|
||||
throws IOException {
|
||||
Assert.isTrue(location.endsWith("/"),
|
||||
() -> String.format("Config tree location '%s' must end with '/'", location));
|
||||
if (!this.resourceLoader.isPattern(location)) {
|
||||
return Collections.singletonList(new ConfigTreeConfigDataResource(location));
|
||||
}
|
||||
Resource[] resources = this.resourceLoader.getResources(location, ResourceType.DIRECTORY);
|
||||
List<ConfigTreeConfigDataResource> resolved = new ArrayList<>(resources.length);
|
||||
for (Resource resource : resources) {
|
||||
resolved.add(new ConfigTreeConfigDataResource(resource.getFile().toPath()));
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,11 @@ public class ConfigTreeConfigDataResource extends ConfigDataResource {
|
|||
this.path = Paths.get(path).toAbsolutePath();
|
||||
}
|
||||
|
||||
ConfigTreeConfigDataResource(Path path) {
|
||||
Assert.notNull(path, "Path must not be null");
|
||||
this.path = path.toAbsolutePath();
|
||||
}
|
||||
|
||||
Path getPath() {
|
||||
return this.path;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.FilenameFilter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
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.util.Assert;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Strategy interface for loading resources from a location. Supports single resource and
|
||||
* simple wildcard directory patterns.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
class LocationResourceLoader {
|
||||
|
||||
private static final Resource[] EMPTY_RESOURCES = {};
|
||||
|
||||
private static final Comparator<File> FILE_PATH_COMPARATOR = Comparator.comparing(File::getAbsolutePath);
|
||||
|
||||
private static final Comparator<File> FILE_NAME_COMPARATOR = Comparator.comparing(File::getName);
|
||||
|
||||
private final ResourceLoader resourceLoader;
|
||||
|
||||
/**
|
||||
* Create a new {@link LocationResourceLoader} instance.
|
||||
* @param resourceLoader the underlying resource loader
|
||||
*/
|
||||
LocationResourceLoader(ResourceLoader resourceLoader) {
|
||||
this.resourceLoader = resourceLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the location contains a pattern.
|
||||
* @param location the location to check
|
||||
* @return if the location is a pattern
|
||||
*/
|
||||
boolean isPattern(String location) {
|
||||
return StringUtils.hasLength(location) && location.contains("*");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single resource from a non-pattern location.
|
||||
* @param location the location
|
||||
* @return the resource
|
||||
* @see #isPattern(String)
|
||||
*/
|
||||
Resource getResource(String location) {
|
||||
validateNonPattern(location);
|
||||
location = StringUtils.cleanPath(location);
|
||||
if (!ResourceUtils.isUrl(location)) {
|
||||
location = ResourceUtils.FILE_URL_PREFIX + location;
|
||||
}
|
||||
return this.resourceLoader.getResource(location);
|
||||
}
|
||||
|
||||
private void validateNonPattern(String location) {
|
||||
Assert.state(!isPattern(location), () -> String.format("Location '%s' must not be a pattern", location));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a multiple resources from a location pattern.
|
||||
* @param location the location pattern
|
||||
* @param type the type of resource to return
|
||||
* @return the resources
|
||||
* @see #isPattern(String)
|
||||
*/
|
||||
Resource[] getResources(String location, ResourceType type) {
|
||||
validatePattern(location, type);
|
||||
String directoryPath = location.substring(0, location.indexOf("*/"));
|
||||
String fileName = location.substring(location.lastIndexOf("/") + 1);
|
||||
Resource directoryResource = getResource(directoryPath);
|
||||
if (!directoryResource.exists()) {
|
||||
return new Resource[] { directoryResource };
|
||||
}
|
||||
File directory = getDirectory(location, directoryResource);
|
||||
File[] subDirectories = directory.listFiles(this::isVisibleDirectory);
|
||||
if (subDirectories == null) {
|
||||
return EMPTY_RESOURCES;
|
||||
}
|
||||
Arrays.sort(subDirectories, FILE_PATH_COMPARATOR);
|
||||
if (type == ResourceType.DIRECTORY) {
|
||||
return Arrays.stream(subDirectories).map(FileSystemResource::new).toArray(Resource[]::new);
|
||||
}
|
||||
List<Resource> resources = new ArrayList<>();
|
||||
FilenameFilter filter = (dir, name) -> name.equals(fileName);
|
||||
for (File subDirectory : subDirectories) {
|
||||
File[] files = subDirectory.listFiles(filter);
|
||||
if (files != null) {
|
||||
Arrays.sort(files, FILE_NAME_COMPARATOR);
|
||||
Arrays.stream(files).map(FileSystemResource::new).forEach(resources::add);
|
||||
}
|
||||
}
|
||||
return resources.toArray(EMPTY_RESOURCES);
|
||||
}
|
||||
|
||||
private void validatePattern(String location, ResourceType type) {
|
||||
Assert.state(isPattern(location), () -> String.format("Location '%s' must be a pattern", location));
|
||||
Assert.state(!location.startsWith(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX),
|
||||
() -> String.format("Location '%s' cannot use classpath wildcards", location));
|
||||
Assert.state(StringUtils.countOccurrencesOf(location, "*") == 1,
|
||||
() -> String.format("Location '%s' cannot contain multiple wildcards", location));
|
||||
String directoryPath = (type != ResourceType.DIRECTORY) ? location.substring(0, location.lastIndexOf("/") + 1)
|
||||
: location;
|
||||
Assert.state(directoryPath.endsWith("*/"), () -> String.format("Location '%s' must end with '*/'", 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);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isVisibleDirectory(File file) {
|
||||
return file.isDirectory() && !file.getName().startsWith(".");
|
||||
}
|
||||
|
||||
/**
|
||||
* Resource types that can be returned.
|
||||
*/
|
||||
enum ResourceType {
|
||||
|
||||
/**
|
||||
* Return file resources.
|
||||
*/
|
||||
FILE,
|
||||
|
||||
/**
|
||||
* Return directory resources.
|
||||
*/
|
||||
DIRECTORY
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -16,12 +16,8 @@
|
|||
|
||||
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;
|
||||
|
|
@ -30,18 +26,16 @@ import java.util.regex.Pattern;
|
|||
|
||||
import org.apache.commons.logging.Log;
|
||||
|
||||
import org.springframework.boot.context.config.LocationResourceLoader.ResourceType;
|
||||
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;
|
||||
|
||||
/**
|
||||
|
|
@ -61,10 +55,6 @@ public class StandardConfigDataLocationResolver
|
|||
|
||||
private static final String[] DEFAULT_CONFIG_NAMES = { "application" };
|
||||
|
||||
private static final Resource[] EMPTY_RESOURCES = {};
|
||||
|
||||
private static final Comparator<File> 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+)\\](?!\\[)$");
|
||||
|
|
@ -77,7 +67,7 @@ public class StandardConfigDataLocationResolver
|
|||
|
||||
private final String[] configNames;
|
||||
|
||||
private final ResourceLoader resourceLoader;
|
||||
private final LocationResourceLoader resourceLoader;
|
||||
|
||||
/**
|
||||
* Create a new {@link StandardConfigDataLocationResolver} instance.
|
||||
|
|
@ -90,7 +80,7 @@ public class StandardConfigDataLocationResolver
|
|||
this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
|
||||
getClass().getClassLoader());
|
||||
this.configNames = getConfigNames(binder);
|
||||
this.resourceLoader = resourceLoader;
|
||||
this.resourceLoader = new LocationResourceLoader(resourceLoader);
|
||||
}
|
||||
|
||||
private String[] getConfigNames(Binder binder) {
|
||||
|
|
@ -243,20 +233,20 @@ public class StandardConfigDataLocationResolver
|
|||
}
|
||||
|
||||
private void assertDirectoryExists(StandardConfigDataReference reference) {
|
||||
Resource resource = loadResource(reference.getDirectory());
|
||||
Resource resource = this.resourceLoader.getResource(reference.getDirectory());
|
||||
StandardConfigDataResource configDataResource = new StandardConfigDataResource(reference, resource);
|
||||
ConfigDataResourceNotFoundException.throwIfDoesNotExist(configDataResource, resource);
|
||||
}
|
||||
|
||||
private List<StandardConfigDataResource> resolve(StandardConfigDataReference reference) {
|
||||
if (!reference.isPatternLocation()) {
|
||||
if (!this.resourceLoader.isPattern(reference.getResourceLocation())) {
|
||||
return resolveNonPattern(reference);
|
||||
}
|
||||
return resolvePattern(reference);
|
||||
}
|
||||
|
||||
private List<StandardConfigDataResource> resolveNonPattern(StandardConfigDataReference reference) {
|
||||
Resource resource = loadResource(reference.getResourceLocation());
|
||||
Resource resource = this.resourceLoader.getResource(reference.getResourceLocation());
|
||||
if (!resource.exists() && reference.isSkippable()) {
|
||||
logSkippingResource(reference);
|
||||
return Collections.emptyList();
|
||||
|
|
@ -265,9 +255,8 @@ public class StandardConfigDataLocationResolver
|
|||
}
|
||||
|
||||
private List<StandardConfigDataResource> resolvePattern(StandardConfigDataReference reference) {
|
||||
validatePatternLocation(reference.getResourceLocation());
|
||||
List<StandardConfigDataResource> resolved = new ArrayList<>();
|
||||
for (Resource resource : getResourcesFromResourceLocationPattern(reference.getResourceLocation())) {
|
||||
for (Resource resource : this.resourceLoader.getResources(reference.getResourceLocation(), ResourceType.FILE)) {
|
||||
if (!resource.exists() && reference.isSkippable()) {
|
||||
logSkippingResource(reference);
|
||||
}
|
||||
|
|
@ -287,62 +276,4 @@ public class StandardConfigDataLocationResolver
|
|||
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(this::isVisibleDirectory);
|
||||
if (subDirectories == null) {
|
||||
return EMPTY_RESOURCES;
|
||||
}
|
||||
Arrays.sort(subDirectories, FILE_COMPARATOR);
|
||||
List<Resource> 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);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isVisibleDirectory(File file) {
|
||||
return file.isDirectory() && !file.getName().startsWith(".");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,10 +78,6 @@ class StandardConfigDataReference {
|
|||
return this.configDataLocation.isOptional() || this.directory != null || this.profile != null;
|
||||
}
|
||||
|
||||
boolean isPatternLocation() {
|
||||
return this.resourceLocation.contains("*");
|
||||
}
|
||||
|
||||
PropertySourceLoader getPropertySourceLoader() {
|
||||
return this.propertySourceLoader;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,10 @@ import java.io.File;
|
|||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
|
@ -32,10 +36,14 @@ import static org.mockito.Mockito.mock;
|
|||
*/
|
||||
class ConfigTreeConfigDataLocationResolverTests {
|
||||
|
||||
private ConfigTreeConfigDataLocationResolver resolver = new ConfigTreeConfigDataLocationResolver();
|
||||
private ConfigTreeConfigDataLocationResolver resolver = new ConfigTreeConfigDataLocationResolver(
|
||||
new DefaultResourceLoader());
|
||||
|
||||
private ConfigDataLocationResolverContext context = mock(ConfigDataLocationResolverContext.class);
|
||||
|
||||
@TempDir
|
||||
File temp;
|
||||
|
||||
@Test
|
||||
void isResolvableWhenPrefixMatchesReturnsTrue() {
|
||||
assertThat(this.resolver.isResolvable(this.context, ConfigDataLocation.of("configtree:/etc/config"))).isTrue();
|
||||
|
|
@ -50,10 +58,26 @@ class ConfigTreeConfigDataLocationResolverTests {
|
|||
@Test
|
||||
void resolveReturnsConfigVolumeMountLocation() {
|
||||
List<ConfigTreeConfigDataResource> locations = this.resolver.resolve(this.context,
|
||||
ConfigDataLocation.of("configtree:/etc/config"));
|
||||
ConfigDataLocation.of("configtree:/etc/config/"));
|
||||
assertThat(locations.size()).isEqualTo(1);
|
||||
assertThat(locations).extracting(Object::toString)
|
||||
.containsExactly("config tree [" + new File("/etc/config").getAbsolutePath() + "]");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveWilcardPattern() throws Exception {
|
||||
File directoryA = new File(this.temp, "a");
|
||||
File directoryB = new File(this.temp, "b");
|
||||
directoryA.mkdirs();
|
||||
directoryB.mkdirs();
|
||||
FileCopyUtils.copy("test".getBytes(), new File(directoryA, "spring"));
|
||||
FileCopyUtils.copy("test".getBytes(), new File(directoryB, "boot"));
|
||||
List<ConfigTreeConfigDataResource> locations = this.resolver.resolve(this.context,
|
||||
ConfigDataLocation.of("configtree:" + this.temp.getAbsolutePath() + "/*/"));
|
||||
assertThat(locations.size()).isEqualTo(2);
|
||||
assertThat(locations).extracting(Object::toString).containsExactly(
|
||||
"config tree [" + directoryA.getAbsolutePath() + "]",
|
||||
"config tree [" + directoryB.getAbsolutePath() + "]");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package org.springframework.boot.context.config;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
|
@ -31,9 +32,15 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
|
|||
*/
|
||||
public class ConfigTreeConfigDataResourceTests {
|
||||
|
||||
@Test
|
||||
void constructorWhenPathStringIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new ConfigTreeConfigDataResource((String) null))
|
||||
.withMessage("Path must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void constructorWhenPathIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new ConfigTreeConfigDataResource(null))
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new ConfigTreeConfigDataResource((Path) null))
|
||||
.withMessage("Path must not be null");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* 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.IOException;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.boot.context.config.LocationResourceLoader.ResourceType;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Tests for {@link LocationResourceLoader}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class LocationResourceLoaderTests {
|
||||
|
||||
private LocationResourceLoader loader = new LocationResourceLoader(new DefaultResourceLoader());
|
||||
|
||||
@TempDir
|
||||
File temp;
|
||||
|
||||
@Test
|
||||
void isPatternWhenHasAsteriskReturnsTrue() {
|
||||
assertThat(this.loader.isPattern("spring/*/boot")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isPatternWhenNoAsteriskReturnsFalse() {
|
||||
assertThat(this.loader.isPattern("spring/boot")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getResourceWhenPatternThrowsException() {
|
||||
assertThatIllegalStateException().isThrownBy(() -> this.loader.getResource("spring/boot/*"))
|
||||
.withMessage("Location 'spring/boot/*' must not be a pattern");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getResourceReturnsResource() throws Exception {
|
||||
File file = new File(this.temp, "file");
|
||||
FileCopyUtils.copy("test".getBytes(), file);
|
||||
Resource resource = this.loader.getResource(file.toURI().toString());
|
||||
assertThat(resource.getInputStream()).hasContent("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getResourceWhenNotUrlReturnsResource() throws Exception {
|
||||
File file = new File(this.temp, "file");
|
||||
FileCopyUtils.copy("test".getBytes(), file);
|
||||
Resource resource = this.loader.getResource(file.getAbsolutePath());
|
||||
assertThat(resource.getInputStream()).hasContent("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getResourceWhenNonCleanPathReturnsResource() throws Exception {
|
||||
File file = new File(this.temp, "file");
|
||||
FileCopyUtils.copy("test".getBytes(), file);
|
||||
Resource resource = this.loader.getResource(this.temp.getAbsolutePath() + "/spring/../file");
|
||||
assertThat(resource.getInputStream()).hasContent("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getResourcesWhenNotPatternThrowsException() {
|
||||
assertThatIllegalStateException().isThrownBy(() -> this.loader.getResources("spring/boot", ResourceType.FILE))
|
||||
.withMessage("Location 'spring/boot' must be a pattern");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getResourcesWhenLocationStartsWithClasspathWildcardThrowsException() {
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> this.loader.getResources("classpath*:spring/boot/*/", ResourceType.FILE))
|
||||
.withMessage("Location 'classpath*:spring/boot/*/' cannot use classpath wildcards");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getResourcesWhenLocationContainsMultipleWildcardsThrowsException() {
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> this.loader.getResources("spring/*/boot/*/", ResourceType.FILE))
|
||||
.withMessage("Location 'spring/*/boot/*/' cannot contain multiple wildcards");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getResourcesWhenPatternDoesNotEndWithAsteriskSlashThrowsException() {
|
||||
assertThatIllegalStateException().isThrownBy(() -> this.loader.getResources("spring/boot/*", ResourceType.FILE))
|
||||
.withMessage("Location 'spring/boot/*' must end with '*/'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getFileResourceReturnsResources() throws Exception {
|
||||
createTree();
|
||||
Resource[] resources = this.loader.getResources(this.temp.getAbsolutePath() + "/*/file", ResourceType.FILE);
|
||||
assertThat(resources).hasSize(2);
|
||||
assertThat(resources[0].getInputStream()).hasContent("a");
|
||||
assertThat(resources[1].getInputStream()).hasContent("b");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getDirectoryResourceReturnsResources() throws Exception {
|
||||
createTree();
|
||||
Resource[] resources = this.loader.getResources(this.temp.getAbsolutePath() + "/*/", ResourceType.DIRECTORY);
|
||||
assertThat(resources).hasSize(2);
|
||||
assertThat(resources[0].getFilename()).isEqualTo("a");
|
||||
assertThat(resources[1].getFilename()).isEqualTo("b");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getResourcesWhenHasHiddenDirectoriesFiltersResults() throws IOException {
|
||||
createTree();
|
||||
File hiddenDirectory = new File(this.temp, ".a");
|
||||
hiddenDirectory.mkdirs();
|
||||
FileCopyUtils.copy("h".getBytes(), new File(hiddenDirectory, "file"));
|
||||
Resource[] resources = this.loader.getResources(this.temp.getAbsolutePath() + "/*/file", ResourceType.FILE);
|
||||
assertThat(resources).hasSize(2);
|
||||
assertThat(resources[0].getInputStream()).hasContent("a");
|
||||
assertThat(resources[1].getInputStream()).hasContent("b");
|
||||
}
|
||||
|
||||
private void createTree() throws IOException {
|
||||
File directoryA = new File(this.temp, "a");
|
||||
File directoryB = new File(this.temp, "b");
|
||||
directoryA.mkdirs();
|
||||
directoryB.mkdirs();
|
||||
FileCopyUtils.copy("a".getBytes(), new File(directoryA, "file"));
|
||||
FileCopyUtils.copy("b".getBytes(), new File(directoryB, "file"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -100,14 +100,14 @@ public class StandardConfigDataLocationResolverTests {
|
|||
void resolveWhenLocationWildcardIsSpecifiedForClasspathLocationThrowsException() {
|
||||
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");
|
||||
.withMessageContaining("Location 'classpath*:application.properties' cannot use classpath wildcards");
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveWhenLocationWildcardIsNotBeforeLastSlashThrowsException() {
|
||||
ConfigDataLocation location = ConfigDataLocation.of("file:src/test/resources/*/config/");
|
||||
assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(this.context, location))
|
||||
.withMessageStartingWith("Search location '").withMessageEndingWith("' must end with '*/'");
|
||||
.withMessageStartingWith("Location '").withMessageEndingWith("' must end with '*/'");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -123,8 +123,7 @@ public class StandardConfigDataLocationResolverTests {
|
|||
void resolveWhenLocationHasMultipleWildcardsThrowsException() {
|
||||
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");
|
||||
.withMessageStartingWith("Location '").withMessageEndingWith("' cannot contain multiple wildcards");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
Loading…
Reference in New Issue