diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataResourceNotFoundException.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataResourceNotFoundException.java index 439a0231d1b..86a68546b2e 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataResourceNotFoundException.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataResourceNotFoundException.java @@ -116,7 +116,7 @@ public class ConfigDataResourceNotFoundException extends ConfigDataNotFoundExcep * @param pathToCheck the path to check */ public static void throwIfDoesNotExist(ConfigDataResource resource, Path pathToCheck) { - throwIfDoesNotExist(resource, Files.exists(pathToCheck)); + throwIfNot(resource, Files.exists(pathToCheck)); } /** @@ -126,7 +126,7 @@ public class ConfigDataResourceNotFoundException extends ConfigDataNotFoundExcep * @param fileToCheck the file to check */ public static void throwIfDoesNotExist(ConfigDataResource resource, File fileToCheck) { - throwIfDoesNotExist(resource, fileToCheck.exists()); + throwIfNot(resource, fileToCheck.exists()); } /** @@ -136,11 +136,11 @@ public class ConfigDataResourceNotFoundException extends ConfigDataNotFoundExcep * @param resourceToCheck the resource to check */ public static void throwIfDoesNotExist(ConfigDataResource resource, Resource resourceToCheck) { - throwIfDoesNotExist(resource, resourceToCheck.exists()); + throwIfNot(resource, resourceToCheck.exists()); } - private static void throwIfDoesNotExist(ConfigDataResource resource, boolean exists) { - if (!exists) { + private static void throwIfNot(ConfigDataResource resource, boolean check) { + if (!check) { throw new ConfigDataResourceNotFoundException(resource); } } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/EnvConfigDataLoader.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/EnvConfigDataLoader.java deleted file mode 100644 index 31db4cbe4d3..00000000000 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/EnvConfigDataLoader.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2012-2025 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.IOException; -import java.nio.charset.StandardCharsets; -import java.util.function.Function; - -import org.springframework.core.io.ByteArrayResource; - -/** - * {@link ConfigDataLoader} to load data from environment variables. - * - * @author Moritz Halbritter - */ -class EnvConfigDataLoader implements ConfigDataLoader { - - private final Function readEnvVariable; - - EnvConfigDataLoader() { - this.readEnvVariable = System::getenv; - } - - EnvConfigDataLoader(Function readEnvVariable) { - this.readEnvVariable = readEnvVariable; - } - - @Override - public ConfigData load(ConfigDataLoaderContext context, EnvConfigDataResource resource) - throws IOException, ConfigDataResourceNotFoundException { - String content = this.readEnvVariable.apply(resource.getVariableName()); - if (content == null) { - throw new ConfigDataResourceNotFoundException(resource); - } - String name = String.format("Environment variable '%s' via location '%s'", resource.getVariableName(), - resource.getLocation()); - return new ConfigData(resource.getLoader().load(name, createResource(content))); - } - - private ByteArrayResource createResource(String content) { - return new ByteArrayResource(content.getBytes(StandardCharsets.UTF_8)); - } - -} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/EnvConfigDataResource.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/EnvConfigDataResource.java deleted file mode 100644 index 1c7b0c1aa9f..00000000000 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/EnvConfigDataResource.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2012-2025 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.Objects; - -import org.springframework.boot.env.PropertySourceLoader; - -/** - * {@link ConfigDataResource} used by {@link EnvConfigDataLoader}. - * - * @author Moritz Halbritter - */ -class EnvConfigDataResource extends ConfigDataResource { - - private final ConfigDataLocation location; - - private final String variableName; - - private final PropertySourceLoader loader; - - EnvConfigDataResource(ConfigDataLocation location, String variableName, PropertySourceLoader loader) { - super(location.isOptional()); - this.location = location; - this.variableName = variableName; - this.loader = loader; - } - - ConfigDataLocation getLocation() { - return this.location; - } - - String getVariableName() { - return this.variableName; - } - - PropertySourceLoader getLoader() { - return this.loader; - } - - @Override - public boolean equals(Object o) { - if (o == null || getClass() != o.getClass()) { - return false; - } - EnvConfigDataResource that = (EnvConfigDataResource) o; - return Objects.equals(this.location, that.location) && Objects.equals(this.variableName, that.variableName) - && Objects.equals(this.loader, that.loader); - } - - @Override - public int hashCode() { - return Objects.hash(this.location, this.variableName, this.loader); - } - - @Override - public String toString() { - return "env variable [" + this.variableName + "]"; - } - -} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/FileExtensionHint.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/FileExtensionHint.java new file mode 100644 index 00000000000..42c70d74e61 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/FileExtensionHint.java @@ -0,0 +1,82 @@ +/* + * Copyright 2012-2025 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.regex.Matcher; +import java.util.regex.Pattern; + +/** + * User provided hint for an otherwise missing file extension. + * + * @author Phillip Webb + */ +final class FileExtensionHint { + + private static final Pattern PATTERN = Pattern.compile("^(.*)\\[(\\.\\w+)](?!\\[)$"); + + private static final FileExtensionHint NONE = new FileExtensionHint(null); + + private final Matcher matcher; + + private FileExtensionHint(Matcher matcher) { + this.matcher = matcher; + } + + /** + * Return {@code true} if the hint is present. + * @return if the hint is present + */ + boolean isPresent() { + return this.matcher != null; + } + + /** + * Return the extension from the hint or return the parameter if the hint is not + * {@link #isPresent() present}. + * @param extension the fallback extension + * @return the extension either from the hint or fallback + */ + String orElse(String extension) { + return (this.matcher != null) ? toString() : extension; + } + + @Override + public String toString() { + return (this.matcher != null) ? this.matcher.group(2) : ""; + } + + /** + * Return the {@link FileExtensionHint} from the given value. + * @param value the source value + * @return the {@link FileExtensionHint} (never {@code null}) + */ + static FileExtensionHint from(String value) { + Matcher matcher = PATTERN.matcher(value); + return (matcher.matches()) ? new FileExtensionHint(matcher) : NONE; + } + + /** + * Remove any hint from the given value. + * @param value the source value + * @return the value without any hint + */ + static String removeFrom(String value) { + Matcher matcher = PATTERN.matcher(value); + return (matcher.matches()) ? matcher.group(1) : value; + } + +} 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 2de64f33529..73302e2016e 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 @@ -26,7 +26,6 @@ import java.util.Deque; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; -import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -68,8 +67,6 @@ public class StandardConfigDataLocationResolver 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+)](?!\\[)$"); - private static final String NO_PROFILE = null; private final Log logger; @@ -238,17 +235,16 @@ public class StandardConfigDataLocationResolver private Set getReferencesForFile(ConfigDataLocation configDataLocation, String file, String profile) { - Matcher extensionHintMatcher = EXTENSION_HINT_PATTERN.matcher(file); - boolean extensionHintLocation = extensionHintMatcher.matches(); - if (extensionHintLocation) { - file = extensionHintMatcher.group(1) + extensionHintMatcher.group(2); + FileExtensionHint fileExtensionHint = FileExtensionHint.from(file); + if (fileExtensionHint.isPresent()) { + file = FileExtensionHint.removeFrom(file) + fileExtensionHint; } for (PropertySourceLoader propertySourceLoader : this.propertySourceLoaders) { - String extension = getLoadableFileExtension(propertySourceLoader, file); - if (extension != null) { - String root = file.substring(0, file.length() - extension.length() - 1); + String fileExtension = getLoadableFileExtension(propertySourceLoader, file); + if (fileExtension != null) { + String root = file.substring(0, file.length() - fileExtension.length() - 1); StandardConfigDataReference reference = new StandardConfigDataReference(configDataLocation, null, root, - profile, (!extensionHintLocation) ? extension : null, propertySourceLoader); + profile, (!fileExtensionHint.isPresent()) ? fileExtension : null, propertySourceLoader); return Collections.singleton(reference); } } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/SystemEnvironmentConfigDataLoader.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/SystemEnvironmentConfigDataLoader.java new file mode 100644 index 00000000000..7b9c7866684 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/SystemEnvironmentConfigDataLoader.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012-2025 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.IOException; +import java.util.List; + +import org.springframework.core.env.PropertySource; + +/** + * {@link ConfigDataLoader} to load data from system environment variables. + * + * @author Moritz Halbritter + */ +class SystemEnvironmentConfigDataLoader implements ConfigDataLoader { + + @Override + public ConfigData load(ConfigDataLoaderContext context, SystemEnvironmentConfigDataResource resource) + throws IOException, ConfigDataResourceNotFoundException { + List> loaded = resource.load(); + if (loaded == null) { + throw new ConfigDataResourceNotFoundException(resource); + } + return new ConfigData(loaded); + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/EnvConfigDataLocationResolver.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/SystemEnvironmentConfigDataLocationResolver.java similarity index 61% rename from spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/EnvConfigDataLocationResolver.java rename to spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/SystemEnvironmentConfigDataLocationResolver.java index 3e28b35ec37..4c3692de665 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/EnvConfigDataLocationResolver.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/SystemEnvironmentConfigDataLocationResolver.java @@ -19,8 +19,6 @@ package org.springframework.boot.context.config; import java.util.Collections; import java.util.List; import java.util.function.Function; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import org.springframework.boot.env.PropertySourceLoader; import org.springframework.core.io.support.SpringFactoriesLoader; @@ -29,27 +27,28 @@ import org.springframework.core.io.support.SpringFactoriesLoader; * {@link ConfigDataLocationResolver} to resolve {@code env:} locations. * * @author Moritz Halbritter + * @author Phillip Webb */ -class EnvConfigDataLocationResolver implements ConfigDataLocationResolver { +class SystemEnvironmentConfigDataLocationResolver + implements ConfigDataLocationResolver { private static final String PREFIX = "env:"; - private static final Pattern EXTENSION_HINT_PATTERN = Pattern.compile("^(.*)\\[(\\.\\w+)](?!\\[)$"); - private static final String DEFAULT_EXTENSION = ".properties"; private final List loaders; - private final Function readEnvVariable; + private final Function environment; - EnvConfigDataLocationResolver() { + SystemEnvironmentConfigDataLocationResolver() { this.loaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class, getClass().getClassLoader()); - this.readEnvVariable = System::getenv; + this.environment = System::getenv; } - EnvConfigDataLocationResolver(List loaders, Function readEnvVariable) { + SystemEnvironmentConfigDataLocationResolver(List loaders, + Function environment) { this.loaders = loaders; - this.readEnvVariable = readEnvVariable; + this.environment = environment; } @Override @@ -58,15 +57,15 @@ class EnvConfigDataLocationResolver implements ConfigDataLocationResolver resolve(ConfigDataLocationResolverContext context, ConfigDataLocation location) + public List resolve(ConfigDataLocationResolverContext context, + ConfigDataLocation location) throws ConfigDataLocationNotFoundException, ConfigDataResourceNotFoundException { String value = location.getNonPrefixedValue(PREFIX); - Matcher matcher = EXTENSION_HINT_PATTERN.matcher(value); - String extension = getExtension(matcher); - String variableName = getVariableName(matcher, value); - PropertySourceLoader loader = getLoader(extension); + FileExtensionHint fileExtensionHint = FileExtensionHint.from(value); + String variableName = FileExtensionHint.removeFrom(value); + PropertySourceLoader loader = getLoader(fileExtensionHint.orElse(DEFAULT_EXTENSION)); if (hasEnvVariable(variableName)) { - return List.of(new EnvConfigDataResource(location, variableName, loader)); + return List.of(new SystemEnvironmentConfigDataResource(variableName, loader, this.environment)); } if (location.isOptional()) { return Collections.emptyList(); @@ -76,12 +75,7 @@ class EnvConfigDataLocationResolver implements ConfigDataLocationResolver environment; + + SystemEnvironmentConfigDataResource(String variableName, PropertySourceLoader loader, + Function environment) { + this.variableName = variableName; + this.loader = loader; + this.environment = environment; + } + + String getVariableName() { + return this.variableName; + } + + PropertySourceLoader getLoader() { + return this.loader; + } + + List> load() throws IOException { + String content = this.environment.apply(this.variableName); + return (content != null) ? this.loader.load(StringUtils.capitalize(toString()), asResource(content)) : null; + } + + private ByteArrayResource asResource(String content) { + return new ByteArrayResource(content.getBytes(StandardCharsets.UTF_8)); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + SystemEnvironmentConfigDataResource other = (SystemEnvironmentConfigDataResource) obj; + return Objects.equals(this.loader.getClass(), other.loader.getClass()) + && Objects.equals(this.variableName, other.variableName); + } + + @Override + public int hashCode() { + return Objects.hash(this.variableName, this.loader.getClass()); + } + + @Override + public String toString() { + return "system envionement variable [" + this.variableName + "] content loaded using " + + ClassUtils.getShortName(this.loader.getClass()); + } + +} diff --git a/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories index b00a2861430..6ec2d815a64 100644 --- a/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories @@ -12,14 +12,14 @@ org.springframework.boot.env.YamlPropertySourceLoader # ConfigData Location Resolvers org.springframework.boot.context.config.ConfigDataLocationResolver=\ org.springframework.boot.context.config.ConfigTreeConfigDataLocationResolver,\ -org.springframework.boot.context.config.EnvConfigDataLocationResolver,\ -org.springframework.boot.context.config.StandardConfigDataLocationResolver +org.springframework.boot.context.config.StandardConfigDataLocationResolver,\ +org.springframework.boot.context.config.SystemEnvironmentConfigDataLocationResolver # ConfigData Loaders org.springframework.boot.context.config.ConfigDataLoader=\ org.springframework.boot.context.config.ConfigTreeConfigDataLoader,\ -org.springframework.boot.context.config.EnvConfigDataLoader,\ -org.springframework.boot.context.config.StandardConfigDataLoader +org.springframework.boot.context.config.StandardConfigDataLoader,\ +org.springframework.boot.context.config.SystemEnvironmentConfigDataLoader # Application Context Factories org.springframework.boot.ApplicationContextFactory=\ diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/EnvConfigDataResourceTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/EnvConfigDataResourceTests.java deleted file mode 100644 index 1362732d10c..00000000000 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/EnvConfigDataResourceTests.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2012-2025 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 org.junit.jupiter.api.Test; - -import org.springframework.boot.env.PropertiesPropertySourceLoader; -import org.springframework.boot.env.PropertySourceLoader; -import org.springframework.boot.env.YamlPropertySourceLoader; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link EnvConfigDataResource}. - * - * @author Moritz Halbritter - */ -class EnvConfigDataResourceTests { - - private final YamlPropertySourceLoader yamlPropertySourceLoader = new YamlPropertySourceLoader(); - - private final PropertiesPropertySourceLoader propertiesPropertySourceLoader = new PropertiesPropertySourceLoader(); - - @Test - void shouldHaveEqualsAndHashcode() { - EnvConfigDataResource var1 = createResource("VAR1"); - EnvConfigDataResource var2 = createResource("VAR2"); - EnvConfigDataResource var3 = createResource("VAR1", this.yamlPropertySourceLoader); - EnvConfigDataResource var4 = createResource("VAR1"); - assertThat(var1).isNotEqualTo(var2); - assertThat(var1).isNotEqualTo(var3); - assertThat(var1).isEqualTo(var4); - assertThat(var1).hasSameHashCodeAs(var4); - } - - @Test - void shouldHaveToString() { - EnvConfigDataResource resource = createResource("VAR1"); - assertThat(resource).hasToString("env variable [VAR1]"); - } - - private EnvConfigDataResource createResource(String variableName) { - return createResource(variableName, this.propertiesPropertySourceLoader); - } - - private EnvConfigDataResource createResource(String variableName, PropertySourceLoader propertySourceLoader) { - return new EnvConfigDataResource(ConfigDataLocation.of("env:" + variableName), variableName, - propertySourceLoader); - } - -} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/FileExtensionHintTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/FileExtensionHintTests.java new file mode 100644 index 00000000000..6462437f380 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/FileExtensionHintTests.java @@ -0,0 +1,72 @@ +/* + * Copyright 2012-2025 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 org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link FileExtensionHint}. + * + * @author Phillip Webb + */ +class FileExtensionHintTests { + + @Test + void isPresentWhenHasHint() { + assertThat(FileExtensionHint.from("foo[.bar]").isPresent()).isTrue(); + } + + @Test + void isPresentWhenHasNoHint() { + assertThat(FileExtensionHint.from("foo").isPresent()).isFalse(); + assertThat(FileExtensionHint.from("foo[bar]").isPresent()).isFalse(); + assertThat(FileExtensionHint.from("foo[.b[ar]").isPresent()).isFalse(); + } + + @Test + void orElseWhenHasHint() { + assertThat(FileExtensionHint.from("foo[.bar]").orElse(".txt")).isEqualTo(".bar"); + } + + @Test + void orElseWhenHasNoHint() { + assertThat(FileExtensionHint.from("foo").orElse(".txt")).isEqualTo(".txt"); + } + + @Test + void toStringWhenHasHintReturnsDotExtension() { + assertThat(FileExtensionHint.from("foo[.bar]")).hasToString(".bar"); + } + + @Test + void toStringWhenHasNoHintReturnsEmpty() { + assertThat(FileExtensionHint.from("foo")).hasToString(""); + } + + @Test + void removeFromWhenHasHint() { + assertThat(FileExtensionHint.removeFrom("foo[.bar]")).isEqualTo("foo"); + } + + @Test + void removeFromWhenHasNoHint() { + assertThat(FileExtensionHint.removeFrom("foo[bar]")).isEqualTo("foo[bar]"); + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/EnvConfigDataLoaderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/SystemEnvironmentConfigDataLoaderTests.java similarity index 68% rename from spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/EnvConfigDataLoaderTests.java rename to spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/SystemEnvironmentConfigDataLoaderTests.java index c4f4c8f2b93..d1b7f6c2b10 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/EnvConfigDataLoaderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/SystemEnvironmentConfigDataLoaderTests.java @@ -31,28 +31,28 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.Mockito.mock; /** - * Tests for {@link EnvConfigDataLoader}. + * Tests for {@link SystemEnvironmentConfigDataLoader}. * * @author Moritz Halbritter */ -class EnvConfigDataLoaderTests { +class SystemEnvironmentConfigDataLoaderTests { private ConfigDataLoaderContext context; - private Map envVariables; + private Map environment; - private EnvConfigDataLoader loader; + private SystemEnvironmentConfigDataLoader loader; @BeforeEach void setUp() { this.context = mock(ConfigDataLoaderContext.class); - this.envVariables = new HashMap<>(); - this.loader = new EnvConfigDataLoader(this.envVariables::get); + this.environment = new HashMap<>(); + this.loader = new SystemEnvironmentConfigDataLoader(); } @Test - void shouldLoadFromVariable() throws IOException { - this.envVariables.put("VAR1", "key1=value1"); + void loadLoadsConfigData() throws IOException { + this.environment.put("VAR1", "key1=value1"); ConfigData data = this.loader.load(this.context, createResource("VAR1")); assertThat(data.getPropertySources()).hasSize(1); PropertySource propertySource = data.getPropertySources().get(0); @@ -60,15 +60,16 @@ class EnvConfigDataLoaderTests { } @Test - void shouldFailIfVariableIsNotSet() { + void loadWhenNoContentThrowsException() { assertThatExceptionOfType(ConfigDataResourceNotFoundException.class) .isThrownBy(() -> this.loader.load(this.context, createResource("VAR1"))) - .withMessage("Config data resource 'env variable [VAR1]' cannot be found"); + .withMessage("Config data resource 'system envionement variable [VAR1] content " + + "loaded using PropertiesPropertySourceLoader' cannot be found"); } - private static EnvConfigDataResource createResource(String variableName) { - return new EnvConfigDataResource(ConfigDataLocation.of("env:" + variableName), variableName, - new PropertiesPropertySourceLoader()); + private SystemEnvironmentConfigDataResource createResource(String variableName) { + return new SystemEnvironmentConfigDataResource(variableName, new PropertiesPropertySourceLoader(), + this.environment::get); } } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/EnvConfigDataLocationResolverTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/SystemEnvironmentConfigDataLocationResolverTests.java similarity index 67% rename from spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/EnvConfigDataLocationResolverTests.java rename to spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/SystemEnvironmentConfigDataLocationResolverTests.java index d9db28d57eb..220db212d3f 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/EnvConfigDataLocationResolverTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/SystemEnvironmentConfigDataLocationResolverTests.java @@ -32,24 +32,24 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.mockito.Mockito.mock; /** - * Tests for {@link EnvConfigDataLocationResolver}. + * Tests for {@link SystemEnvironmentConfigDataLocationResolver}. * * @author Moritz Halbritter */ -class EnvConfigDataLocationResolverTests { +class SystemEnvironmentConfigDataLocationResolverTests { - private EnvConfigDataLocationResolver resolver; + private SystemEnvironmentConfigDataLocationResolver resolver; - private Map envVariables; + private Map environment; private ConfigDataLocationResolverContext context; @BeforeEach void setUp() { this.context = mock(ConfigDataLocationResolverContext.class); - this.envVariables = new HashMap<>(); - this.resolver = new EnvConfigDataLocationResolver( - List.of(new PropertiesPropertySourceLoader(), new YamlPropertySourceLoader()), this.envVariables::get); + this.environment = new HashMap<>(); + this.resolver = new SystemEnvironmentConfigDataLocationResolver( + List.of(new PropertiesPropertySourceLoader(), new YamlPropertySourceLoader()), this.environment::get); } @Test @@ -60,69 +60,65 @@ class EnvConfigDataLocationResolverTests { } @Test - void shouldResolve() { - this.envVariables.put("VAR1", "VALUE1"); + void resolveResolves() { + this.environment.put("VAR1", "VALUE1"); ConfigDataLocation location = ConfigDataLocation.of("env:VAR1"); - List resolved = this.resolver.resolve(this.context, location); + List resolved = this.resolver.resolve(this.context, location); assertThat(resolved).hasSize(1); - EnvConfigDataResource resource = resolved.get(0); - assertThat(resource.getLocation()).isEqualTo(location); + SystemEnvironmentConfigDataResource resource = resolved.get(0); assertThat(resource.getVariableName()).isEqualTo("VAR1"); assertThat(resource.getLoader()).isInstanceOf(PropertiesPropertySourceLoader.class); } @Test - void shouldResolveOptional() { - this.envVariables.put("VAR1", "VALUE1"); + void resolveWhenHasNoVariableThrowsException() { + assertThatExceptionOfType(ConfigDataLocationNotFoundException.class) + .isThrownBy(() -> this.resolver.resolve(this.context, ConfigDataLocation.of("env:VAR1"))) + .withMessage("Environment variable 'VAR1' is not set"); + } + + @Test + void resolveWhenOptionalAndHasVariableResolves() { + this.environment.put("VAR1", "VALUE1"); ConfigDataLocation location = ConfigDataLocation.of("optional:env:VAR1"); - List resolved = this.resolver.resolve(this.context, location); + List resolved = this.resolver.resolve(this.context, location); assertThat(resolved).hasSize(1); - EnvConfigDataResource resource = resolved.get(0); - assertThat(resource.getLocation()).isEqualTo(location); + SystemEnvironmentConfigDataResource resource = resolved.get(0); assertThat(resource.getVariableName()).isEqualTo("VAR1"); assertThat(resource.getLoader()).isInstanceOf(PropertiesPropertySourceLoader.class); } @Test - void shouldResolveOptionalIfVariableIsNotSet() { + void resolveWhenOptionalAndHasNoVariableResolvesEmpty() { ConfigDataLocation location = ConfigDataLocation.of("optional:env:VAR1"); - List resolved = this.resolver.resolve(this.context, location); + List resolved = this.resolver.resolve(this.context, location); assertThat(resolved).isEmpty(); } @Test - void shouldResolveWithPropertiesExtension() { - this.envVariables.put("VAR1", "VALUE1"); + void resolveWhenHasPropertiesExtensionHintResolves() { + this.environment.put("VAR1", "VALUE1"); ConfigDataLocation location = ConfigDataLocation.of("env:VAR1[.properties]"); - List resolved = this.resolver.resolve(this.context, location); + List resolved = this.resolver.resolve(this.context, location); assertThat(resolved).hasSize(1); - EnvConfigDataResource resource = resolved.get(0); - assertThat(resource.getLocation()).isEqualTo(location); + SystemEnvironmentConfigDataResource resource = resolved.get(0); assertThat(resource.getVariableName()).isEqualTo("VAR1"); assertThat(resource.getLoader()).isInstanceOf(PropertiesPropertySourceLoader.class); } @Test - void shouldResolveWithYamlExtension() { - this.envVariables.put("VAR1", "VALUE1"); + void resolveWhenHasYamlExtensionHintResolves() { + this.environment.put("VAR1", "VALUE1"); ConfigDataLocation location = ConfigDataLocation.of("env:VAR1[.yaml]"); - List resolved = this.resolver.resolve(this.context, location); + List resolved = this.resolver.resolve(this.context, location); assertThat(resolved).hasSize(1); - EnvConfigDataResource resource = resolved.get(0); - assertThat(resource.getLocation()).isEqualTo(location); + SystemEnvironmentConfigDataResource resource = resolved.get(0); assertThat(resource.getVariableName()).isEqualTo("VAR1"); assertThat(resource.getLoader()).isInstanceOf(YamlPropertySourceLoader.class); } @Test - void shouldFailIfVariableIsNotSet() { - assertThatExceptionOfType(ConfigDataLocationNotFoundException.class) - .isThrownBy(() -> this.resolver.resolve(this.context, ConfigDataLocation.of("env:VAR1"))) - .withMessage("Environment variable 'VAR1' is not set"); - } - - @Test - void shouldFailIfUnknownExtensionIsGiven() { + void resolveWhenHasUnknownExtensionHintThrowsException() { assertThatIllegalStateException() .isThrownBy(() -> this.resolver.resolve(this.context, ConfigDataLocation.of("env:VAR1[.dummy]"))) .withMessage("File extension 'dummy' is not known to any PropertySourceLoader"); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/SystemEnvironmentConfigDataResourceTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/SystemEnvironmentConfigDataResourceTests.java new file mode 100644 index 00000000000..8d0f75c9653 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/SystemEnvironmentConfigDataResourceTests.java @@ -0,0 +1,88 @@ +/* + * Copyright 2012-2025 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.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.env.PropertiesPropertySourceLoader; +import org.springframework.boot.env.PropertySourceLoader; +import org.springframework.boot.env.YamlPropertySourceLoader; +import org.springframework.core.env.PropertySource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link SystemEnvironmentConfigDataResource}. + * + * @author Moritz Halbritter + */ +class SystemEnvironmentConfigDataResourceTests { + + private Map environment = new HashMap<>(); + + private final YamlPropertySourceLoader yamlLoader = new YamlPropertySourceLoader(); + + private final PropertiesPropertySourceLoader propertiesLoader = new PropertiesPropertySourceLoader(); + + @Test + void loadLoadsPropertySources() throws IOException { + this.environment.put("VAR1", "key1=value1"); + List> loaded = createResource("VAR1").load(); + assertThat(loaded).hasSize(1); + assertThat(loaded.get(0).getProperty("key1")).isEqualTo("value1"); + } + + @Test + void loadWhenNoContentReturnsNull() throws IOException { + List> loaded = createResource("VAR1").load(); + assertThat(loaded).isNull(); + } + + @Test + void equalsAndHashcode() { + SystemEnvironmentConfigDataResource var1 = createResource("VAR1"); + SystemEnvironmentConfigDataResource var2 = createResource("VAR2"); + SystemEnvironmentConfigDataResource var3 = createResource("VAR1", this.yamlLoader); + SystemEnvironmentConfigDataResource var4 = createResource("VAR1"); + assertThat(var1).isNotEqualTo(var2); + assertThat(var1).isNotEqualTo(var3); + assertThat(var1).isEqualTo(var4); + assertThat(var1).hasSameHashCodeAs(var4); + } + + @Test + void toStringReturnsString() { + SystemEnvironmentConfigDataResource resource = createResource("VAR1"); + assertThat(resource) + .hasToString("system envionement variable [VAR1] content loaded using PropertiesPropertySourceLoader"); + } + + private SystemEnvironmentConfigDataResource createResource(String variableName) { + return createResource(variableName, this.propertiesLoader); + } + + private SystemEnvironmentConfigDataResource createResource(String variableName, + PropertySourceLoader propertySourceLoader) { + return new SystemEnvironmentConfigDataResource(variableName, propertySourceLoader, this.environment::get); + } + +}