From 01dd68e29edc43cb52651f568af482d94ade01fe Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 21 May 2021 10:54:01 +0100 Subject: [PATCH] Allow optional directories without sub-directories Update `StandardConfigDataLocationResolver` so that directory resources are only required when the location is not optional. Closes gh-26627 Co-authored-by: Phillip Webb --- .../ConfigDataLocationNotFoundException.java | 15 +++++++++++++-- .../StandardConfigDataLocationResolver.java | 12 ++++++++---- ...nvironmentPostProcessorIntegrationTests.java | 17 ++++++++++++++--- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationNotFoundException.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationNotFoundException.java index 33337bf589..62bd68dc52 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationNotFoundException.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataLocationNotFoundException.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 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. @@ -44,7 +44,18 @@ public class ConfigDataLocationNotFoundException extends ConfigDataNotFoundExcep * @param cause the exception cause */ public ConfigDataLocationNotFoundException(ConfigDataLocation location, Throwable cause) { - super(getMessage(location), cause); + this(location, getMessage(location), cause); + } + + /** + * Create a new {@link ConfigDataLocationNotFoundException} instance. + * @param location the location that could not be found + * @param message the exception message + * @param cause the exception cause + * @since 2.4.7 + */ + public ConfigDataLocationNotFoundException(ConfigDataLocation location, String message, Throwable cause) { + super(message, cause); Assert.notNull(location, "Location must not be null"); this.location = location; } 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 abecc18d68..8d956b334c 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 @@ -42,6 +42,7 @@ import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.core.log.LogMessage; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** @@ -264,10 +265,13 @@ public class StandardConfigDataLocationResolver } 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) + Resource[] subdirectories = this.resourceLoader.getResources(reference.getDirectory(), ResourceType.DIRECTORY); + ConfigDataLocation location = reference.getConfigDataLocation(); + if (location.isOptional() && ObjectUtils.isEmpty(subdirectories)) { + String message = String.format("Config data location '%s' contains no subdirectories", location); + throw new ConfigDataLocationNotFoundException(location, message, null); + } + return Arrays.stream(subdirectories).filter(Resource::exists) .map((resource) -> new StandardConfigDataResource(reference, resource, true)) .collect(Collectors.toCollection(LinkedHashSet::new)); } 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 677dc1329d..d11631b0f5 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 @@ -713,10 +713,9 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests { @Test void runWhenMandatoryWildcardLocationHasNoSubdirectories() { - assertThatIllegalStateException().isThrownBy( + assertThatExceptionOfType(ConfigDataLocationNotFoundException.class).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/*/'."); + .withMessage("Config data location 'file:src/test/resources/config/0-empty/*/' cannot be found"); } @Test @@ -725,6 +724,18 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests { .isThrownBy(() -> this.application.run("--spring.config.location=file:invalid/*/")); } + @Test + void runWhenHasOptionalWildcardLocationThatDoesNotExistDoesNotThrow() { + assertThatNoException() + .isThrownBy(() -> this.application.run("--spring.config.location=optional:file:invalid/*/")); + } + + @Test + void runWhenOptionalWildcardLocationHasNoSubdirectoriesDoesNotThrow() { + assertThatNoException().isThrownBy(() -> this.application + .run("--spring.config.location=optional:file:src/test/resources/config/0-empty/*/")); + } + @Test // gh-24990 void runWhenHasProfileSpecificFileWithActiveOnProfileProperty() { ConfigurableApplicationContext context = this.application