Skip to content

Commit 3dc03ac

Browse files
committed
Refine non-optional classpath location checking
Update `StandardConfigDataLocationResolver` to no longer check if directories exist for classpath resources. Unfortunately checking for the parent directory of a `ClassPathResource` isn't always possible without resorting something similar to the `PathMatchingResourcePatternResolver` which would add a lot of complexity to the resolver. In order to ensure that non-optional locations are always resolved, the `ConfigDataEnvironment` now checks that all imported locations have been loaded. Closes gh-24143
1 parent 01478a2 commit 3dc03ac

9 files changed

+138
-61
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironment.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ private ConfigDataEnvironmentContributors processWithProfiles(ConfigDataEnvironm
297297
private void applyToEnvironment(ConfigDataEnvironmentContributors contributors,
298298
ConfigDataActivationContext activationContext) {
299299
checkForInvalidProperties(contributors);
300+
checkMandatoryLocations(contributors, activationContext);
300301
MutablePropertySources propertySources = this.environment.getPropertySources();
301302
this.logger.trace("Applying config data environment contributions");
302303
for (ConfigDataEnvironmentContributor contributor : contributors) {
@@ -327,4 +328,33 @@ private void checkForInvalidProperties(ConfigDataEnvironmentContributors contrib
327328
}
328329
}
329330

331+
private void checkMandatoryLocations(ConfigDataEnvironmentContributors contributors,
332+
ConfigDataActivationContext activationContext) {
333+
Set<ConfigDataLocation> mandatoryLocations = new LinkedHashSet<>();
334+
for (ConfigDataEnvironmentContributor contributor : contributors) {
335+
mandatoryLocations.addAll(getMandatoryImports(contributor));
336+
}
337+
for (ConfigDataEnvironmentContributor contributor : contributors) {
338+
if (contributor.getLocation() != null) {
339+
mandatoryLocations.remove(contributor.getLocation());
340+
}
341+
}
342+
if (!mandatoryLocations.isEmpty()) {
343+
for (ConfigDataLocation mandatoryLocation : mandatoryLocations) {
344+
this.notFoundAction.handle(this.logger, new ConfigDataLocationNotFoundException(mandatoryLocation));
345+
}
346+
}
347+
}
348+
349+
private Set<ConfigDataLocation> getMandatoryImports(ConfigDataEnvironmentContributor contributor) {
350+
List<ConfigDataLocation> imports = contributor.getImports();
351+
Set<ConfigDataLocation> mandatoryLocations = new LinkedHashSet<>(imports.size());
352+
for (ConfigDataLocation location : imports) {
353+
if (!location.isOptional()) {
354+
mandatoryLocations.add(location);
355+
}
356+
}
357+
return mandatoryLocations;
358+
}
359+
330360
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributor.java

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@
5151
*/
5252
class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironmentContributor> {
5353

54+
private final ConfigDataLocation location;
55+
5456
private final ConfigDataResource resource;
5557

5658
private final PropertySource<?> propertySource;
@@ -68,6 +70,7 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment
6870
/**
6971
* Create a new {@link ConfigDataEnvironmentContributor} instance.
7072
* @param kind the contributor kind
73+
* @param location the location of this contributor
7174
* @param resource the resource that contributed the data or {@code null}
7275
* @param propertySource the property source for the data or {@code null}
7376
* @param configurationPropertySource the configuration property source for the data
@@ -76,10 +79,12 @@ class ConfigDataEnvironmentContributor implements Iterable<ConfigDataEnvironment
7679
* @param ignoreImports if import properties should be ignored
7780
* @param children the children of this contributor at each {@link ImportPhase}
7881
*/
79-
ConfigDataEnvironmentContributor(Kind kind, ConfigDataResource resource, PropertySource<?> propertySource,
80-
ConfigurationPropertySource configurationPropertySource, ConfigDataProperties properties,
81-
boolean ignoreImports, Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children) {
82+
ConfigDataEnvironmentContributor(Kind kind, ConfigDataLocation location, ConfigDataResource resource,
83+
PropertySource<?> propertySource, ConfigurationPropertySource configurationPropertySource,
84+
ConfigDataProperties properties, boolean ignoreImports,
85+
Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children) {
8286
this.kind = kind;
87+
this.location = location;
8388
this.resource = resource;
8489
this.properties = properties;
8590
this.propertySource = propertySource;
@@ -96,6 +101,10 @@ Kind getKind() {
96101
return this.kind;
97102
}
98103

104+
ConfigDataLocation getLocation() {
105+
return this.location;
106+
}
107+
99108
/**
100109
* Return if this contributor is currently active.
101110
* @param activationContext the activation context
@@ -191,8 +200,8 @@ ConfigDataEnvironmentContributor withBoundProperties(Binder binder) {
191200
if (this.ignoreImports) {
192201
properties = properties.withoutImports();
193202
}
194-
return new ConfigDataEnvironmentContributor(Kind.BOUND_IMPORT, this.resource, this.propertySource,
195-
this.configurationPropertySource, properties, this.ignoreImports, null);
203+
return new ConfigDataEnvironmentContributor(Kind.BOUND_IMPORT, this.location, this.resource,
204+
this.propertySource, this.configurationPropertySource, properties, this.ignoreImports, null);
196205
}
197206

198207
/**
@@ -206,7 +215,7 @@ ConfigDataEnvironmentContributor withChildren(ImportPhase importPhase,
206215
List<ConfigDataEnvironmentContributor> children) {
207216
Map<ImportPhase, List<ConfigDataEnvironmentContributor>> updatedChildren = new LinkedHashMap<>(this.children);
208217
updatedChildren.put(importPhase, children);
209-
return new ConfigDataEnvironmentContributor(this.kind, this.resource, this.propertySource,
218+
return new ConfigDataEnvironmentContributor(this.kind, this.location, this.resource, this.propertySource,
210219
this.configurationPropertySource, this.properties, this.ignoreImports, updatedChildren);
211220
}
212221

@@ -231,7 +240,7 @@ ConfigDataEnvironmentContributor withReplacement(ConfigDataEnvironmentContributo
231240
}
232241
updatedChildren.put(importPhase, Collections.unmodifiableList(updatedContributors));
233242
});
234-
return new ConfigDataEnvironmentContributor(this.kind, this.resource, this.propertySource,
243+
return new ConfigDataEnvironmentContributor(this.kind, this.location, this.resource, this.propertySource,
235244
this.configurationPropertySource, this.properties, this.ignoreImports, updatedChildren);
236245
}
237246

@@ -243,7 +252,7 @@ ConfigDataEnvironmentContributor withReplacement(ConfigDataEnvironmentContributo
243252
static ConfigDataEnvironmentContributor of(List<ConfigDataEnvironmentContributor> contributors) {
244253
Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children = new LinkedHashMap<>();
245254
children.put(ImportPhase.BEFORE_PROFILE_ACTIVATION, Collections.unmodifiableList(contributors));
246-
return new ConfigDataEnvironmentContributor(Kind.ROOT, null, null, null, null, false, children);
255+
return new ConfigDataEnvironmentContributor(Kind.ROOT, null, null, null, null, null, false, children);
247256
}
248257

249258
/**
@@ -256,7 +265,8 @@ static ConfigDataEnvironmentContributor of(List<ConfigDataEnvironmentContributor
256265
static ConfigDataEnvironmentContributor ofInitialImport(ConfigDataLocation initialImport) {
257266
List<ConfigDataLocation> imports = Collections.singletonList(initialImport);
258267
ConfigDataProperties properties = new ConfigDataProperties(imports, null);
259-
return new ConfigDataEnvironmentContributor(Kind.INITIAL_IMPORT, null, null, null, properties, false, null);
268+
return new ConfigDataEnvironmentContributor(Kind.INITIAL_IMPORT, null, null, null, null, properties, false,
269+
null);
260270
}
261271

262272
/**
@@ -267,25 +277,26 @@ static ConfigDataEnvironmentContributor ofInitialImport(ConfigDataLocation initi
267277
* @return a new {@link ConfigDataEnvironmentContributor} instance
268278
*/
269279
static ConfigDataEnvironmentContributor ofExisting(PropertySource<?> propertySource) {
270-
return new ConfigDataEnvironmentContributor(Kind.EXISTING, null, propertySource,
280+
return new ConfigDataEnvironmentContributor(Kind.EXISTING, null, null, propertySource,
271281
ConfigurationPropertySource.from(propertySource), null, false, null);
272282
}
273283

274284
/**
275285
* Factory method to create an {@link Kind#UNBOUND_IMPORT unbound import} contributor.
276286
* This contributor has been actively imported from another contributor and may itself
277287
* import further contributors later.
278-
* @param resource the condig data resource
288+
* @param location the location of this contributor
289+
* @param resource the config data resource
279290
* @param configData the config data
280291
* @param propertySourceIndex the index of the property source that should be used
281292
* @return a new {@link ConfigDataEnvironmentContributor} instance
282293
*/
283-
static ConfigDataEnvironmentContributor ofUnboundImport(ConfigDataResource resource, ConfigData configData,
284-
int propertySourceIndex) {
294+
static ConfigDataEnvironmentContributor ofUnboundImport(ConfigDataLocation location, ConfigDataResource resource,
295+
ConfigData configData, int propertySourceIndex) {
285296
PropertySource<?> propertySource = configData.getPropertySources().get(propertySourceIndex);
286297
ConfigurationPropertySource configurationPropertySource = ConfigurationPropertySource.from(propertySource);
287298
boolean ignoreImports = configData.getOptions().contains(ConfigData.Option.IGNORE_IMPORTS);
288-
return new ConfigDataEnvironmentContributor(Kind.UNBOUND_IMPORT, resource, propertySource,
299+
return new ConfigDataEnvironmentContributor(Kind.UNBOUND_IMPORT, location, resource, propertySource,
289300
configurationPropertySource, null, ignoreImports, null);
290301
}
291302

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributors.java

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.List;
2525
import java.util.Map;
2626
import java.util.Set;
27+
import java.util.stream.Collectors;
2728
import java.util.stream.Stream;
2829

2930
import org.apache.commons.logging.Log;
@@ -115,10 +116,9 @@ ConfigDataEnvironmentContributors withProcessedImports(ConfigDataImporter import
115116
ConfigDataLoaderContext loaderContext = new ContributorDataLoaderContext(this);
116117
List<ConfigDataLocation> imports = contributor.getImports();
117118
this.logger.trace(LogMessage.format("Processing imports %s", imports));
118-
Map<ConfigDataResource, ConfigData> imported = importer.resolveAndLoad(activationContext,
119+
Map<ConfigDataResolutionResult, ConfigData> imported = importer.resolveAndLoad(activationContext,
119120
locationResolverContext, loaderContext, imports);
120-
this.logger.trace(LogMessage.of(() -> imported.isEmpty() ? "Nothing imported" : "Imported "
121-
+ imported.size() + " resource " + ((imported.size() != 1) ? "s" : "") + imported.keySet()));
121+
this.logger.trace(LogMessage.of(() -> getImportedMessage(imported.keySet())));
122122
ConfigDataEnvironmentContributor contributorAndChildren = contributor.withChildren(importPhase,
123123
asContributors(imported));
124124
result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext,
@@ -127,6 +127,16 @@ ConfigDataEnvironmentContributors withProcessedImports(ConfigDataImporter import
127127
}
128128
}
129129

130+
private CharSequence getImportedMessage(Set<ConfigDataResolutionResult> results) {
131+
if (results.isEmpty()) {
132+
return "Nothing imported";
133+
}
134+
StringBuilder message = new StringBuilder();
135+
message.append("Imported " + results.size() + " resource" + ((results.size() != 1) ? "s " : " "));
136+
message.append(results.stream().map(ConfigDataResolutionResult::getResource).collect(Collectors.toList()));
137+
return message;
138+
}
139+
130140
protected final ConfigurableBootstrapContext getBootstrapContext() {
131141
return this.bootstrapContext;
132142
}
@@ -147,11 +157,14 @@ private boolean isActiveWithUnprocessedImports(ConfigDataActivationContext activ
147157
return contributor.isActive(activationContext) && contributor.hasUnprocessedImports(importPhase);
148158
}
149159

150-
private List<ConfigDataEnvironmentContributor> asContributors(Map<ConfigDataResource, ConfigData> imported) {
160+
private List<ConfigDataEnvironmentContributor> asContributors(
161+
Map<ConfigDataResolutionResult, ConfigData> imported) {
151162
List<ConfigDataEnvironmentContributor> contributors = new ArrayList<>(imported.size() * 5);
152-
imported.forEach((location, data) -> {
163+
imported.forEach((resolutionResult, data) -> {
153164
for (int i = data.getPropertySources().size() - 1; i >= 0; i--) {
154-
contributors.add(ConfigDataEnvironmentContributor.ofUnboundImport(location, data, i));
165+
ConfigDataLocation location = resolutionResult.getLocation();
166+
ConfigDataResource resource = resolutionResult.getResource();
167+
contributors.add(ConfigDataEnvironmentContributor.ofUnboundImport(location, resource, data, i));
155168
}
156169
});
157170
return Collections.unmodifiableList(contributors);

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigDataImporter.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ class ConfigDataImporter {
7373
* @param locations the locations to resolve
7474
* @return a map of the loaded locations and data
7575
*/
76-
Map<ConfigDataResource, ConfigData> resolveAndLoad(ConfigDataActivationContext activationContext,
76+
Map<ConfigDataResolutionResult, ConfigData> resolveAndLoad(ConfigDataActivationContext activationContext,
7777
ConfigDataLocationResolverContext locationResolverContext, ConfigDataLoaderContext loaderContext,
7878
List<ConfigDataLocation> locations) {
7979
try {
@@ -106,9 +106,9 @@ private List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolverConte
106106
}
107107
}
108108

109-
private Map<ConfigDataResource, ConfigData> load(ConfigDataLoaderContext loaderContext,
109+
private Map<ConfigDataResolutionResult, ConfigData> load(ConfigDataLoaderContext loaderContext,
110110
List<ConfigDataResolutionResult> candidates) throws IOException {
111-
Map<ConfigDataResource, ConfigData> result = new LinkedHashMap<>();
111+
Map<ConfigDataResolutionResult, ConfigData> result = new LinkedHashMap<>();
112112
for (int i = candidates.size() - 1; i >= 0; i--) {
113113
ConfigDataResolutionResult candidate = candidates.get(i);
114114
ConfigDataLocation location = candidate.getLocation();
@@ -117,7 +117,7 @@ private Map<ConfigDataResource, ConfigData> load(ConfigDataLoaderContext loaderC
117117
try {
118118
ConfigData loaded = this.loaders.load(loaderContext, resource);
119119
if (loaded != null) {
120-
result.put(resource, loaded);
120+
result.put(candidate, loaded);
121121
}
122122
}
123123
catch (ConfigDataNotFoundException ex) {

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/StandardConfigDataLocationResolver.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.boot.env.PropertySourceLoader;
3232
import org.springframework.core.Ordered;
3333
import org.springframework.core.env.Environment;
34+
import org.springframework.core.io.ClassPathResource;
3435
import org.springframework.core.io.Resource;
3536
import org.springframework.core.io.ResourceLoader;
3637
import org.springframework.core.io.support.SpringFactoriesLoader;
@@ -234,8 +235,10 @@ private void assertNonOptionalDirectories(Set<StandardConfigDataReference> refer
234235

235236
private void assertDirectoryExists(StandardConfigDataReference reference) {
236237
Resource resource = this.resourceLoader.getResource(reference.getDirectory());
237-
StandardConfigDataResource configDataResource = new StandardConfigDataResource(reference, resource);
238-
ConfigDataResourceNotFoundException.throwIfDoesNotExist(configDataResource, resource);
238+
if (!(resource instanceof ClassPathResource)) {
239+
StandardConfigDataResource configDataResource = new StandardConfigDataResource(reference, resource);
240+
ConfigDataResourceNotFoundException.throwIfDoesNotExist(configDataResource, resource);
241+
}
239242
}
240243

241244
private List<StandardConfigDataResource> resolve(StandardConfigDataReference reference) {

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorPlaceholdersResolverTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ static class TestConfigDataEnvironmentContributor extends ConfigDataEnvironmentC
122122
private final boolean active;
123123

124124
protected TestConfigDataEnvironmentContributor(PropertySource<?> propertySource, boolean active) {
125-
super(Kind.ROOT, null, propertySource, null, null, false, null);
125+
super(Kind.ROOT, null, null, propertySource, null, null, false, null);
126126
this.active = active;
127127
}
128128

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ConfigDataEnvironmentContributorTests.java

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ void isActiveWhenPropertiesIsNotActiveReturnsFalse() {
8383
void getLocationReturnsLocation() {
8484
ConfigData configData = new ConfigData(Collections.singleton(new MockPropertySource()));
8585
ConfigDataResource resource = mock(ConfigDataResource.class);
86-
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofUnboundImport(resource,
87-
configData, 0);
86+
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofUnboundImport(TEST_LOCATION,
87+
resource, configData, 0);
8888
assertThat(contributor.getResource()).isSameAs(resource);
8989
}
9090

@@ -100,7 +100,7 @@ void getConfigurationPropertySourceReturnsAdaptedPropertySource() {
100100
MockPropertySource propertySource = new MockPropertySource();
101101
propertySource.setProperty("spring", "boot");
102102
ConfigData configData = new ConfigData(Collections.singleton(propertySource));
103-
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofUnboundImport(null,
103+
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofUnboundImport(null, null,
104104
configData, 0);
105105
assertThat(contributor.getConfigurationPropertySource()
106106
.getConfigurationProperty(ConfigurationPropertyName.of("spring")).getValue()).isEqualTo("boot");
@@ -279,14 +279,14 @@ void ofExistingCreatesExistingContributor() {
279279

280280
@Test
281281
void ofUnboundImportCreatesImportedContributor() {
282-
TestResource location = new TestResource("test");
282+
TestResource resource = new TestResource("test");
283283
MockPropertySource propertySource = new MockPropertySource();
284284
propertySource.setProperty("spring.config.import", "test");
285285
ConfigData configData = new ConfigData(Collections.singleton(propertySource));
286-
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofUnboundImport(location,
287-
configData, 0);
286+
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofUnboundImport(TEST_LOCATION,
287+
resource, configData, 0);
288288
assertThat(contributor.getKind()).isEqualTo(Kind.UNBOUND_IMPORT);
289-
assertThat(contributor.getResource()).isSameAs(location);
289+
assertThat(contributor.getResource()).isSameAs(resource);
290290
assertThat(contributor.getImports()).isEmpty();
291291
assertThat(contributor.isActive(this.activationContext)).isTrue();
292292
assertThat(contributor.getPropertySource()).isEqualTo(propertySource);
@@ -296,13 +296,13 @@ void ofUnboundImportCreatesImportedContributor() {
296296

297297
@Test
298298
void bindCreatesImportedContributor() {
299-
TestResource location = new TestResource("test");
299+
TestResource resource = new TestResource("test");
300300
MockPropertySource propertySource = new MockPropertySource();
301301
propertySource.setProperty("spring.config.import", "test");
302302
ConfigData configData = new ConfigData(Collections.singleton(propertySource));
303-
ConfigDataEnvironmentContributor contributor = createBoundContributor(location, configData, 0);
303+
ConfigDataEnvironmentContributor contributor = createBoundContributor(resource, configData, 0);
304304
assertThat(contributor.getKind()).isEqualTo(Kind.BOUND_IMPORT);
305-
assertThat(contributor.getResource()).isSameAs(location);
305+
assertThat(contributor.getResource()).isSameAs(resource);
306306
assertThat(contributor.getImports()).containsExactly(TEST_LOCATION);
307307
assertThat(contributor.isActive(this.activationContext)).isTrue();
308308
assertThat(contributor.getPropertySource()).isEqualTo(propertySource);
@@ -312,13 +312,13 @@ void bindCreatesImportedContributor() {
312312

313313
@Test
314314
void bindWhenConfigDataHasIgnoreImportsOptionsCreatesImportedContributorWithoutImports() {
315-
TestResource location = new TestResource("test");
315+
TestResource resource = new TestResource("test");
316316
MockPropertySource propertySource = new MockPropertySource();
317317
propertySource.setProperty("spring.config.import", "test");
318318
ConfigData configData = new ConfigData(Collections.singleton(propertySource), ConfigData.Option.IGNORE_IMPORTS);
319-
ConfigDataEnvironmentContributor contributor = createBoundContributor(location, configData, 0);
319+
ConfigDataEnvironmentContributor contributor = createBoundContributor(resource, configData, 0);
320320
assertThat(contributor.getKind()).isEqualTo(Kind.BOUND_IMPORT);
321-
assertThat(contributor.getResource()).isSameAs(location);
321+
assertThat(contributor.getResource()).isSameAs(resource);
322322
assertThat(contributor.getImports()).isEmpty();
323323
assertThat(contributor.isActive(this.activationContext)).isTrue();
324324
assertThat(contributor.getPropertySource()).isEqualTo(propertySource);
@@ -341,8 +341,8 @@ private ConfigDataEnvironmentContributor createBoundContributor(String location)
341341

342342
private ConfigDataEnvironmentContributor createBoundContributor(ConfigDataResource resource, ConfigData configData,
343343
int propertySourceIndex) {
344-
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofUnboundImport(resource,
345-
configData, propertySourceIndex);
344+
ConfigDataEnvironmentContributor contributor = ConfigDataEnvironmentContributor.ofUnboundImport(TEST_LOCATION,
345+
resource, configData, propertySourceIndex);
346346
Binder binder = new Binder(contributor.getConfigurationPropertySource());
347347
return contributor.withBoundProperties(binder);
348348
}

0 commit comments

Comments
 (0)