Skip to content

Commit 12cfb1f

Browse files
committed
Merge branch '3.2.x'
Closes gh-40560
2 parents 3c00bf3 + 8a3b0cd commit 12cfb1f

File tree

7 files changed

+187
-23
lines changed

7 files changed

+187
-23
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.ssl;
18+
19+
/**
20+
* Thrown when a bundle content location is not watchable.
21+
*
22+
* @author Moritz Halbritter
23+
*/
24+
class BundleContentNotWatchableException extends RuntimeException {
25+
26+
private final BundleContentProperty property;
27+
28+
BundleContentNotWatchableException(BundleContentProperty property) {
29+
super("The content of '%s' is not watchable. Only 'file:' resources are watchable, but '%s' has been set"
30+
.formatted(property.name(), property.value()));
31+
this.property = property;
32+
}
33+
34+
private BundleContentNotWatchableException(String bundleName, BundleContentProperty property, Throwable cause) {
35+
super("The content of '%s' from bundle '%s' is not watchable'. Only 'file:' resources are watchable, but '%s' has been set"
36+
.formatted(property.name(), bundleName, property.value()), cause);
37+
this.property = property;
38+
}
39+
40+
BundleContentNotWatchableException withBundleName(String bundleName) {
41+
return new BundleContentNotWatchableException(bundleName, this.property, this);
42+
}
43+
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.ssl;
18+
19+
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
20+
import org.springframework.boot.diagnostics.FailureAnalysis;
21+
22+
/**
23+
* An {@link AbstractFailureAnalyzer} that performs analysis of non-watchable bundle
24+
* content failures caused by {@link BundleContentNotWatchableException}.
25+
*
26+
* @author Moritz Halbritter
27+
*/
28+
class BundleContentNotWatchableFailureAnalyzer extends AbstractFailureAnalyzer<BundleContentNotWatchableException> {
29+
30+
@Override
31+
protected FailureAnalysis analyze(Throwable rootFailure, BundleContentNotWatchableException cause) {
32+
return new FailureAnalysis(cause.getMessage(), "Update your application to correct the invalid configuration:\n"
33+
+ "Either use a watchable resource, or disable bundle reloading by setting reload-on-update = false on the bundle.",
34+
cause);
35+
}
36+
37+
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/BundleContentProperty.java

+7-5
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
* @param name the configuration property name (excluding any prefix)
3232
* @param value the configuration property value
3333
* @author Phillip Webb
34+
* @author Moritz Halbritter
3435
*/
3536
record BundleContentProperty(String name, String value) {
3637

@@ -51,16 +52,17 @@ boolean hasValue() {
5152
}
5253

5354
Path toWatchPath() {
54-
return toPath();
55-
}
56-
57-
private Path toPath() {
5855
try {
5956
Resource resource = getResource();
60-
Assert.state(resource.isFile(), () -> "Value '%s' is not a file resource".formatted(this.value));
57+
if (!resource.isFile()) {
58+
throw new BundleContentNotWatchableException(this);
59+
}
6160
return Path.of(resource.getFile().getAbsolutePath());
6261
}
6362
catch (Exception ex) {
63+
if (ex instanceof BundleContentNotWatchableException bundleContentNotWatchableException) {
64+
throw bundleContentNotWatchableException;
65+
}
6466
throw new IllegalStateException("Unable to convert value of property '%s' to a path".formatted(this.name),
6567
ex);
6668
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/ssl/SslPropertiesBundleRegistrar.java

+31-17
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,14 @@ public void registerBundles(SslBundleRegistry registry) {
5454
}
5555

5656
private <P extends SslBundleProperties> void registerBundles(SslBundleRegistry registry, Map<String, P> properties,
57-
Function<P, SslBundle> bundleFactory, Function<P, Set<Path>> watchedPaths) {
57+
Function<P, SslBundle> bundleFactory, Function<Bundle<P>, Set<Path>> watchedPaths) {
5858
properties.forEach((bundleName, bundleProperties) -> {
5959
Supplier<SslBundle> bundleSupplier = () -> bundleFactory.apply(bundleProperties);
6060
try {
6161
registry.registerBundle(bundleName, bundleSupplier.get());
6262
if (bundleProperties.isReloadOnUpdate()) {
63-
Supplier<Set<Path>> pathsSupplier = () -> watchedPaths.apply(bundleProperties);
63+
Supplier<Set<Path>> pathsSupplier = () -> watchedPaths
64+
.apply(new Bundle<>(bundleName, bundleProperties));
6465
watchForUpdates(registry, bundleName, pathsSupplier, bundleSupplier);
6566
}
6667
}
@@ -80,27 +81,40 @@ private void watchForUpdates(SslBundleRegistry registry, String bundleName, Supp
8081
}
8182
}
8283

83-
private Set<Path> watchedJksPaths(JksSslBundleProperties properties) {
84+
private Set<Path> watchedJksPaths(Bundle<JksSslBundleProperties> bundle) {
8485
List<BundleContentProperty> watched = new ArrayList<>();
85-
watched.add(new BundleContentProperty("keystore.location", properties.getKeystore().getLocation()));
86-
watched.add(new BundleContentProperty("truststore.location", properties.getTruststore().getLocation()));
87-
return watchedPaths(watched);
86+
watched.add(new BundleContentProperty("keystore.location", bundle.properties().getKeystore().getLocation()));
87+
watched
88+
.add(new BundleContentProperty("truststore.location", bundle.properties().getTruststore().getLocation()));
89+
return watchedPaths(bundle.name(), watched);
8890
}
8991

90-
private Set<Path> watchedPemPaths(PemSslBundleProperties properties) {
92+
private Set<Path> watchedPemPaths(Bundle<PemSslBundleProperties> bundle) {
9193
List<BundleContentProperty> watched = new ArrayList<>();
92-
watched.add(new BundleContentProperty("keystore.private-key", properties.getKeystore().getPrivateKey()));
93-
watched.add(new BundleContentProperty("keystore.certificate", properties.getKeystore().getCertificate()));
94-
watched.add(new BundleContentProperty("truststore.private-key", properties.getTruststore().getPrivateKey()));
95-
watched.add(new BundleContentProperty("truststore.certificate", properties.getTruststore().getCertificate()));
96-
return watchedPaths(watched);
94+
watched
95+
.add(new BundleContentProperty("keystore.private-key", bundle.properties().getKeystore().getPrivateKey()));
96+
watched
97+
.add(new BundleContentProperty("keystore.certificate", bundle.properties().getKeystore().getCertificate()));
98+
watched.add(new BundleContentProperty("truststore.private-key",
99+
bundle.properties().getTruststore().getPrivateKey()));
100+
watched.add(new BundleContentProperty("truststore.certificate",
101+
bundle.properties().getTruststore().getCertificate()));
102+
return watchedPaths(bundle.name(), watched);
97103
}
98104

99-
private Set<Path> watchedPaths(List<BundleContentProperty> properties) {
100-
return properties.stream()
101-
.filter(BundleContentProperty::hasValue)
102-
.map(BundleContentProperty::toWatchPath)
103-
.collect(Collectors.toSet());
105+
private Set<Path> watchedPaths(String bundleName, List<BundleContentProperty> properties) {
106+
try {
107+
return properties.stream()
108+
.filter(BundleContentProperty::hasValue)
109+
.map(BundleContentProperty::toWatchPath)
110+
.collect(Collectors.toSet());
111+
}
112+
catch (BundleContentNotWatchableException ex) {
113+
throw ex.withBundleName(bundleName);
114+
}
115+
}
116+
117+
private record Bundle<P>(String name, P properties) {
104118
}
105119

106120
}

spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories

+2-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ org.springframework.boot.autoconfigure.jooq.NoDslContextBeanFailureAnalyzer,\
3131
org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryBeanCreationFailureAnalyzer,\
3232
org.springframework.boot.autoconfigure.r2dbc.MissingR2dbcPoolDependencyFailureAnalyzer,\
3333
org.springframework.boot.autoconfigure.r2dbc.MultipleConnectionPoolConfigurationsFailureAnalyzer,\
34-
org.springframework.boot.autoconfigure.r2dbc.NoConnectionFactoryBeanFailureAnalyzer
34+
org.springframework.boot.autoconfigure.r2dbc.NoConnectionFactoryBeanFailureAnalyzer,\
35+
org.springframework.boot.autoconfigure.ssl.BundleContentNotWatchableFailureAnalyzer
3536

3637
# Template Availability Providers
3738
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright 2012-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.ssl;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import org.springframework.boot.diagnostics.FailureAnalysis;
22+
23+
import static org.assertj.core.api.Assertions.assertThat;
24+
25+
/**
26+
* Tests for {@link BundleContentNotWatchableFailureAnalyzer}.
27+
*
28+
* @author Moritz Halbritter
29+
*/
30+
class BundleContentNotWatchableFailureAnalyzerTests {
31+
32+
@Test
33+
void shouldAnalyze() {
34+
FailureAnalysis failureAnalysis = performAnalysis(null);
35+
assertThat(failureAnalysis.getDescription()).isEqualTo(
36+
"The content of 'name' is not watchable. Only 'file:' resources are watchable, but 'classpath:resource.pem' has been set");
37+
assertThat(failureAnalysis.getAction())
38+
.isEqualTo("Update your application to correct the invalid configuration:\n"
39+
+ "Either use a watchable resource, or disable bundle reloading by setting reload-on-update = false on the bundle.");
40+
}
41+
42+
@Test
43+
void shouldAnalyzeWithBundle() {
44+
FailureAnalysis failureAnalysis = performAnalysis("bundle-1");
45+
assertThat(failureAnalysis.getDescription()).isEqualTo(
46+
"The content of 'name' from bundle 'bundle-1' is not watchable'. Only 'file:' resources are watchable, but 'classpath:resource.pem' has been set");
47+
}
48+
49+
private FailureAnalysis performAnalysis(String bundle) {
50+
BundleContentNotWatchableException failure = new BundleContentNotWatchableException(
51+
new BundleContentProperty("name", "classpath:resource.pem"));
52+
if (bundle != null) {
53+
failure = failure.withBundleName(bundle);
54+
}
55+
return new BundleContentNotWatchableFailureAnalyzer().analyze(failure);
56+
}
57+
58+
}

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/ssl/BundleContentPropertyTests.java

+8
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.junit.jupiter.api.Test;
2424

2525
import static org.assertj.core.api.Assertions.assertThat;
26+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
2627
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
2728

2829
/**
@@ -83,4 +84,11 @@ void toWatchPathWhenPathReturnsPath() throws URISyntaxException {
8384
assertThat(property.toWatchPath()).isEqualTo(file);
8485
}
8586

87+
@Test
88+
void shouldThrowBundleContentNotWatchableExceptionIfContentIsNotWatchable() {
89+
BundleContentProperty property = new BundleContentProperty("name", "https://example.com/");
90+
assertThatExceptionOfType(BundleContentNotWatchableException.class).isThrownBy(property::toWatchPath)
91+
.withMessageContaining("Only 'file:' resources are watchable");
92+
}
93+
8694
}

0 commit comments

Comments
 (0)