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 <111210881+wonyongg@users.noreply.github.com>
This commit is contained in:
parent
088ef836c1
commit
75bcc2e118
|
@ -36,6 +36,7 @@ architectureCheck {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testCompileOnly("com.google.code.findbugs:jsr305:3.0.2")
|
testCompileOnly("com.google.code.findbugs:jsr305:3.0.2")
|
||||||
|
testCompileOnly("org.jspecify:jspecify")
|
||||||
|
|
||||||
testImplementation(enforcedPlatform(project(":platform:spring-boot-dependencies")))
|
testImplementation(enforcedPlatform(project(":platform:spring-boot-dependencies")))
|
||||||
testImplementation(project(":test-support:spring-boot-test-support"))
|
testImplementation(project(":test-support:spring-boot-test-support"))
|
||||||
|
|
|
@ -52,10 +52,13 @@ import org.springframework.boot.configurationprocessor.metadata.ItemDeprecation;
|
||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
* @author Scott Frederick
|
* @author Scott Frederick
|
||||||
* @author Moritz Halbritter
|
* @author Moritz Halbritter
|
||||||
|
* @author Wonyong Hwang
|
||||||
*/
|
*/
|
||||||
class MetadataGenerationEnvironment {
|
class MetadataGenerationEnvironment {
|
||||||
|
|
||||||
private static final String NULLABLE_ANNOTATION = "org.springframework.lang.Nullable";
|
private static final Set<String> NULLABLE_ANNOTATIONS = Set.of(
|
||||||
|
"org.springframework.lang.Nullable",
|
||||||
|
"org.jspecify.annotations.Nullable");
|
||||||
|
|
||||||
private static final Set<String> TYPE_EXCLUDES = Set.of("com.zaxxer.hikari.IConnectionCustomizer",
|
private static final Set<String> TYPE_EXCLUDES = Set.of("com.zaxxer.hikari.IConnectionCustomizer",
|
||||||
"groovy.lang.MetaClass", "groovy.text.markup.MarkupTemplateEngine", "java.io.Writer", "java.io.PrintWriter",
|
"groovy.lang.MetaClass", "groovy.text.markup.MarkupTemplateEngine", "java.io.Writer", "java.io.PrintWriter",
|
||||||
|
@ -265,6 +268,12 @@ class MetadataGenerationEnvironment {
|
||||||
return annotation;
|
return annotation;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (AnnotationMirror annotation : element.asType().getAnnotationMirrors()) {
|
||||||
|
if (type.equals(annotation.getAnnotationType().toString())) {
|
||||||
|
return annotation;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -368,7 +377,12 @@ class MetadataGenerationEnvironment {
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasNullableAnnotation(Element element) {
|
boolean hasNullableAnnotation(Element element) {
|
||||||
return getAnnotation(element, NULLABLE_ANNOTATION) != null;
|
for (String nullableAnnotation : NULLABLE_ANNOTATIONS) {
|
||||||
|
if (getAnnotation(element, nullableAnnotation) != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasOptionalParameterAnnotation(Element element) {
|
boolean hasOptionalParameterAnnotation(Element element) {
|
||||||
|
|
|
@ -35,6 +35,8 @@ import org.springframework.boot.configurationsample.endpoint.SimpleEndpoint3;
|
||||||
import org.springframework.boot.configurationsample.endpoint.SpecificEndpoint;
|
import org.springframework.boot.configurationsample.endpoint.SpecificEndpoint;
|
||||||
import org.springframework.boot.configurationsample.endpoint.UnrestrictedAccessEndpoint;
|
import org.springframework.boot.configurationsample.endpoint.UnrestrictedAccessEndpoint;
|
||||||
import org.springframework.boot.configurationsample.endpoint.incremental.IncrementalEndpoint;
|
import org.springframework.boot.configurationsample.endpoint.incremental.IncrementalEndpoint;
|
||||||
|
import org.springframework.boot.configurationsample.endpoint.NullableParameterEndpoint;
|
||||||
|
import org.springframework.boot.configurationsample.endpoint.OptionalParameterEndpoint;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatRuntimeException;
|
import static org.assertj.core.api.Assertions.assertThatRuntimeException;
|
||||||
|
@ -45,6 +47,7 @@ import static org.assertj.core.api.Assertions.assertThatRuntimeException;
|
||||||
* @author Stephane Nicoll
|
* @author Stephane Nicoll
|
||||||
* @author Scott Frederick
|
* @author Scott Frederick
|
||||||
* @author Moritz Halbritter
|
* @author Moritz Halbritter
|
||||||
|
* @author Wonyong Hwang
|
||||||
*/
|
*/
|
||||||
class EndpointMetadataGenerationTests extends AbstractMetadataGenerationTests {
|
class EndpointMetadataGenerationTests extends AbstractMetadataGenerationTests {
|
||||||
|
|
||||||
|
@ -192,6 +195,38 @@ class EndpointMetadataGenerationTests extends AbstractMetadataGenerationTests {
|
||||||
"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");
|
"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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void nullableParameterEndpoint() {
|
||||||
|
ConfigurationMetadata metadata = compile(NullableParameterEndpoint.class);
|
||||||
|
assertThat(metadata).has(Metadata.withGroup("management.endpoint.nullable").fromSource(NullableParameterEndpoint.class));
|
||||||
|
assertThat(metadata).has(access("nullable", Access.UNRESTRICTED));
|
||||||
|
assertThat(metadata).has(cacheTtl("nullable"));
|
||||||
|
assertThat(metadata.getItems()).hasSize(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void optionalParameterEndpoint() {
|
||||||
|
ConfigurationMetadata metadata = compile(OptionalParameterEndpoint.class);
|
||||||
|
assertThat(metadata).has(Metadata.withGroup("management.endpoint.optional").fromSource(OptionalParameterEndpoint.class));
|
||||||
|
assertThat(metadata).has(access("optional", Access.UNRESTRICTED));
|
||||||
|
assertThat(metadata).has(cacheTtl("optional"));
|
||||||
|
assertThat(metadata.getItems()).hasSize(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void nullableAndOptionalParameterEquivalence() {
|
||||||
|
ConfigurationMetadata nullableMetadata = compile(NullableParameterEndpoint.class);
|
||||||
|
ConfigurationMetadata optionalMetadata = compile(OptionalParameterEndpoint.class);
|
||||||
|
|
||||||
|
assertThat(nullableMetadata.getItems()).hasSize(3);
|
||||||
|
assertThat(optionalMetadata.getItems()).hasSize(3);
|
||||||
|
|
||||||
|
assertThat(nullableMetadata).has(access("nullable", Access.UNRESTRICTED));
|
||||||
|
assertThat(optionalMetadata).has(access("optional", Access.UNRESTRICTED));
|
||||||
|
assertThat(nullableMetadata).has(cacheTtl("nullable"));
|
||||||
|
assertThat(optionalMetadata).has(cacheTtl("optional"));
|
||||||
|
}
|
||||||
|
|
||||||
private Metadata.MetadataItemCondition access(String endpointId, Access defaultValue) {
|
private Metadata.MetadataItemCondition access(String endpointId, Access defaultValue) {
|
||||||
return defaultAccess(endpointId, endpointId, defaultValue);
|
return defaultAccess(endpointId, endpointId, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-present 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.configurationsample.endpoint;
|
||||||
|
|
||||||
|
import org.springframework.boot.configurationsample.Endpoint;
|
||||||
|
import org.springframework.boot.configurationsample.ReadOperation;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An endpoint with @Nullable parameter to test.
|
||||||
|
*
|
||||||
|
* @author Wonyong Hwang
|
||||||
|
*/
|
||||||
|
@Endpoint(id = "nullable")
|
||||||
|
public class NullableParameterEndpoint {
|
||||||
|
|
||||||
|
@ReadOperation
|
||||||
|
public String invoke(@Nullable String parameter) {
|
||||||
|
return "test with " + parameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-present 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.configurationsample.endpoint;
|
||||||
|
|
||||||
|
import org.springframework.boot.configurationsample.Endpoint;
|
||||||
|
import org.springframework.boot.configurationsample.ReadOperation;
|
||||||
|
import org.springframework.boot.configurationsample.OptionalParameter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An endpoint with @OptionalParameter to compare with @Nullable behavior.
|
||||||
|
*
|
||||||
|
* @author Wonyong Hwang
|
||||||
|
*/
|
||||||
|
@Endpoint(id = "optional")
|
||||||
|
public class OptionalParameterEndpoint {
|
||||||
|
|
||||||
|
@ReadOperation
|
||||||
|
public String invoke(@OptionalParameter String parameter) {
|
||||||
|
return "test with " + parameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue