Polish `EnvConfigData`

Rename classes to align with existing `SystemEnvironment...` classes
and extract common `FileExtensionHint` logic.

See gh-41609
This commit is contained in:
Phillip Webb 2025-01-31 16:49:02 -08:00
parent aaaeb1ec12
commit e207e7ca83
14 changed files with 475 additions and 325 deletions

View File

@ -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);
}
}

View File

@ -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<EnvConfigDataResource> {
private final Function<String, String> readEnvVariable;
EnvConfigDataLoader() {
this.readEnvVariable = System::getenv;
}
EnvConfigDataLoader(Function<String, String> 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));
}
}

View File

@ -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 + "]";
}
}

View File

@ -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;
}
}

View File

@ -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<StandardConfigDataReference> 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);
}
}

View File

@ -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<SystemEnvironmentConfigDataResource> {
@Override
public ConfigData load(ConfigDataLoaderContext context, SystemEnvironmentConfigDataResource resource)
throws IOException, ConfigDataResourceNotFoundException {
List<PropertySource<?>> loaded = resource.load();
if (loaded == null) {
throw new ConfigDataResourceNotFoundException(resource);
}
return new ConfigData(loaded);
}
}

View File

@ -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<EnvConfigDataResource> {
class SystemEnvironmentConfigDataLocationResolver
implements ConfigDataLocationResolver<SystemEnvironmentConfigDataResource> {
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<PropertySourceLoader> loaders;
private final Function<String, String> readEnvVariable;
private final Function<String, String> environment;
EnvConfigDataLocationResolver() {
SystemEnvironmentConfigDataLocationResolver() {
this.loaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class, getClass().getClassLoader());
this.readEnvVariable = System::getenv;
this.environment = System::getenv;
}
EnvConfigDataLocationResolver(List<PropertySourceLoader> loaders, Function<String, String> readEnvVariable) {
SystemEnvironmentConfigDataLocationResolver(List<PropertySourceLoader> loaders,
Function<String, String> environment) {
this.loaders = loaders;
this.readEnvVariable = readEnvVariable;
this.environment = environment;
}
@Override
@ -58,15 +57,15 @@ class EnvConfigDataLocationResolver implements ConfigDataLocationResolver<EnvCon
}
@Override
public List<EnvConfigDataResource> resolve(ConfigDataLocationResolverContext context, ConfigDataLocation location)
public List<SystemEnvironmentConfigDataResource> 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<EnvCon
}
private PropertySourceLoader getLoader(String extension) {
if (extension == null) {
extension = DEFAULT_EXTENSION;
}
if (extension.startsWith(".")) {
extension = extension.substring(1);
}
extension = (!extension.startsWith(".")) ? extension : extension.substring(1);
for (PropertySourceLoader loader : this.loaders) {
for (String supportedExtension : loader.getFileExtensions()) {
if (supportedExtension.equalsIgnoreCase(extension)) {
@ -94,21 +88,7 @@ class EnvConfigDataLocationResolver implements ConfigDataLocationResolver<EnvCon
}
private boolean hasEnvVariable(String variableName) {
return this.readEnvVariable.apply(variableName) != null;
}
private String getVariableName(Matcher matcher, String value) {
if (matcher.matches()) {
return matcher.group(1);
}
return value;
}
private String getExtension(Matcher matcher) {
if (matcher.matches()) {
return matcher.group(2);
}
return null;
return this.environment.apply(variableName) != null;
}
}

View File

@ -0,0 +1,92 @@
/*
* 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.List;
import java.util.Objects;
import java.util.function.Function;
import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* {@link ConfigDataResource} used by {@link SystemEnvironmentConfigDataLoader}.
*
* @author Moritz Halbritter
*/
class SystemEnvironmentConfigDataResource extends ConfigDataResource {
private final String variableName;
private final PropertySourceLoader loader;
private final Function<String, String> environment;
SystemEnvironmentConfigDataResource(String variableName, PropertySourceLoader loader,
Function<String, String> environment) {
this.variableName = variableName;
this.loader = loader;
this.environment = environment;
}
String getVariableName() {
return this.variableName;
}
PropertySourceLoader getLoader() {
return this.loader;
}
List<PropertySource<?>> 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());
}
}

View File

@ -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=\

View File

@ -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);
}
}

View File

@ -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]");
}
}

View File

@ -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<String, String> envVariables;
private Map<String, String> 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);
}
}

View File

@ -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<String, String> envVariables;
private Map<String, String> 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<EnvConfigDataResource> resolved = this.resolver.resolve(this.context, location);
List<SystemEnvironmentConfigDataResource> 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");
ConfigDataLocation location = ConfigDataLocation.of("optional:env:VAR1");
List<EnvConfigDataResource> resolved = this.resolver.resolve(this.context, location);
assertThat(resolved).hasSize(1);
EnvConfigDataResource resource = resolved.get(0);
assertThat(resource.getLocation()).isEqualTo(location);
assertThat(resource.getVariableName()).isEqualTo("VAR1");
assertThat(resource.getLoader()).isInstanceOf(PropertiesPropertySourceLoader.class);
}
@Test
void shouldResolveOptionalIfVariableIsNotSet() {
ConfigDataLocation location = ConfigDataLocation.of("optional:env:VAR1");
List<EnvConfigDataResource> resolved = this.resolver.resolve(this.context, location);
assertThat(resolved).isEmpty();
}
@Test
void shouldResolveWithPropertiesExtension() {
this.envVariables.put("VAR1", "VALUE1");
ConfigDataLocation location = ConfigDataLocation.of("env:VAR1[.properties]");
List<EnvConfigDataResource> resolved = this.resolver.resolve(this.context, location);
assertThat(resolved).hasSize(1);
EnvConfigDataResource resource = resolved.get(0);
assertThat(resource.getLocation()).isEqualTo(location);
assertThat(resource.getVariableName()).isEqualTo("VAR1");
assertThat(resource.getLoader()).isInstanceOf(PropertiesPropertySourceLoader.class);
}
@Test
void shouldResolveWithYamlExtension() {
this.envVariables.put("VAR1", "VALUE1");
ConfigDataLocation location = ConfigDataLocation.of("env:VAR1[.yaml]");
List<EnvConfigDataResource> resolved = this.resolver.resolve(this.context, location);
assertThat(resolved).hasSize(1);
EnvConfigDataResource resource = resolved.get(0);
assertThat(resource.getLocation()).isEqualTo(location);
assertThat(resource.getVariableName()).isEqualTo("VAR1");
assertThat(resource.getLoader()).isInstanceOf(YamlPropertySourceLoader.class);
}
@Test
void shouldFailIfVariableIsNotSet() {
void resolveWhenHasNoVariableThrowsException() {
assertThatExceptionOfType(ConfigDataLocationNotFoundException.class)
.isThrownBy(() -> this.resolver.resolve(this.context, ConfigDataLocation.of("env:VAR1")))
.withMessage("Environment variable 'VAR1' is not set");
}
@Test
void shouldFailIfUnknownExtensionIsGiven() {
void resolveWhenOptionalAndHasVariableResolves() {
this.environment.put("VAR1", "VALUE1");
ConfigDataLocation location = ConfigDataLocation.of("optional:env:VAR1");
List<SystemEnvironmentConfigDataResource> resolved = this.resolver.resolve(this.context, location);
assertThat(resolved).hasSize(1);
SystemEnvironmentConfigDataResource resource = resolved.get(0);
assertThat(resource.getVariableName()).isEqualTo("VAR1");
assertThat(resource.getLoader()).isInstanceOf(PropertiesPropertySourceLoader.class);
}
@Test
void resolveWhenOptionalAndHasNoVariableResolvesEmpty() {
ConfigDataLocation location = ConfigDataLocation.of("optional:env:VAR1");
List<SystemEnvironmentConfigDataResource> resolved = this.resolver.resolve(this.context, location);
assertThat(resolved).isEmpty();
}
@Test
void resolveWhenHasPropertiesExtensionHintResolves() {
this.environment.put("VAR1", "VALUE1");
ConfigDataLocation location = ConfigDataLocation.of("env:VAR1[.properties]");
List<SystemEnvironmentConfigDataResource> resolved = this.resolver.resolve(this.context, location);
assertThat(resolved).hasSize(1);
SystemEnvironmentConfigDataResource resource = resolved.get(0);
assertThat(resource.getVariableName()).isEqualTo("VAR1");
assertThat(resource.getLoader()).isInstanceOf(PropertiesPropertySourceLoader.class);
}
@Test
void resolveWhenHasYamlExtensionHintResolves() {
this.environment.put("VAR1", "VALUE1");
ConfigDataLocation location = ConfigDataLocation.of("env:VAR1[.yaml]");
List<SystemEnvironmentConfigDataResource> resolved = this.resolver.resolve(this.context, location);
assertThat(resolved).hasSize(1);
SystemEnvironmentConfigDataResource resource = resolved.get(0);
assertThat(resource.getVariableName()).isEqualTo("VAR1");
assertThat(resource.getLoader()).isInstanceOf(YamlPropertySourceLoader.class);
}
@Test
void resolveWhenHasUnknownExtensionHintThrowsException() {
assertThatIllegalStateException()
.isThrownBy(() -> this.resolver.resolve(this.context, ConfigDataLocation.of("env:VAR1[.dummy]")))
.withMessage("File extension 'dummy' is not known to any PropertySourceLoader");

View File

@ -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<String, String> 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<PropertySource<?>> loaded = createResource("VAR1").load();
assertThat(loaded).hasSize(1);
assertThat(loaded.get(0).getProperty("key1")).isEqualTo("value1");
}
@Test
void loadWhenNoContentReturnsNull() throws IOException {
List<PropertySource<?>> 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);
}
}