From c45bb2bd950f1a99786634fffe24bd4a93268013 Mon Sep 17 00:00:00 2001 From: Madhura Bhave Date: Fri, 14 May 2021 14:52:50 -0700 Subject: [PATCH] Handle empty locations from patterns Update `StandardConfigDataLocationResolver` to deal with patterns when resolving empty directories. This update also fixes the handling of mandatory pattern locations which would previously throw an exception. The error message returned when a location with a pattern does not contain any subdirectories has also been improved. Fixes gh-26468 Fixes gh-26577 Fixes gh-26415 --- .../StandardConfigDataLocationResolver.java | 34 ++++++++++++++----- ...ironmentPostProcessorIntegrationTests.java | 31 +++++++++++++++-- 2 files changed, 53 insertions(+), 12 deletions(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataLocationResolver.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataLocationResolver.java index f21970c391..abecc18d68 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataLocationResolver.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataLocationResolver.java @@ -18,6 +18,7 @@ package org.springframework.boot.context.config; import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Deque; @@ -26,6 +27,7 @@ import java.util.List; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; import org.apache.commons.logging.Log; @@ -243,19 +245,33 @@ public class StandardConfigDataLocationResolver Set references) { Set empty = new LinkedHashSet<>(); for (StandardConfigDataReference reference : references) { - if (reference.isMandatoryDirectory()) { - Resource resource = this.resourceLoader.getResource(reference.getDirectory()); - if (resource instanceof ClassPathResource) { - continue; - } - StandardConfigDataResource configDataResource = new StandardConfigDataResource(reference, resource); - ConfigDataResourceNotFoundException.throwIfDoesNotExist(configDataResource, resource); - empty.add(new StandardConfigDataResource(reference, resource, true)); - } + empty.addAll(resolveEmptyDirectories(reference)); } return empty; } + private Set resolveEmptyDirectories(StandardConfigDataReference reference) { + if (!this.resourceLoader.isPattern(reference.getResourceLocation())) { + return resolveNonPatternEmptyDirectories(reference); + } + return resolvePatternEmptyDirectories(reference); + } + + private Set resolveNonPatternEmptyDirectories(StandardConfigDataReference reference) { + Resource resource = this.resourceLoader.getResource(reference.getDirectory()); + return (resource instanceof ClassPathResource || !resource.exists()) ? Collections.emptySet() + : Collections.singleton(new StandardConfigDataResource(reference, resource, true)); + } + + private Set resolvePatternEmptyDirectories(StandardConfigDataReference reference) { + Resource[] resources = this.resourceLoader.getResources(reference.getDirectory(), ResourceType.DIRECTORY); + Assert.state(resources.length > 0, + "No subdirectories found for mandatory directory location '" + reference.getDirectory() + "'."); + return Arrays.stream(resources).filter(Resource::exists) + .map((resource) -> new StandardConfigDataResource(reference, resource, true)) + .collect(Collectors.toCollection(LinkedHashSet::new)); + } + private List resolve(StandardConfigDataReference reference) { if (!this.resourceLoader.isPattern(reference.getResourceLocation())) { return resolveNonPattern(reference); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java index a018b2e946..6f11c70ff6 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentPostProcessorIntegrationTests.java @@ -547,7 +547,7 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests { @Test void runWhenConfigLocationHasNonOptionalMissingFileDirectoryThrowsResourceNotFoundException() { File location = new File(this.temp, "application.unknown"); - assertThatExceptionOfType(ConfigDataResourceNotFoundException.class).isThrownBy(() -> this.application + assertThatExceptionOfType(ConfigDataLocationNotFoundException.class).isThrownBy(() -> this.application .run("--spring.config.location=" + StringUtils.cleanPath(location.getAbsolutePath()) + "/")); } @@ -566,6 +566,12 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests { .run("--spring.config.location=" + StringUtils.cleanPath(location.getAbsolutePath()) + "/")); } + @Test + void runWhenConfigLocationHasMandatoryDirectoryThatDoesntExistThrowsException() { + assertThatExceptionOfType(ConfigDataLocationNotFoundException.class).isThrownBy( + () -> this.application.run("--spring.config.location=" + StringUtils.cleanPath("invalid/"))); + } + @Test void runWhenConfigLocationHasNonOptionalEmptyFileDoesNotThrowException() throws IOException { File location = new File(this.temp, "application.properties"); @@ -686,13 +692,32 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests { @Test void runWhenHasWildcardLocationLoadsFromAllMatchingLocations() { ConfigurableApplicationContext context = this.application.run( - "--spring.config.location=optional:file:src/test/resources/config/*/", - "--spring.config.name=testproperties"); + "--spring.config.location=file:src/test/resources/config/*/", "--spring.config.name=testproperties"); ConfigurableEnvironment environment = context.getEnvironment(); assertThat(environment.getProperty("first.property")).isEqualTo("apple"); assertThat(environment.getProperty("second.property")).isEqualTo("ball"); } + @Test + void runWhenMandatoryWildcardLocationHasEmptyFileDirectory() { + assertThatNoException() + .isThrownBy(() -> this.application.run("--spring.config.location=file:src/test/resources/config/*/")); + } + + @Test + void runWhenMandatoryWildcardLocationHasNoSubdirectories() { + assertThatIllegalStateException().isThrownBy( + () -> this.application.run("--spring.config.location=file:src/test/resources/config/0-empty/*/")) + .withMessage( + "No subdirectories found for mandatory directory location 'file:src/test/resources/config/0-empty/*/'."); + } + + @Test + void runWhenHasMandatoryWildcardLocationThatDoesNotExist() { + assertThatExceptionOfType(ConfigDataLocationNotFoundException.class) + .isThrownBy(() -> this.application.run("--spring.config.location=file:invalid/*/")); + } + @Test // gh-24990 void runWhenHasProfileSpecificFileWithActiveOnProfileProperty() { ConfigurableApplicationContext context = this.application