Skip to content

Commit 75bcc2e

Browse files
wonyonggsnicoll
authored andcommitted
Support additional nullness signal for Actuator endpoints
This commit expands the detection of optional parameters for Actuator Endpoints. Before this commit, JSpecify's `@Nullable` annotation was not detected. See gh-46854 Signed-off-by: wonyongg <[email protected]>
1 parent 088ef83 commit 75bcc2e

File tree

5 files changed

+124
-2
lines changed

5 files changed

+124
-2
lines changed

configuration-metadata/spring-boot-configuration-processor/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ architectureCheck {
3636

3737
dependencies {
3838
testCompileOnly("com.google.code.findbugs:jsr305:3.0.2")
39+
testCompileOnly("org.jspecify:jspecify")
3940

4041
testImplementation(enforcedPlatform(project(":platform:spring-boot-dependencies")))
4142
testImplementation(project(":test-support:spring-boot-test-support"))

configuration-metadata/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataGenerationEnvironment.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,13 @@
5252
* @author Stephane Nicoll
5353
* @author Scott Frederick
5454
* @author Moritz Halbritter
55+
* @author Wonyong Hwang
5556
*/
5657
class MetadataGenerationEnvironment {
5758

58-
private static final String NULLABLE_ANNOTATION = "org.springframework.lang.Nullable";
59+
private static final Set<String> NULLABLE_ANNOTATIONS = Set.of(
60+
"org.springframework.lang.Nullable",
61+
"org.jspecify.annotations.Nullable");
5962

6063
private static final Set<String> TYPE_EXCLUDES = Set.of("com.zaxxer.hikari.IConnectionCustomizer",
6164
"groovy.lang.MetaClass", "groovy.text.markup.MarkupTemplateEngine", "java.io.Writer", "java.io.PrintWriter",
@@ -265,6 +268,12 @@ AnnotationMirror getAnnotation(Element element, String type) {
265268
return annotation;
266269
}
267270
}
271+
272+
for (AnnotationMirror annotation : element.asType().getAnnotationMirrors()) {
273+
if (type.equals(annotation.getAnnotationType().toString())) {
274+
return annotation;
275+
}
276+
}
268277
}
269278
return null;
270279
}
@@ -368,7 +377,12 @@ AnnotationMirror getNameAnnotation(Element element) {
368377
}
369378

370379
boolean hasNullableAnnotation(Element element) {
371-
return getAnnotation(element, NULLABLE_ANNOTATION) != null;
380+
for (String nullableAnnotation : NULLABLE_ANNOTATIONS) {
381+
if (getAnnotation(element, nullableAnnotation) != null) {
382+
return true;
383+
}
384+
}
385+
return false;
372386
}
373387

374388
boolean hasOptionalParameterAnnotation(Element element) {

configuration-metadata/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/EndpointMetadataGenerationTests.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
import org.springframework.boot.configurationsample.endpoint.SpecificEndpoint;
3636
import org.springframework.boot.configurationsample.endpoint.UnrestrictedAccessEndpoint;
3737
import org.springframework.boot.configurationsample.endpoint.incremental.IncrementalEndpoint;
38+
import org.springframework.boot.configurationsample.endpoint.NullableParameterEndpoint;
39+
import org.springframework.boot.configurationsample.endpoint.OptionalParameterEndpoint;
3840

3941
import static org.assertj.core.api.Assertions.assertThat;
4042
import static org.assertj.core.api.Assertions.assertThatRuntimeException;
@@ -45,6 +47,7 @@
4547
* @author Stephane Nicoll
4648
* @author Scott Frederick
4749
* @author Moritz Halbritter
50+
* @author Wonyong Hwang
4851
*/
4952
class EndpointMetadataGenerationTests extends AbstractMetadataGenerationTests {
5053

@@ -192,6 +195,38 @@ void shouldFailIfEndpointWithSameIdButWithConflictingEnabledByDefaultSetting() {
192195
"Existing property 'management.endpoint.simple.access' from type org.springframework.boot.configurationsample.endpoint.SimpleEndpoint has a conflicting value. Existing value: unrestricted, new value from type org.springframework.boot.configurationsample.endpoint.SimpleEndpoint3: none");
193196
}
194197

198+
@Test
199+
void nullableParameterEndpoint() {
200+
ConfigurationMetadata metadata = compile(NullableParameterEndpoint.class);
201+
assertThat(metadata).has(Metadata.withGroup("management.endpoint.nullable").fromSource(NullableParameterEndpoint.class));
202+
assertThat(metadata).has(access("nullable", Access.UNRESTRICTED));
203+
assertThat(metadata).has(cacheTtl("nullable"));
204+
assertThat(metadata.getItems()).hasSize(3);
205+
}
206+
207+
@Test
208+
void optionalParameterEndpoint() {
209+
ConfigurationMetadata metadata = compile(OptionalParameterEndpoint.class);
210+
assertThat(metadata).has(Metadata.withGroup("management.endpoint.optional").fromSource(OptionalParameterEndpoint.class));
211+
assertThat(metadata).has(access("optional", Access.UNRESTRICTED));
212+
assertThat(metadata).has(cacheTtl("optional"));
213+
assertThat(metadata.getItems()).hasSize(3);
214+
}
215+
216+
@Test
217+
void nullableAndOptionalParameterEquivalence() {
218+
ConfigurationMetadata nullableMetadata = compile(NullableParameterEndpoint.class);
219+
ConfigurationMetadata optionalMetadata = compile(OptionalParameterEndpoint.class);
220+
221+
assertThat(nullableMetadata.getItems()).hasSize(3);
222+
assertThat(optionalMetadata.getItems()).hasSize(3);
223+
224+
assertThat(nullableMetadata).has(access("nullable", Access.UNRESTRICTED));
225+
assertThat(optionalMetadata).has(access("optional", Access.UNRESTRICTED));
226+
assertThat(nullableMetadata).has(cacheTtl("nullable"));
227+
assertThat(optionalMetadata).has(cacheTtl("optional"));
228+
}
229+
195230
private Metadata.MetadataItemCondition access(String endpointId, Access defaultValue) {
196231
return defaultAccess(endpointId, endpointId, defaultValue);
197232
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2012-present 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.configurationsample.endpoint;
18+
19+
import org.springframework.boot.configurationsample.Endpoint;
20+
import org.springframework.boot.configurationsample.ReadOperation;
21+
import org.jspecify.annotations.Nullable;
22+
23+
/**
24+
* An endpoint with @Nullable parameter to test.
25+
*
26+
* @author Wonyong Hwang
27+
*/
28+
@Endpoint(id = "nullable")
29+
public class NullableParameterEndpoint {
30+
31+
@ReadOperation
32+
public String invoke(@Nullable String parameter) {
33+
return "test with " + parameter;
34+
}
35+
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2012-present 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.configurationsample.endpoint;
18+
19+
import org.springframework.boot.configurationsample.Endpoint;
20+
import org.springframework.boot.configurationsample.ReadOperation;
21+
import org.springframework.boot.configurationsample.OptionalParameter;
22+
23+
/**
24+
* An endpoint with @OptionalParameter to compare with @Nullable behavior.
25+
*
26+
* @author Wonyong Hwang
27+
*/
28+
@Endpoint(id = "optional")
29+
public class OptionalParameterEndpoint {
30+
31+
@ReadOperation
32+
public String invoke(@OptionalParameter String parameter) {
33+
return "test with " + parameter;
34+
}
35+
36+
}

0 commit comments

Comments
 (0)