Restrict wildcard pattern support for configuration files
This commit restricts how wildcards can be used in search locations for property files. If a search location contains a pattern, there must be only one '*' and the location should end with a '*/'. For search locations that specify the file name, the pattern should end with '*/<filename>'. The list of files read from wildcard locations are now sorted alphabetically according to the absolute path of the file. Closes gh-21217
This commit is contained in:
parent
080123ebeb
commit
8ec16bd027
|
|
@ -499,7 +499,8 @@ For example, if you have some Redis configuration and some MySQL configuration,
|
|||
This might result in two separate `application.properties` files mounted at different locations such as `/config/redis/application.properties` and `/config/mysql/application.properties`.
|
||||
In such a case, having a wildcard location of `config/*/`, will result in both files being processed.
|
||||
|
||||
NOTE: Locations with wildcards are not processed in a deterministic order and files that match the wildcard cannot be used to override keys in the other.
|
||||
NOTE: A wildcard location must contain only one `*` and end with `*/` for search locations that are directories or `*/<filename>` for search locations that are files.
|
||||
Locations with wildcards are sorted alphabetically based on the absolute path of the file names.
|
||||
|
||||
|
||||
[[boot-features-external-config-application-json]]
|
||||
|
|
|
|||
|
|
@ -16,10 +16,12 @@
|
|||
|
||||
package org.springframework.boot.context.config;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
|
@ -28,9 +30,12 @@ import java.util.LinkedHashSet;
|
|||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
|
||||
|
|
@ -60,9 +65,9 @@ import org.springframework.core.env.MutablePropertySources;
|
|||
import org.springframework.core.env.Profiles;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
import org.springframework.core.io.support.ResourcePatternResolver;
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
import org.springframework.util.Assert;
|
||||
|
|
@ -163,6 +168,8 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
|
|||
|
||||
private static final Resource[] EMPTY_RESOURCES = {};
|
||||
|
||||
private static final Comparator<File> FILE_COMPARATOR = Comparator.comparing(File::getAbsolutePath);
|
||||
|
||||
private String searchLocations;
|
||||
|
||||
private String names;
|
||||
|
|
@ -304,8 +311,6 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
|
|||
|
||||
private final ResourceLoader resourceLoader;
|
||||
|
||||
private final PathMatchingResourcePatternResolver patternResolver;
|
||||
|
||||
private final List<PropertySourceLoader> propertySourceLoaders;
|
||||
|
||||
private Deque<Profile> profiles;
|
||||
|
|
@ -325,7 +330,6 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
|
|||
: new DefaultResourceLoader(getClass().getClassLoader());
|
||||
this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
|
||||
getClass().getClassLoader());
|
||||
this.patternResolver = new PathMatchingResourcePatternResolver(this.resourceLoader);
|
||||
}
|
||||
|
||||
void load() {
|
||||
|
|
@ -555,9 +559,22 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
|
|||
|
||||
private Resource[] getResources(String location) {
|
||||
try {
|
||||
return this.patternResolver.getResources(location);
|
||||
if (location.contains("*")) {
|
||||
String directoryPath = location.substring(0, location.indexOf("*/"));
|
||||
String fileName = location.substring(location.lastIndexOf("/") + 1);
|
||||
Resource resource = this.resourceLoader.getResource(directoryPath);
|
||||
File[] files = resource.getFile().listFiles(File::isDirectory);
|
||||
if (files != null) {
|
||||
Arrays.sort(files, FILE_COMPARATOR);
|
||||
return Arrays.stream(files).map((file) -> file.listFiles((dir, name) -> name.equals(fileName)))
|
||||
.filter(Objects::nonNull).flatMap((Function<File[], Stream<File>>) Arrays::stream)
|
||||
.map(FileSystemResource::new).toArray(Resource[]::new);
|
||||
}
|
||||
return EMPTY_RESOURCES;
|
||||
}
|
||||
return new Resource[] { this.resourceLoader.getResource(location) };
|
||||
}
|
||||
catch (IOException ex) {
|
||||
catch (Exception ex) {
|
||||
return EMPTY_RESOURCES;
|
||||
}
|
||||
}
|
||||
|
|
@ -658,7 +675,8 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
|
|||
if (!path.contains("$")) {
|
||||
path = StringUtils.cleanPath(path);
|
||||
Assert.state(!path.startsWith(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX),
|
||||
"Classpath wildard patterns cannot be used as a search location");
|
||||
"Classpath wildcard patterns cannot be used as a search location");
|
||||
validateWildcardLocation(path);
|
||||
if (!ResourceUtils.isUrl(path)) {
|
||||
path = ResourceUtils.FILE_URL_PREFIX + path;
|
||||
}
|
||||
|
|
@ -669,6 +687,15 @@ public class ConfigFileApplicationListener implements EnvironmentPostProcessor,
|
|||
return locations;
|
||||
}
|
||||
|
||||
private void validateWildcardLocation(String path) {
|
||||
if (path.contains("*")) {
|
||||
Assert.state(StringUtils.countOccurrencesOf(path, "*") == 1,
|
||||
"Wildard pattern with multiple '*'s cannot be used as search location");
|
||||
String directoryPath = path.substring(0, path.lastIndexOf("/") + 1);
|
||||
Assert.state(directoryPath.endsWith("*/"), "Wildcard patterns must end with '*/'");
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> getSearchNames() {
|
||||
if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
|
||||
String property = this.environment.getProperty(CONFIG_NAME_PROPERTY);
|
||||
|
|
|
|||
|
|
@ -716,7 +716,7 @@ class ConfigFileApplicationListenerTests {
|
|||
"spring.config.location=classpath*:override.properties");
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> this.initializer.postProcessEnvironment(this.environment, this.application))
|
||||
.withMessage("Classpath wildard patterns cannot be used as a search location");
|
||||
.withMessage("Classpath wildcard patterns cannot be used as a search location");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -1032,6 +1032,36 @@ class ConfigFileApplicationListenerTests {
|
|||
this.initializer.postProcessEnvironment(this.environment, this.application);
|
||||
}
|
||||
|
||||
@Test
|
||||
void directoryLocationsWithWildcardShouldHaveWildcardAsLastCharacterBeforeSlash() {
|
||||
String location = "file:src/test/resources/*/config/";
|
||||
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment,
|
||||
"spring.config.location=" + location);
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> this.initializer.postProcessEnvironment(this.environment, this.application))
|
||||
.withMessage("Wildcard patterns must end with '*/'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void directoryLocationsWithMultipleWildcardsShouldThrowException() {
|
||||
String location = "file:src/test/resources/config/**/";
|
||||
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment,
|
||||
"spring.config.location=" + location);
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> this.initializer.postProcessEnvironment(this.environment, this.application))
|
||||
.withMessage("Wildard pattern with multiple '*'s cannot be used as search location");
|
||||
}
|
||||
|
||||
@Test
|
||||
void locationsWithWildcardDirectoriesShouldRestrictToOneLevelDeep() {
|
||||
String location = "file:src/test/resources/config/*/";
|
||||
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment,
|
||||
"spring.config.location=" + location);
|
||||
this.initializer.setSearchNames("testproperties");
|
||||
this.initializer.postProcessEnvironment(this.environment, this.application);
|
||||
assertThat(this.environment.getProperty("third.property")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void locationsWithWildcardDirectoriesShouldLoadAllFilesThatMatch() {
|
||||
String location = "file:src/test/resources/config/*/";
|
||||
|
|
@ -1045,6 +1075,22 @@ class ConfigFileApplicationListenerTests {
|
|||
assertThat(second).isEqualTo("ball");
|
||||
}
|
||||
|
||||
@Test
|
||||
void locationsWithWildcardDirectoriesShouldSortAlphabeticallyBasedOnAbsolutePath() {
|
||||
String location = "file:src/test/resources/config/*/";
|
||||
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(this.environment,
|
||||
"spring.config.location=" + location);
|
||||
this.initializer.setSearchNames("testproperties");
|
||||
this.initializer.postProcessEnvironment(this.environment, this.application);
|
||||
List<String> sources = this.environment.getPropertySources().stream()
|
||||
.filter((source) -> source.getName().contains("applicationConfig")).map((source) -> {
|
||||
String name = source.getName();
|
||||
return name.substring(name.indexOf("src/test/resources"));
|
||||
}).collect(Collectors.toList());
|
||||
assertThat(sources).containsExactly("src/test/resources/config/1-first/testproperties.properties]]",
|
||||
"src/test/resources/config/2-second/testproperties.properties]]");
|
||||
}
|
||||
|
||||
@Test
|
||||
void locationsWithWildcardFilesShouldLoadAllFilesThatMatch() {
|
||||
String location = "file:src/test/resources/config/*/testproperties.properties";
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
third.property=shouldnotbefound
|
||||
Loading…
Reference in New Issue