Allow optional ConfigDataLocationResolver results
Update `ConfigData` so that it signal if is considered optional. This update allows `ConfigDataLocationResolvers` to return results that behave in the same way as `optional:` prefixed locations without the user themselves needing to prefix the location string. Closes gh-25894
This commit is contained in:
parent
2df5050040
commit
0e3ef4071e
|
@ -233,7 +233,8 @@ class ConfigDataEnvironment {
|
|||
contributors = processWithoutProfiles(contributors, importer, activationContext);
|
||||
activationContext = withProfiles(contributors, activationContext);
|
||||
contributors = processWithProfiles(contributors, importer, activationContext);
|
||||
applyToEnvironment(contributors, activationContext, importer.getLoadedLocations());
|
||||
applyToEnvironment(contributors, activationContext, importer.getLoadedLocations(),
|
||||
importer.getOptionalLocations());
|
||||
}
|
||||
|
||||
private ConfigDataEnvironmentContributors processInitial(ConfigDataEnvironmentContributors contributors,
|
||||
|
@ -322,9 +323,10 @@ class ConfigDataEnvironment {
|
|||
}
|
||||
|
||||
private void applyToEnvironment(ConfigDataEnvironmentContributors contributors,
|
||||
ConfigDataActivationContext activationContext, Set<ConfigDataLocation> loadedLocations) {
|
||||
ConfigDataActivationContext activationContext, Set<ConfigDataLocation> loadedLocations,
|
||||
Set<ConfigDataLocation> optionalLocations) {
|
||||
checkForInvalidProperties(contributors);
|
||||
checkMandatoryLocations(contributors, activationContext, loadedLocations);
|
||||
checkMandatoryLocations(contributors, activationContext, loadedLocations, optionalLocations);
|
||||
MutablePropertySources propertySources = this.environment.getPropertySources();
|
||||
this.logger.trace("Applying config data environment contributions");
|
||||
for (ConfigDataEnvironmentContributor contributor : contributors) {
|
||||
|
@ -359,7 +361,8 @@ class ConfigDataEnvironment {
|
|||
}
|
||||
|
||||
private void checkMandatoryLocations(ConfigDataEnvironmentContributors contributors,
|
||||
ConfigDataActivationContext activationContext, Set<ConfigDataLocation> loadedLocations) {
|
||||
ConfigDataActivationContext activationContext, Set<ConfigDataLocation> loadedLocations,
|
||||
Set<ConfigDataLocation> optionalLocations) {
|
||||
Set<ConfigDataLocation> mandatoryLocations = new LinkedHashSet<>();
|
||||
for (ConfigDataEnvironmentContributor contributor : contributors) {
|
||||
if (contributor.isActive(activationContext)) {
|
||||
|
@ -372,6 +375,7 @@ class ConfigDataEnvironment {
|
|||
}
|
||||
}
|
||||
mandatoryLocations.removeAll(loadedLocations);
|
||||
mandatoryLocations.removeAll(optionalLocations);
|
||||
if (!mandatoryLocations.isEmpty()) {
|
||||
for (ConfigDataLocation mandatoryLocation : mandatoryLocations) {
|
||||
this.notFoundAction.handle(this.logger, new ConfigDataLocationNotFoundException(mandatoryLocation));
|
||||
|
|
|
@ -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.
|
||||
|
@ -51,6 +51,8 @@ class ConfigDataImporter {
|
|||
|
||||
private final Set<ConfigDataLocation> loadedLocations = new HashSet<>();
|
||||
|
||||
private final Set<ConfigDataLocation> optionalLocations = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Create a new {@link ConfigDataImporter} instance.
|
||||
* @param logFactory the log factory
|
||||
|
@ -103,7 +105,7 @@ class ConfigDataImporter {
|
|||
return this.resolvers.resolve(locationResolverContext, location, profiles);
|
||||
}
|
||||
catch (ConfigDataNotFoundException ex) {
|
||||
handle(ex, location);
|
||||
handle(ex, location, null);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
@ -115,6 +117,9 @@ class ConfigDataImporter {
|
|||
ConfigDataResolutionResult candidate = candidates.get(i);
|
||||
ConfigDataLocation location = candidate.getLocation();
|
||||
ConfigDataResource resource = candidate.getResource();
|
||||
if (resource.isOptional()) {
|
||||
this.optionalLocations.add(location);
|
||||
}
|
||||
if (this.loaded.contains(resource)) {
|
||||
this.loadedLocations.add(location);
|
||||
}
|
||||
|
@ -128,26 +133,33 @@ class ConfigDataImporter {
|
|||
}
|
||||
}
|
||||
catch (ConfigDataNotFoundException ex) {
|
||||
handle(ex, location);
|
||||
handle(ex, location, resource);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableMap(result);
|
||||
}
|
||||
|
||||
private void handle(ConfigDataNotFoundException ex, ConfigDataLocation location) {
|
||||
private void handle(ConfigDataNotFoundException ex, ConfigDataLocation location, ConfigDataResource resource) {
|
||||
if (ex instanceof ConfigDataResourceNotFoundException) {
|
||||
ex = ((ConfigDataResourceNotFoundException) ex).withLocation(location);
|
||||
}
|
||||
getNotFoundAction(location).handle(this.logger, ex);
|
||||
getNotFoundAction(location, resource).handle(this.logger, ex);
|
||||
}
|
||||
|
||||
private ConfigDataNotFoundAction getNotFoundAction(ConfigDataLocation location) {
|
||||
return (!location.isOptional()) ? this.notFoundAction : ConfigDataNotFoundAction.IGNORE;
|
||||
private ConfigDataNotFoundAction getNotFoundAction(ConfigDataLocation location, ConfigDataResource resource) {
|
||||
if (location.isOptional() || (resource != null && resource.isOptional())) {
|
||||
return ConfigDataNotFoundAction.IGNORE;
|
||||
}
|
||||
return this.notFoundAction;
|
||||
}
|
||||
|
||||
Set<ConfigDataLocation> getLoadedLocations() {
|
||||
return this.loadedLocations;
|
||||
}
|
||||
|
||||
Set<ConfigDataLocation> getOptionalLocations() {
|
||||
return this.optionalLocations;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
@ -27,4 +27,26 @@ package org.springframework.boot.context.config;
|
|||
*/
|
||||
public abstract class ConfigDataResource {
|
||||
|
||||
private final boolean optional;
|
||||
|
||||
/**
|
||||
* Create a new non-optional {@link ConfigDataResource} instance.
|
||||
*/
|
||||
public ConfigDataResource() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link ConfigDataResource} instance.
|
||||
* @param optional if the resource is optional
|
||||
* @since 2.4.6
|
||||
*/
|
||||
protected ConfigDataResource(boolean optional) {
|
||||
this.optional = optional;
|
||||
}
|
||||
|
||||
boolean isOptional() {
|
||||
return this.optional;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@ import org.springframework.boot.context.properties.bind.Binder;
|
|||
import org.springframework.boot.context.properties.source.ConfigurationProperty;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
|
||||
import org.springframework.boot.origin.Origin;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
|
@ -581,6 +582,12 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests {
|
|||
+ StringUtils.cleanPath(location.getAbsolutePath())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void runWhenResolvedIsOptionalDoesNotThrowException() {
|
||||
ApplicationContext context = this.application.run("--spring.config.location=test:optionalresult");
|
||||
assertThat(context.getEnvironment().containsProperty("spring")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("Disabled until spring.profiles suppport is dropped")
|
||||
void runWhenUsingInvalidPropertyThrowsException() {
|
||||
|
@ -752,4 +759,44 @@ class ConfigDataEnvironmentPostProcessorIntegrationTests {
|
|||
|
||||
}
|
||||
|
||||
static class LocationResolver implements ConfigDataLocationResolver<TestConfigDataResource> {
|
||||
|
||||
@Override
|
||||
public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location) {
|
||||
return location.hasPrefix("test:");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TestConfigDataResource> resolve(ConfigDataLocationResolverContext context,
|
||||
ConfigDataLocation location)
|
||||
throws ConfigDataLocationNotFoundException, ConfigDataResourceNotFoundException {
|
||||
return Collections.singletonList(new TestConfigDataResource(location));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class Loader implements ConfigDataLoader<TestConfigDataResource> {
|
||||
|
||||
@Override
|
||||
public ConfigData load(ConfigDataLoaderContext context, TestConfigDataResource resource)
|
||||
throws IOException, ConfigDataResourceNotFoundException {
|
||||
if (resource.isOptional()) {
|
||||
return null;
|
||||
}
|
||||
MapPropertySource propertySource = new MapPropertySource("loaded",
|
||||
Collections.singletonMap("spring", "boot"));
|
||||
return new ConfigData(Collections.singleton(propertySource));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class TestConfigDataResource extends ConfigDataResource {
|
||||
|
||||
TestConfigDataResource(ConfigDataLocation location) {
|
||||
super(location.toString().contains("optionalresult"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -168,8 +168,27 @@ class ConfigDataLocationResolversTests {
|
|||
.satisfies((ex) -> assertThat(ex.getLocation()).isEqualTo(location));
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolveWhenOptional() {
|
||||
ConfigDataLocationResolvers resolvers = new ConfigDataLocationResolvers(this.logFactory, this.bootstrapContext,
|
||||
this.binder, this.resourceLoader, Arrays.asList(OptionalResourceTestResolver.class.getName()));
|
||||
ConfigDataLocation location = ConfigDataLocation.of("OptionalResourceTestResolver:test");
|
||||
List<ConfigDataResolutionResult> resolved = resolvers.resolve(this.context, location, null);
|
||||
assertThat(resolved.get(0).getResource().isOptional()).isTrue();
|
||||
}
|
||||
|
||||
static class TestResolver implements ConfigDataLocationResolver<TestConfigDataResource> {
|
||||
|
||||
private final boolean optionalResource;
|
||||
|
||||
TestResolver() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
TestResolver(boolean optionalResource) {
|
||||
this.optionalResource = optionalResource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResolvable(ConfigDataLocationResolverContext context, ConfigDataLocation location) {
|
||||
String name = getClass().getName();
|
||||
|
@ -181,13 +200,13 @@ class ConfigDataLocationResolversTests {
|
|||
public List<TestConfigDataResource> resolve(ConfigDataLocationResolverContext context,
|
||||
ConfigDataLocation location)
|
||||
throws ConfigDataLocationNotFoundException, ConfigDataResourceNotFoundException {
|
||||
return Collections.singletonList(new TestConfigDataResource(this, location, false));
|
||||
return Collections.singletonList(new TestConfigDataResource(this.optionalResource, this, location, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TestConfigDataResource> resolveProfileSpecific(ConfigDataLocationResolverContext context,
|
||||
ConfigDataLocation location, Profiles profiles) throws ConfigDataLocationNotFoundException {
|
||||
return Collections.singletonList(new TestConfigDataResource(this, location, true));
|
||||
return Collections.singletonList(new TestConfigDataResource(this.optionalResource, this, location, true));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -250,6 +269,14 @@ class ConfigDataLocationResolversTests {
|
|||
|
||||
}
|
||||
|
||||
static class OptionalResourceTestResolver extends TestResolver {
|
||||
|
||||
OptionalResourceTestResolver() {
|
||||
super(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class TestConfigDataResource extends ConfigDataResource {
|
||||
|
||||
private final TestResolver resolver;
|
||||
|
@ -258,7 +285,9 @@ class ConfigDataLocationResolversTests {
|
|||
|
||||
private final boolean profileSpecific;
|
||||
|
||||
TestConfigDataResource(TestResolver resolver, ConfigDataLocation location, boolean profileSpecific) {
|
||||
TestConfigDataResource(boolean optional, TestResolver resolver, ConfigDataLocation location,
|
||||
boolean profileSpecific) {
|
||||
super(optional);
|
||||
this.resolver = resolver;
|
||||
this.location = location;
|
||||
this.profileSpecific = profileSpecific;
|
||||
|
|
|
@ -3,9 +3,11 @@ org.springframework.boot.context.config.TestPropertySourceLoader1,\
|
|||
org.springframework.boot.context.config.TestPropertySourceLoader2
|
||||
|
||||
org.springframework.boot.context.config.ConfigDataLocationResolver=\
|
||||
org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessorIntegrationTests.LocationResolver,\
|
||||
org.springframework.boot.context.config.TestConfigDataBootstrap.LocationResolver,\
|
||||
org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessorImportCombinedWithProfileSpecificIntegrationTests.LocationResolver
|
||||
|
||||
org.springframework.boot.context.config.ConfigDataLoader=\
|
||||
org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessorIntegrationTests.Loader,\
|
||||
org.springframework.boot.context.config.TestConfigDataBootstrap.Loader,\
|
||||
org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessorImportCombinedWithProfileSpecificIntegrationTests.Loader
|
||||
|
|
Loading…
Reference in New Issue