Browse Source

Replace @OptionalParameter with JSpecify's @Nullable

This commit removes OptionalParameter in favor of the nullness support
introduced in Spring Framework 7. The parameter of an action can now
be flagged as optional using JSpecify's @Nullable, and simplifies the
setup for those who are using JSpecify as only a single annotation is
required.

Closes gh-45390
pull/47162/head
Stéphane Nicoll 4 months ago
parent
commit
d7c482aa16
  1. 16
      configuration-metadata/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java
  2. 9
      configuration-metadata/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/MetadataGenerationEnvironment.java
  3. 13
      configuration-metadata/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/EndpointMetadataGenerationTests.java
  4. 1
      configuration-metadata/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/MetadataGenerationEnvironmentFactory.java
  5. 7
      configuration-metadata/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/test/TestConfigurationMetadataAnnotationProcessor.java
  6. 36
      configuration-metadata/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/OptionalParameter.java
  7. 36
      configuration-metadata/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/endpoint/OptionalParameterEndpoint.java
  8. 5
      configuration-metadata/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/endpoint/SpecificEndpoint.java
  9. 5
      configuration-metadata/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/endpoint/incremental/IncrementalEndpoint.java
  10. 4
      documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/endpoints.adoc
  11. 8
      integration-test/spring-boot-actuator-integration-tests/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/AbstractWebEndpointIntegrationTests.java
  12. 5
      module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/audit/AuditEventsEndpoint.java
  13. 8
      module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/DiscoveredOperationMethod.java
  14. 36
      module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/OptionalParameter.java
  15. 19
      module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/reflect/OperationMethod.java
  16. 13
      module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/reflect/OperationMethodParameter.java
  17. 13
      module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/reflect/OperationMethodParameters.java
  18. 3
      module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/env/EnvironmentEndpoint.java
  19. 4
      module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/env/EnvironmentEndpointWebExtension.java
  20. 3
      module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/logging/LoggersEndpoint.java
  21. 3
      module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/management/HeapDumpWebEndpoint.java
  22. 4
      module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusScrapeEndpoint.java
  23. 56
      module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoke/reflect/OperationMethodParameterTests.java
  24. 22
      module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoke/reflect/OperationMethodParametersTests.java
  25. 16
      module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoke/reflect/OperationMethodTests.java
  26. 26
      module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoke/reflect/ReflectiveOperationInvokerTests.java
  27. 28
      module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvokerAdvisorTests.java
  28. 6
      module/spring-boot-cache/src/main/java/org/springframework/boot/cache/actuate/endpoint/CachesEndpoint.java
  29. 7
      module/spring-boot-cache/src/main/java/org/springframework/boot/cache/actuate/endpoint/CachesEndpointWebExtension.java
  30. 4
      module/spring-boot-micrometer-metrics/src/main/java/org/springframework/boot/micrometer/metrics/actuate/endpoint/MetricsEndpoint.java

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

@ -109,8 +109,6 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
static final String READ_OPERATION_ANNOTATION = "org.springframework.boot.actuate.endpoint.annotation.ReadOperation"; static final String READ_OPERATION_ANNOTATION = "org.springframework.boot.actuate.endpoint.annotation.ReadOperation";
static final String OPTIONAL_PARAMETER_ANNOTATION = "org.springframework.boot.actuate.endpoint.annotation.OptionalParameter";
static final String NAME_ANNOTATION = "org.springframework.boot.context.properties.bind.Name"; static final String NAME_ANNOTATION = "org.springframework.boot.context.properties.bind.Name";
static final String ENDPOINT_ACCESS_ENUM = "org.springframework.boot.actuate.endpoint.Access"; static final String ENDPOINT_ACCESS_ENUM = "org.springframework.boot.actuate.endpoint.Access";
@ -166,10 +164,6 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
return NAME_ANNOTATION; return NAME_ANNOTATION;
} }
protected String optionalParameterAnnotation() {
return OPTIONAL_PARAMETER_ANNOTATION;
}
protected String endpointAccessEnum() { protected String endpointAccessEnum() {
return ENDPOINT_ACCESS_ENUM; return ENDPOINT_ACCESS_ENUM;
} }
@ -194,8 +188,7 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
this.metadataEnv = new MetadataGenerationEnvironment(env, configurationPropertiesAnnotation(), this.metadataEnv = new MetadataGenerationEnvironment(env, configurationPropertiesAnnotation(),
configurationPropertiesSourceAnnotation(), nestedConfigurationPropertyAnnotation(), configurationPropertiesSourceAnnotation(), nestedConfigurationPropertyAnnotation(),
deprecatedConfigurationPropertyAnnotation(), constructorBindingAnnotation(), autowiredAnnotation(), deprecatedConfigurationPropertyAnnotation(), constructorBindingAnnotation(), autowiredAnnotation(),
defaultValueAnnotation(), endpointAnnotations(), readOperationAnnotation(), defaultValueAnnotation(), endpointAnnotations(), readOperationAnnotation(), nameAnnotation());
optionalParameterAnnotation(), nameAnnotation());
} }
@Override @Override
@ -383,18 +376,13 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
private boolean hasNoOrOptionalParameters(ExecutableElement method) { private boolean hasNoOrOptionalParameters(ExecutableElement method) {
for (VariableElement parameter : method.getParameters()) { for (VariableElement parameter : method.getParameters()) {
if (!isOptionalParameter(parameter)) { if (!this.metadataEnv.hasNullableAnnotation(parameter)) {
return false; return false;
} }
} }
return true; return true;
} }
private boolean isOptionalParameter(VariableElement parameter) {
return this.metadataEnv.hasNullableAnnotation(parameter)
|| this.metadataEnv.hasOptionalParameterAnnotation(parameter);
}
private String getPrefix(AnnotationMirror annotation) { private String getPrefix(AnnotationMirror annotation) {
String prefix = this.metadataEnv.getAnnotationElementStringValue(annotation, "prefix"); String prefix = this.metadataEnv.getAnnotationElementStringValue(annotation, "prefix");
if (prefix != null) { if (prefix != null) {

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

@ -98,8 +98,6 @@ class MetadataGenerationEnvironment {
private final String readOperationAnnotation; private final String readOperationAnnotation;
private final String optionalParameterAnnotation;
private final String nameAnnotation; private final String nameAnnotation;
private final String autowiredAnnotation; private final String autowiredAnnotation;
@ -108,7 +106,7 @@ class MetadataGenerationEnvironment {
String configurationPropertiesSourceAnnotation, String nestedConfigurationPropertyAnnotation, String configurationPropertiesSourceAnnotation, String nestedConfigurationPropertyAnnotation,
String deprecatedConfigurationPropertyAnnotation, String constructorBindingAnnotation, String deprecatedConfigurationPropertyAnnotation, String constructorBindingAnnotation,
String autowiredAnnotation, String defaultValueAnnotation, Set<String> endpointAnnotations, String autowiredAnnotation, String defaultValueAnnotation, Set<String> endpointAnnotations,
String readOperationAnnotation, String optionalParameterAnnotation, String nameAnnotation) { String readOperationAnnotation, String nameAnnotation) {
this.typeUtils = new TypeUtils(environment); this.typeUtils = new TypeUtils(environment);
this.elements = environment.getElementUtils(); this.elements = environment.getElementUtils();
this.messager = environment.getMessager(); this.messager = environment.getMessager();
@ -123,7 +121,6 @@ class MetadataGenerationEnvironment {
this.defaultValueAnnotation = defaultValueAnnotation; this.defaultValueAnnotation = defaultValueAnnotation;
this.endpointAnnotations = endpointAnnotations; this.endpointAnnotations = endpointAnnotations;
this.readOperationAnnotation = readOperationAnnotation; this.readOperationAnnotation = readOperationAnnotation;
this.optionalParameterAnnotation = optionalParameterAnnotation;
this.nameAnnotation = nameAnnotation; this.nameAnnotation = nameAnnotation;
} }
@ -382,10 +379,6 @@ class MetadataGenerationEnvironment {
return getTypeUseAnnotation(element, NULLABLE_ANNOTATION) != null; return getTypeUseAnnotation(element, NULLABLE_ANNOTATION) != null;
} }
boolean hasOptionalParameterAnnotation(Element element) {
return getAnnotation(element, this.optionalParameterAnnotation) != null;
}
private boolean isElementDeprecated(Element element) { private boolean isElementDeprecated(Element element) {
return hasAnnotation(element, "java.lang.Deprecated") return hasAnnotation(element, "java.lang.Deprecated")
|| hasAnnotation(element, this.deprecatedConfigurationPropertyAnnotation); || hasAnnotation(element, this.deprecatedConfigurationPropertyAnnotation);

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

@ -29,7 +29,6 @@ import org.springframework.boot.configurationsample.endpoint.CustomPropertiesEnd
import org.springframework.boot.configurationsample.endpoint.EnabledEndpoint; import org.springframework.boot.configurationsample.endpoint.EnabledEndpoint;
import org.springframework.boot.configurationsample.endpoint.NoAccessEndpoint; import org.springframework.boot.configurationsample.endpoint.NoAccessEndpoint;
import org.springframework.boot.configurationsample.endpoint.NullableParameterEndpoint; import org.springframework.boot.configurationsample.endpoint.NullableParameterEndpoint;
import org.springframework.boot.configurationsample.endpoint.OptionalParameterEndpoint;
import org.springframework.boot.configurationsample.endpoint.ReadOnlyAccessEndpoint; import org.springframework.boot.configurationsample.endpoint.ReadOnlyAccessEndpoint;
import org.springframework.boot.configurationsample.endpoint.SimpleEndpoint; import org.springframework.boot.configurationsample.endpoint.SimpleEndpoint;
import org.springframework.boot.configurationsample.endpoint.SimpleEndpoint2; import org.springframework.boot.configurationsample.endpoint.SimpleEndpoint2;
@ -153,7 +152,7 @@ class EndpointMetadataGenerationTests extends AbstractMetadataGenerationTests {
assertThat(metadata).has(access("incremental", Access.UNRESTRICTED)); assertThat(metadata).has(access("incremental", Access.UNRESTRICTED));
assertThat(metadata).has(cacheTtl("incremental")); assertThat(metadata).has(cacheTtl("incremental"));
assertThat(metadata.getItems()).hasSize(3); assertThat(metadata.getItems()).hasSize(3);
project.replaceText(IncrementalEndpoint.class, "@OptionalParameter String param", "String param"); project.replaceText(IncrementalEndpoint.class, "@Nullable String param", "String param");
metadata = project.compile(); metadata = project.compile();
assertThat(metadata) assertThat(metadata)
.has(Metadata.withGroup("management.endpoint.incremental").fromSource(IncrementalEndpoint.class)); .has(Metadata.withGroup("management.endpoint.incremental").fromSource(IncrementalEndpoint.class));
@ -205,16 +204,6 @@ class EndpointMetadataGenerationTests extends AbstractMetadataGenerationTests {
assertThat(metadata.getItems()).hasSize(3); assertThat(metadata.getItems()).hasSize(3);
} }
@Test
void endpointWithOptionalParameter() {
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);
}
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);
} }

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

@ -49,7 +49,6 @@ class MetadataGenerationEnvironmentFactory implements Function<ProcessingEnviron
TestConfigurationMetadataAnnotationProcessor.AUTOWIRED_ANNOTATION, TestConfigurationMetadataAnnotationProcessor.AUTOWIRED_ANNOTATION,
TestConfigurationMetadataAnnotationProcessor.DEFAULT_VALUE_ANNOTATION, endpointAnnotations, TestConfigurationMetadataAnnotationProcessor.DEFAULT_VALUE_ANNOTATION, endpointAnnotations,
TestConfigurationMetadataAnnotationProcessor.READ_OPERATION_ANNOTATION, TestConfigurationMetadataAnnotationProcessor.READ_OPERATION_ANNOTATION,
TestConfigurationMetadataAnnotationProcessor.OPTIONAL_PARAMETER_ANNOTATION,
TestConfigurationMetadataAnnotationProcessor.NAME_ANNOTATION); TestConfigurationMetadataAnnotationProcessor.NAME_ANNOTATION);
} }

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

@ -75,8 +75,6 @@ public class TestConfigurationMetadataAnnotationProcessor extends ConfigurationM
public static final String READ_OPERATION_ANNOTATION = "org.springframework.boot.configurationsample.ReadOperation"; public static final String READ_OPERATION_ANNOTATION = "org.springframework.boot.configurationsample.ReadOperation";
public static final String OPTIONAL_PARAMETER_ANNOTATION = "org.springframework.boot.configurationsample.OptionalParameter";
public static final String NAME_ANNOTATION = "org.springframework.boot.configurationsample.Name"; public static final String NAME_ANNOTATION = "org.springframework.boot.configurationsample.Name";
public static final String ENDPOINT_ACCESS_ENUM = "org.springframework.boot.configurationsample.Access"; public static final String ENDPOINT_ACCESS_ENUM = "org.springframework.boot.configurationsample.Access";
@ -130,11 +128,6 @@ public class TestConfigurationMetadataAnnotationProcessor extends ConfigurationM
return READ_OPERATION_ANNOTATION; return READ_OPERATION_ANNOTATION;
} }
@Override
protected String optionalParameterAnnotation() {
return OPTIONAL_PARAMETER_ANNOTATION;
}
@Override @Override
protected String nameAnnotation() { protected String nameAnnotation() {
return NAME_ANNOTATION; return NAME_ANNOTATION;

36
configuration-metadata/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/OptionalParameter.java

@ -1,36 +0,0 @@
/*
* 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;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Alternative to Spring Boot's {@code @OptionalParameter} for testing (removes the need
* for a dependency on the real annotation).
*
* @author Phillip Webb
*/
@Target({ ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OptionalParameter {
}

36
configuration-metadata/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/endpoint/OptionalParameterEndpoint.java

@ -1,36 +0,0 @@
/*
* 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.OptionalParameter;
import org.springframework.boot.configurationsample.ReadOperation;
/**
* An endpoint that uses {@code OptionalParameter} to signal an optional parameter.
*
* @author Wonyong Hwang
*/
@Endpoint(id = "optional")
public class OptionalParameterEndpoint {
@ReadOperation
public String invoke(@OptionalParameter String parameter) {
return "test with " + parameter;
}
}

5
configuration-metadata/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/endpoint/SpecificEndpoint.java

@ -16,8 +16,9 @@
package org.springframework.boot.configurationsample.endpoint; package org.springframework.boot.configurationsample.endpoint;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.configurationsample.Access; import org.springframework.boot.configurationsample.Access;
import org.springframework.boot.configurationsample.OptionalParameter;
import org.springframework.boot.configurationsample.ReadOperation; import org.springframework.boot.configurationsample.ReadOperation;
import org.springframework.boot.configurationsample.WebEndpoint; import org.springframework.boot.configurationsample.WebEndpoint;
@ -31,7 +32,7 @@ import org.springframework.boot.configurationsample.WebEndpoint;
public class SpecificEndpoint { public class SpecificEndpoint {
@ReadOperation @ReadOperation
String invoke(@OptionalParameter String param) { String invoke(@Nullable String param) {
return "test"; return "test";
} }

5
configuration-metadata/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/endpoint/incremental/IncrementalEndpoint.java

@ -16,8 +16,9 @@
package org.springframework.boot.configurationsample.endpoint.incremental; package org.springframework.boot.configurationsample.endpoint.incremental;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.configurationsample.Endpoint; import org.springframework.boot.configurationsample.Endpoint;
import org.springframework.boot.configurationsample.OptionalParameter;
import org.springframework.boot.configurationsample.ReadOperation; import org.springframework.boot.configurationsample.ReadOperation;
/** /**
@ -29,7 +30,7 @@ import org.springframework.boot.configurationsample.ReadOperation;
public class IncrementalEndpoint { public class IncrementalEndpoint {
@ReadOperation @ReadOperation
public String invoke(@OptionalParameter String param) { public String invoke(@Nullable String param) {
return "test"; return "test";
} }

4
documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/actuator/endpoints.adoc

@ -412,7 +412,7 @@ Operations on an endpoint receive input through their parameters.
When exposed over the web, the values for these parameters are taken from the URL's query parameters and from the JSON request body. When exposed over the web, the values for these parameters are taken from the URL's query parameters and from the JSON request body.
When exposed over JMX, the parameters are mapped to the parameters of the MBean's operations. When exposed over JMX, the parameters are mapped to the parameters of the MBean's operations.
Parameters are required by default. Parameters are required by default.
They can be made optional by annotating them with either javadoc:org.springframework.boot.actuate.endpoint.annotation.OptionalParameter[format=annotation] or JSpecify's javadoc:org.jspecify.annotations.Nullable[format=annotation]. They can be made optional by annotating them with JSpecify's javadoc:org.jspecify.annotations.Nullable[format=annotation].
Kotlin null safety is also supported. Kotlin null safety is also supported.
You can map each root property in the JSON request body to a parameter of the endpoint. You can map each root property in the JSON request body to a parameter of the endpoint.
@ -544,7 +544,7 @@ When using Spring MVC or Spring Web Flux, operations that return a javadoc:org.s
==== Web Endpoint Security ==== Web Endpoint Security
An operation on a web endpoint or a web-specific endpoint extension can receive the current javadoc:java.security.Principal[] or javadoc:org.springframework.boot.actuate.endpoint.SecurityContext[] as a method parameter. An operation on a web endpoint or a web-specific endpoint extension can receive the current javadoc:java.security.Principal[] or javadoc:org.springframework.boot.actuate.endpoint.SecurityContext[] as a method parameter.
The former is typically used in conjunction with either javadoc:org.springframework.boot.actuate.endpoint.annotation.OptionalParameter[format=annotation] or javadoc:org.jspecify.annotations.Nullable[format=annotation] to provide different behavior for authenticated and unauthenticated users. The former is typically used in conjunction with javadoc:org.jspecify.annotations.Nullable[format=annotation] to provide different behavior for authenticated and unauthenticated users.
The latter is typically used to perform authorization checks by using its `isUserInRole(String)` method. The latter is typically used to perform authorization checks by using its `isUserInRole(String)` method.

8
integration-test/spring-boot-actuator-integration-tests/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/AbstractWebEndpointIntegrationTests.java

@ -27,6 +27,7 @@ import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
@ -34,7 +35,6 @@ import reactor.core.publisher.Mono;
import org.springframework.boot.actuate.endpoint.SecurityContext; import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation; import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.OptionalParameter;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector; import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.boot.actuate.endpoint.annotation.Selector.Match; import org.springframework.boot.actuate.endpoint.annotation.Selector.Match;
@ -982,7 +982,7 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
} }
@WriteOperation @WriteOperation
void write(@OptionalParameter String foo, @OptionalParameter String bar) { void write(@Nullable String foo, @Nullable String bar) {
this.endpointDelegate.write(foo, bar); this.endpointDelegate.write(foo, bar);
} }
@ -1167,7 +1167,7 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
static class RequiredParametersEndpoint { static class RequiredParametersEndpoint {
@ReadOperation @ReadOperation
String read(String foo, @OptionalParameter String bar) { String read(String foo, @Nullable String bar) {
return foo; return foo;
} }
@ -1177,7 +1177,7 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
static class PrincipalEndpoint { static class PrincipalEndpoint {
@ReadOperation @ReadOperation
String read(@OptionalParameter Principal principal) { String read(@Nullable Principal principal) {
return (principal != null) ? principal.getName() : "None"; return (principal != null) ? principal.getName() : "None";
} }

5
module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/audit/AuditEventsEndpoint.java

@ -24,7 +24,6 @@ import org.jspecify.annotations.Nullable;
import org.springframework.boot.actuate.endpoint.OperationResponseBody; import org.springframework.boot.actuate.endpoint.OperationResponseBody;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.OptionalParameter;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -45,8 +44,8 @@ public class AuditEventsEndpoint {
} }
@ReadOperation @ReadOperation
public AuditEventsDescriptor events(@OptionalParameter @Nullable String principal, public AuditEventsDescriptor events(@Nullable String principal, @Nullable OffsetDateTime after,
@OptionalParameter @Nullable OffsetDateTime after, @OptionalParameter @Nullable String type) { @Nullable String type) {
List<AuditEvent> events = this.auditEventRepository.find(principal, getInstant(after), type); List<AuditEvent> events = this.auditEventRepository.find(principal, getInstant(after), type);
return new AuditEventsDescriptor(events); return new AuditEventsDescriptor(events);
} }

8
module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/DiscoveredOperationMethod.java

@ -17,7 +17,6 @@
package org.springframework.boot.actuate.endpoint.annotation; package org.springframework.boot.actuate.endpoint.annotation;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -27,7 +26,6 @@ import org.springframework.boot.actuate.endpoint.OperationType;
import org.springframework.boot.actuate.endpoint.Producible; import org.springframework.boot.actuate.endpoint.Producible;
import org.springframework.boot.actuate.endpoint.invoke.reflect.OperationMethod; import org.springframework.boot.actuate.endpoint.invoke.reflect.OperationMethod;
import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
@ -42,7 +40,7 @@ public class DiscoveredOperationMethod extends OperationMethod {
public DiscoveredOperationMethod(Method method, OperationType operationType, public DiscoveredOperationMethod(Method method, OperationType operationType,
AnnotationAttributes annotationAttributes) { AnnotationAttributes annotationAttributes) {
super(method, operationType, DiscoveredOperationMethod::isOptionalParameter); super(method, operationType);
Assert.notNull(annotationAttributes, "'annotationAttributes' must not be null"); Assert.notNull(annotationAttributes, "'annotationAttributes' must not be null");
List<String> producesMediaTypes = new ArrayList<>(); List<String> producesMediaTypes = new ArrayList<>();
producesMediaTypes.addAll(Arrays.asList(annotationAttributes.getStringArray("produces"))); producesMediaTypes.addAll(Arrays.asList(annotationAttributes.getStringArray("produces")));
@ -50,10 +48,6 @@ public class DiscoveredOperationMethod extends OperationMethod {
this.producesMediaTypes = Collections.unmodifiableList(producesMediaTypes); this.producesMediaTypes = Collections.unmodifiableList(producesMediaTypes);
} }
private static boolean isOptionalParameter(Parameter parameter) {
return MergedAnnotations.from(parameter).isPresent(OptionalParameter.class);
}
private <E extends Enum<E> & Producible<E>> List<String> getProducesFromProducible( private <E extends Enum<E> & Producible<E>> List<String> getProducesFromProducible(
AnnotationAttributes annotationAttributes) { AnnotationAttributes annotationAttributes) {
Class<?> type = getProducesFrom(annotationAttributes); Class<?> type = getProducesFrom(annotationAttributes);

36
module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/annotation/OptionalParameter.java

@ -1,36 +0,0 @@
/*
* 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.actuate.endpoint.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation that indicates that an operation parameter is optional.
*
* @author Phillip Webb
* @since 4.0.0
*/
@Target({ ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OptionalParameter {
}

19
module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/reflect/OperationMethod.java

@ -17,9 +17,7 @@
package org.springframework.boot.actuate.endpoint.invoke.reflect; package org.springframework.boot.actuate.endpoint.invoke.reflect;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Locale; import java.util.Locale;
import java.util.function.Predicate;
import org.springframework.boot.actuate.endpoint.OperationType; import org.springframework.boot.actuate.endpoint.OperationType;
import org.springframework.boot.actuate.endpoint.invoke.OperationParameters; import org.springframework.boot.actuate.endpoint.invoke.OperationParameters;
@ -48,28 +46,13 @@ public class OperationMethod {
* Create a new {@link OperationMethod} instance. * Create a new {@link OperationMethod} instance.
* @param method the source method * @param method the source method
* @param operationType the operation type * @param operationType the operation type
* @deprecated since 4.0.0 for removal in 4.2.0 in favor of
* {@link #OperationMethod(Method, OperationType, Predicate)}
*/ */
@Deprecated(since = "4.0.0", forRemoval = true)
public OperationMethod(Method method, OperationType operationType) { public OperationMethod(Method method, OperationType operationType) {
this(method, operationType, (parameter) -> false);
}
/**
* Create a new {@link OperationMethod} instance.
* @param method the source method
* @param operationType the operation type
* @param optionalParameters predicate to test if a parameter is optional
* @since 4.0.0
*/
public OperationMethod(Method method, OperationType operationType, Predicate<Parameter> optionalParameters) {
Assert.notNull(method, "'method' must not be null"); Assert.notNull(method, "'method' must not be null");
Assert.notNull(operationType, "'operationType' must not be null"); Assert.notNull(operationType, "'operationType' must not be null");
this.method = method; this.method = method;
this.operationType = operationType; this.operationType = operationType;
this.operationParameters = new OperationMethodParameters(method, DEFAULT_PARAMETER_NAME_DISCOVERER, this.operationParameters = new OperationMethodParameters(method, DEFAULT_PARAMETER_NAME_DISCOVERER);
optionalParameters);
} }
/** /**

13
module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/reflect/OperationMethodParameter.java

@ -18,7 +18,6 @@ package org.springframework.boot.actuate.endpoint.invoke.reflect;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.Parameter; import java.lang.reflect.Parameter;
import java.util.function.Predicate;
import org.springframework.boot.actuate.endpoint.invoke.OperationParameter; import org.springframework.boot.actuate.endpoint.invoke.OperationParameter;
import org.springframework.core.Nullness; import org.springframework.core.Nullness;
@ -35,18 +34,14 @@ class OperationMethodParameter implements OperationParameter {
private final Parameter parameter; private final Parameter parameter;
private final Predicate<Parameter> optional;
/** /**
* Create a new {@link OperationMethodParameter} instance. * Create a new {@link OperationMethodParameter} instance.
* @param name the parameter name * @param name the parameter name
* @param parameter the parameter * @param parameter the parameter
* @param optionalParameters predicate to test if a parameter is optional
*/ */
OperationMethodParameter(String name, Parameter parameter, Predicate<Parameter> optionalParameters) { OperationMethodParameter(String name, Parameter parameter) {
this.name = name; this.name = name;
this.parameter = parameter; this.parameter = parameter;
this.optional = optionalParameters;
} }
@Override @Override
@ -61,11 +56,7 @@ class OperationMethodParameter implements OperationParameter {
@Override @Override
public boolean isMandatory() { public boolean isMandatory() {
return !isOptional(); return Nullness.NULLABLE != Nullness.forParameter(this.parameter);
}
private boolean isOptional() {
return Nullness.NULLABLE == Nullness.forParameter(this.parameter) || this.optional.test(this.parameter);
} }
@Override @Override

13
module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/invoke/reflect/OperationMethodParameters.java

@ -22,7 +22,6 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
@ -45,26 +44,22 @@ class OperationMethodParameters implements OperationParameters {
* Create a new {@link OperationMethodParameters} instance. * Create a new {@link OperationMethodParameters} instance.
* @param method the source method * @param method the source method
* @param parameterNameDiscoverer the parameter name discoverer * @param parameterNameDiscoverer the parameter name discoverer
* @param optionalParameters predicate to test if a parameter is optional
*/ */
OperationMethodParameters(Method method, ParameterNameDiscoverer parameterNameDiscoverer, OperationMethodParameters(Method method, ParameterNameDiscoverer parameterNameDiscoverer) {
Predicate<Parameter> optionalParameters) {
Assert.notNull(method, "'method' must not be null"); Assert.notNull(method, "'method' must not be null");
Assert.notNull(parameterNameDiscoverer, "'parameterNameDiscoverer' must not be null"); Assert.notNull(parameterNameDiscoverer, "'parameterNameDiscoverer' must not be null");
Assert.notNull(optionalParameters, "'optionalParameters' must not be null");
@Nullable String[] parameterNames = parameterNameDiscoverer.getParameterNames(method); @Nullable String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
Parameter[] parameters = method.getParameters(); Parameter[] parameters = method.getParameters();
Assert.state(parameterNames != null, () -> "Failed to extract parameter names for " + method); Assert.state(parameterNames != null, () -> "Failed to extract parameter names for " + method);
this.operationParameters = getOperationParameters(parameters, parameterNames, optionalParameters); this.operationParameters = getOperationParameters(parameters, parameterNames);
} }
private List<OperationParameter> getOperationParameters(Parameter[] parameters, @Nullable String[] names, private List<OperationParameter> getOperationParameters(Parameter[] parameters, @Nullable String[] names) {
Predicate<Parameter> optionalParameters) {
List<OperationParameter> operationParameters = new ArrayList<>(parameters.length); List<OperationParameter> operationParameters = new ArrayList<>(parameters.length);
for (int i = 0; i < names.length; i++) { for (int i = 0; i < names.length; i++) {
String name = names[i]; String name = names[i];
Assert.state(name != null, "'name' must not be null"); Assert.state(name != null, "'name' must not be null");
operationParameters.add(new OperationMethodParameter(name, parameters[i], optionalParameters)); operationParameters.add(new OperationMethodParameter(name, parameters[i]));
} }
return Collections.unmodifiableList(operationParameters); return Collections.unmodifiableList(operationParameters);
} }

3
module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/env/EnvironmentEndpoint.java vendored

@ -34,7 +34,6 @@ import org.springframework.boot.actuate.endpoint.Sanitizer;
import org.springframework.boot.actuate.endpoint.SanitizingFunction; import org.springframework.boot.actuate.endpoint.SanitizingFunction;
import org.springframework.boot.actuate.endpoint.Show; import org.springframework.boot.actuate.endpoint.Show;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.OptionalParameter;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector; import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.boot.context.properties.bind.PlaceholdersResolver; import org.springframework.boot.context.properties.bind.PlaceholdersResolver;
@ -81,7 +80,7 @@ public class EnvironmentEndpoint {
} }
@ReadOperation @ReadOperation
public EnvironmentDescriptor environment(@OptionalParameter @Nullable String pattern) { public EnvironmentDescriptor environment(@Nullable String pattern) {
boolean showUnsanitized = this.showValues.isShown(true); boolean showUnsanitized = this.showValues.isShown(true);
return getEnvironmentDescriptor(pattern, showUnsanitized); return getEnvironmentDescriptor(pattern, showUnsanitized);
} }

4
module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/env/EnvironmentEndpointWebExtension.java vendored

@ -22,7 +22,6 @@ import org.jspecify.annotations.Nullable;
import org.springframework.boot.actuate.endpoint.SecurityContext; import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.endpoint.Show; import org.springframework.boot.actuate.endpoint.Show;
import org.springframework.boot.actuate.endpoint.annotation.OptionalParameter;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector; import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
@ -53,8 +52,7 @@ public class EnvironmentEndpointWebExtension {
} }
@ReadOperation @ReadOperation
public EnvironmentDescriptor environment(SecurityContext securityContext, public EnvironmentDescriptor environment(SecurityContext securityContext, @Nullable String pattern) {
@OptionalParameter @Nullable String pattern) {
boolean showUnsanitized = this.showValues.isShown(securityContext, this.roles); boolean showUnsanitized = this.showValues.isShown(securityContext, this.roles);
return this.delegate.getEnvironmentDescriptor(pattern, showUnsanitized); return this.delegate.getEnvironmentDescriptor(pattern, showUnsanitized);
} }

3
module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/logging/LoggersEndpoint.java

@ -29,7 +29,6 @@ import org.jspecify.annotations.Nullable;
import org.springframework.aot.hint.annotation.RegisterReflectionForBinding; import org.springframework.aot.hint.annotation.RegisterReflectionForBinding;
import org.springframework.boot.actuate.endpoint.OperationResponseBody; import org.springframework.boot.actuate.endpoint.OperationResponseBody;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.OptionalParameter;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector; import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation; import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
@ -100,7 +99,7 @@ public class LoggersEndpoint {
} }
@WriteOperation @WriteOperation
public void configureLogLevel(@Selector String name, @OptionalParameter @Nullable LogLevel configuredLevel) { public void configureLogLevel(@Selector String name, @Nullable LogLevel configuredLevel) {
Assert.notNull(name, "'name' must not be empty"); Assert.notNull(name, "'name' must not be empty");
LoggerGroup group = this.loggerGroups.get(name); LoggerGroup group = this.loggerGroups.get(name);
if (group != null && group.hasMembers()) { if (group != null && group.hasMembers()) {

3
module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/management/HeapDumpWebEndpoint.java

@ -40,7 +40,6 @@ import org.jspecify.annotations.Nullable;
import org.springframework.boot.actuate.endpoint.Access; import org.springframework.boot.actuate.endpoint.Access;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.OptionalParameter;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpoint; import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpoint;
@ -78,7 +77,7 @@ public class HeapDumpWebEndpoint {
} }
@ReadOperation @ReadOperation
public WebEndpointResponse<Resource> heapDump(@OptionalParameter @Nullable Boolean live) { public WebEndpointResponse<Resource> heapDump(@Nullable Boolean live) {
try { try {
if (this.lock.tryLock(this.timeout, TimeUnit.MILLISECONDS)) { if (this.lock.tryLock(this.timeout, TimeUnit.MILLISECONDS)) {
try { try {

4
module/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusScrapeEndpoint.java

@ -29,7 +29,6 @@ import io.prometheus.metrics.model.snapshots.MetricSnapshots;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.OptionalParameter;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpoint; import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpoint;
@ -69,8 +68,7 @@ public class PrometheusScrapeEndpoint {
} }
@ReadOperation(producesFrom = PrometheusOutputFormat.class) @ReadOperation(producesFrom = PrometheusOutputFormat.class)
public WebEndpointResponse<byte[]> scrape(PrometheusOutputFormat format, public WebEndpointResponse<byte[]> scrape(PrometheusOutputFormat format, @Nullable Set<String> includedNames) {
@OptionalParameter @Nullable Set<String> includedNames) {
try { try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(this.nextMetricsScrapeSize); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(this.nextMetricsScrapeSize);
MetricSnapshots metricSnapshots = (includedNames != null) MetricSnapshots metricSnapshots = (includedNames != null)

56
module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoke/reflect/OperationMethodParameterTests.java

@ -16,19 +16,12 @@
package org.springframework.boot.actuate.endpoint.invoke.reflect; package org.springframework.boot.actuate.endpoint.invoke.reflect;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.endpoint.annotation.Selector; import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.boot.actuate.endpoint.annotation.Selector.Match; import org.springframework.boot.actuate.endpoint.annotation.Selector.Match;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -43,87 +36,60 @@ class OperationMethodParameterTests {
private final Method example = ReflectionUtils.findMethod(getClass(), "example", String.class, String.class); private final Method example = ReflectionUtils.findMethod(getClass(), "example", String.class, String.class);
private final Method exampleJSpecifyNullable = ReflectionUtils.findMethod(getClass(), "exampleJSpecifyNullable",
String.class, String.class);
private final Method exampleSpringNullable = ReflectionUtils.findMethod(getClass(), "exampleSpringNullable", private final Method exampleSpringNullable = ReflectionUtils.findMethod(getClass(), "exampleSpringNullable",
String.class, String.class); String.class, String.class);
private Method exampleAnnotation = ReflectionUtils.findMethod(getClass(), "exampleAnnotation", String.class); private final Method exampleAnnotation = ReflectionUtils.findMethod(getClass(), "exampleAnnotation", String.class);
@Test @Test
void getNameShouldReturnName() { void getNameShouldReturnName() {
OperationMethodParameter parameter = new OperationMethodParameter("name", this.example.getParameters()[0], OperationMethodParameter parameter = new OperationMethodParameter("name", this.example.getParameters()[0]);
this::isOptionalParameter);
assertThat(parameter.getName()).isEqualTo("name"); assertThat(parameter.getName()).isEqualTo("name");
} }
@Test @Test
void getTypeShouldReturnType() { void getTypeShouldReturnType() {
OperationMethodParameter parameter = new OperationMethodParameter("name", this.example.getParameters()[0], OperationMethodParameter parameter = new OperationMethodParameter("name", this.example.getParameters()[0]);
this::isOptionalParameter);
assertThat(parameter.getType()).isEqualTo(String.class); assertThat(parameter.getType()).isEqualTo(String.class);
} }
@Test @Test
void isMandatoryWhenNoAnnotationShouldReturnTrue() { void isMandatoryWhenNoAnnotationShouldReturnTrue() {
OperationMethodParameter parameter = new OperationMethodParameter("name", this.example.getParameters()[0], OperationMethodParameter parameter = new OperationMethodParameter("name", this.example.getParameters()[0]);
this::isOptionalParameter);
assertThat(parameter.isMandatory()).isTrue(); assertThat(parameter.isMandatory()).isTrue();
} }
@Test @Test
void isMandatoryWhenOptionalAnnotationShouldReturnFalse() { void isMandatoryWhenNullableAnnotationShouldReturnFalse() {
OperationMethodParameter parameter = new OperationMethodParameter("name", this.example.getParameters()[1], OperationMethodParameter parameter = new OperationMethodParameter("name", this.example.getParameters()[1]);
this::isOptionalParameter);
assertThat(parameter.isMandatory()).isFalse();
}
@Test
void isMandatoryWhenJSpecifyNullableAnnotationShouldReturnFalse() {
OperationMethodParameter parameter = new OperationMethodParameter("name",
this.exampleJSpecifyNullable.getParameters()[1], this::isOptionalParameter);
assertThat(parameter.isMandatory()).isFalse(); assertThat(parameter.isMandatory()).isFalse();
} }
@Test @Test
@Deprecated(since = "4.0.0")
void isMandatoryWhenSpringNullableAnnotationShouldReturnFalse() { void isMandatoryWhenSpringNullableAnnotationShouldReturnFalse() {
OperationMethodParameter parameter = new OperationMethodParameter("name", OperationMethodParameter parameter = new OperationMethodParameter("name",
this.exampleSpringNullable.getParameters()[1], this::isOptionalParameter); this.exampleSpringNullable.getParameters()[1]);
assertThat(parameter.isMandatory()).isFalse(); assertThat(parameter.isMandatory()).isFalse();
} }
@Test @Test
void getAnnotationShouldReturnAnnotation() { void getAnnotationShouldReturnAnnotation() {
OperationMethodParameter parameter = new OperationMethodParameter("name", OperationMethodParameter parameter = new OperationMethodParameter("name",
this.exampleAnnotation.getParameters()[0], this::isOptionalParameter); this.exampleAnnotation.getParameters()[0]);
Selector annotation = parameter.getAnnotation(Selector.class); Selector annotation = parameter.getAnnotation(Selector.class);
assertThat(annotation).isNotNull(); assertThat(annotation).isNotNull();
assertThat(annotation.match()).isEqualTo(Match.ALL_REMAINING); assertThat(annotation.match()).isEqualTo(Match.ALL_REMAINING);
} }
private boolean isOptionalParameter(Parameter parameter) { void example(String one, @org.jspecify.annotations.Nullable String two) {
return MergedAnnotations.from(parameter).isPresent(TestOptional.class);
} }
void example(String one, @TestOptional String two) { @Deprecated(since = "4.0.0")
}
void exampleJSpecifyNullable(String one, @org.jspecify.annotations.Nullable String two) {
}
@SuppressWarnings("deprecation")
void exampleSpringNullable(String one, @org.springframework.lang.Nullable String two) { void exampleSpringNullable(String one, @org.springframework.lang.Nullable String two) {
} }
void exampleAnnotation(@Selector(match = Match.ALL_REMAINING) String allRemaining) { void exampleAnnotation(@Selector(match = Match.ALL_REMAINING) String allRemaining) {
} }
@Target({ ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestOptional {
}
} }

22
module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoke/reflect/OperationMethodParametersTests.java

@ -17,12 +17,10 @@
package org.springframework.boot.actuate.endpoint.invoke.reflect; package org.springframework.boot.actuate.endpoint.invoke.reflect;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Spliterator; import java.util.Spliterator;
import java.util.Spliterators; import java.util.Spliterators;
import java.util.function.Predicate;
import java.util.stream.Stream; import java.util.stream.Stream;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
@ -45,8 +43,6 @@ import static org.mockito.Mockito.mock;
*/ */
class OperationMethodParametersTests { class OperationMethodParametersTests {
private static final Predicate<Parameter> NON_OPTIONAL = (parameter) -> false;
private final Method exampleMethod = ReflectionUtils.findMethod(getClass(), "example", String.class); private final Method exampleMethod = ReflectionUtils.findMethod(getClass(), "example", String.class);
private final Method exampleNoParamsMethod = ReflectionUtils.findMethod(getClass(), "exampleNoParams"); private final Method exampleNoParamsMethod = ReflectionUtils.findMethod(getClass(), "exampleNoParams");
@ -54,50 +50,48 @@ class OperationMethodParametersTests {
@Test @Test
void createWhenMethodIsNullShouldThrowException() { void createWhenMethodIsNullShouldThrowException() {
assertThatIllegalArgumentException() assertThatIllegalArgumentException()
.isThrownBy(() -> new OperationMethodParameters(null, mock(ParameterNameDiscoverer.class), NON_OPTIONAL)) .isThrownBy(() -> new OperationMethodParameters(null, mock(ParameterNameDiscoverer.class)))
.withMessageContaining("'method' must not be null"); .withMessageContaining("'method' must not be null");
} }
@Test @Test
void createWhenParameterNameDiscovererIsNullShouldThrowException() { void createWhenParameterNameDiscovererIsNullShouldThrowException() {
assertThatIllegalArgumentException() assertThatIllegalArgumentException().isThrownBy(() -> new OperationMethodParameters(this.exampleMethod, null))
.isThrownBy(() -> new OperationMethodParameters(this.exampleMethod, null, NON_OPTIONAL))
.withMessageContaining("'parameterNameDiscoverer' must not be null"); .withMessageContaining("'parameterNameDiscoverer' must not be null");
} }
@Test @Test
void createWhenParameterNameDiscovererReturnsNullShouldThrowException() { void createWhenParameterNameDiscovererReturnsNullShouldThrowException() {
assertThatIllegalStateException() assertThatIllegalStateException()
.isThrownBy(() -> new OperationMethodParameters(this.exampleMethod, mock(ParameterNameDiscoverer.class), .isThrownBy(() -> new OperationMethodParameters(this.exampleMethod, mock(ParameterNameDiscoverer.class)))
NON_OPTIONAL))
.withMessageContaining("Failed to extract parameter names"); .withMessageContaining("Failed to extract parameter names");
} }
@Test @Test
void hasParametersWhenHasParametersShouldReturnTrue() { void hasParametersWhenHasParametersShouldReturnTrue() {
OperationMethodParameters parameters = new OperationMethodParameters(this.exampleMethod, OperationMethodParameters parameters = new OperationMethodParameters(this.exampleMethod,
new DefaultParameterNameDiscoverer(), NON_OPTIONAL); new DefaultParameterNameDiscoverer());
assertThat(parameters.hasParameters()).isTrue(); assertThat(parameters.hasParameters()).isTrue();
} }
@Test @Test
void hasParametersWhenHasNoParametersShouldReturnFalse() { void hasParametersWhenHasNoParametersShouldReturnFalse() {
OperationMethodParameters parameters = new OperationMethodParameters(this.exampleNoParamsMethod, OperationMethodParameters parameters = new OperationMethodParameters(this.exampleNoParamsMethod,
new DefaultParameterNameDiscoverer(), NON_OPTIONAL); new DefaultParameterNameDiscoverer());
assertThat(parameters.hasParameters()).isFalse(); assertThat(parameters.hasParameters()).isFalse();
} }
@Test @Test
void getParameterCountShouldReturnParameterCount() { void getParameterCountShouldReturnParameterCount() {
OperationMethodParameters parameters = new OperationMethodParameters(this.exampleMethod, OperationMethodParameters parameters = new OperationMethodParameters(this.exampleMethod,
new DefaultParameterNameDiscoverer(), NON_OPTIONAL); new DefaultParameterNameDiscoverer());
assertThat(parameters.getParameterCount()).isOne(); assertThat(parameters.getParameterCount()).isOne();
} }
@Test @Test
void iteratorShouldIterateOperationParameters() { void iteratorShouldIterateOperationParameters() {
OperationMethodParameters parameters = new OperationMethodParameters(this.exampleMethod, OperationMethodParameters parameters = new OperationMethodParameters(this.exampleMethod,
new DefaultParameterNameDiscoverer(), NON_OPTIONAL); new DefaultParameterNameDiscoverer());
Iterator<OperationParameter> iterator = parameters.iterator(); Iterator<OperationParameter> iterator = parameters.iterator();
assertParameters( assertParameters(
StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false)); StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false));
@ -106,7 +100,7 @@ class OperationMethodParametersTests {
@Test @Test
void streamShouldStreamOperationParameters() { void streamShouldStreamOperationParameters() {
OperationMethodParameters parameters = new OperationMethodParameters(this.exampleMethod, OperationMethodParameters parameters = new OperationMethodParameters(this.exampleMethod,
new DefaultParameterNameDiscoverer(), NON_OPTIONAL); new DefaultParameterNameDiscoverer());
assertParameters(parameters.stream()); assertParameters(parameters.stream());
} }

16
module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoke/reflect/OperationMethodTests.java

@ -17,8 +17,6 @@
package org.springframework.boot.actuate.endpoint.invoke.reflect; package org.springframework.boot.actuate.endpoint.invoke.reflect;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.function.Predicate;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -36,39 +34,35 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
*/ */
class OperationMethodTests { class OperationMethodTests {
private static final Predicate<Parameter> NON_OPTIONAL = (parameter) -> false;
private final Method exampleMethod = ReflectionUtils.findMethod(getClass(), "example", String.class); private final Method exampleMethod = ReflectionUtils.findMethod(getClass(), "example", String.class);
@Test @Test
void createWhenMethodIsNullShouldThrowException() { void createWhenMethodIsNullShouldThrowException() {
assertThatIllegalArgumentException() assertThatIllegalArgumentException().isThrownBy(() -> new OperationMethod(null, OperationType.READ))
.isThrownBy(() -> new OperationMethod(null, OperationType.READ, NON_OPTIONAL))
.withMessageContaining("'method' must not be null"); .withMessageContaining("'method' must not be null");
} }
@Test @Test
void createWhenOperationTypeIsNullShouldThrowException() { void createWhenOperationTypeIsNullShouldThrowException() {
assertThatIllegalArgumentException() assertThatIllegalArgumentException().isThrownBy(() -> new OperationMethod(this.exampleMethod, null))
.isThrownBy(() -> new OperationMethod(this.exampleMethod, null, NON_OPTIONAL))
.withMessageContaining("'operationType' must not be null"); .withMessageContaining("'operationType' must not be null");
} }
@Test @Test
void getMethodShouldReturnMethod() { void getMethodShouldReturnMethod() {
OperationMethod operationMethod = new OperationMethod(this.exampleMethod, OperationType.READ, NON_OPTIONAL); OperationMethod operationMethod = new OperationMethod(this.exampleMethod, OperationType.READ);
assertThat(operationMethod.getMethod()).isEqualTo(this.exampleMethod); assertThat(operationMethod.getMethod()).isEqualTo(this.exampleMethod);
} }
@Test @Test
void getOperationTypeShouldReturnOperationType() { void getOperationTypeShouldReturnOperationType() {
OperationMethod operationMethod = new OperationMethod(this.exampleMethod, OperationType.READ, NON_OPTIONAL); OperationMethod operationMethod = new OperationMethod(this.exampleMethod, OperationType.READ);
assertThat(operationMethod.getOperationType()).isEqualTo(OperationType.READ); assertThat(operationMethod.getOperationType()).isEqualTo(OperationType.READ);
} }
@Test @Test
void getParametersShouldReturnParameters() { void getParametersShouldReturnParameters() {
OperationMethod operationMethod = new OperationMethod(this.exampleMethod, OperationType.READ, NON_OPTIONAL); OperationMethod operationMethod = new OperationMethod(this.exampleMethod, OperationType.READ);
OperationParameters parameters = operationMethod.getParameters(); OperationParameters parameters = operationMethod.getParameters();
assertThat(parameters.getParameterCount()).isOne(); assertThat(parameters.getParameterCount()).isOne();
assertThat(parameters.iterator().next().getName()).isEqualTo("name"); assertThat(parameters.iterator().next().getName()).isEqualTo("name");

26
module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoke/reflect/ReflectiveOperationInvokerTests.java

@ -16,14 +16,9 @@
package org.springframework.boot.actuate.endpoint.invoke.reflect; package org.springframework.boot.actuate.endpoint.invoke.reflect;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Parameter;
import java.util.Collections; import java.util.Collections;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -33,7 +28,6 @@ import org.springframework.boot.actuate.endpoint.OperationType;
import org.springframework.boot.actuate.endpoint.SecurityContext; import org.springframework.boot.actuate.endpoint.SecurityContext;
import org.springframework.boot.actuate.endpoint.invoke.MissingParametersException; import org.springframework.boot.actuate.endpoint.invoke.MissingParametersException;
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper; import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -58,7 +52,7 @@ class ReflectiveOperationInvokerTests {
void setup() { void setup() {
this.target = new Example(); this.target = new Example();
this.operationMethod = new OperationMethod(ReflectionUtils.findMethod(Example.class, "reverse", this.operationMethod = new OperationMethod(ReflectionUtils.findMethod(Example.class, "reverse",
ApiVersion.class, SecurityContext.class, String.class), OperationType.READ, this::isOptional); ApiVersion.class, SecurityContext.class, String.class), OperationType.READ);
this.parameterValueMapper = (parameter, value) -> (value != null) ? value.toString() : null; this.parameterValueMapper = (parameter, value) -> (value != null) ? value.toString() : null;
} }
@ -103,8 +97,7 @@ class ReflectiveOperationInvokerTests {
@Test @Test
void invokeWhenMissingOptionalArgumentShouldInvoke() { void invokeWhenMissingOptionalArgumentShouldInvoke() {
OperationMethod operationMethod = new OperationMethod(ReflectionUtils.findMethod(Example.class, OperationMethod operationMethod = new OperationMethod(ReflectionUtils.findMethod(Example.class,
"reverseOptional", ApiVersion.class, SecurityContext.class, String.class), OperationType.READ, "reverseOptional", ApiVersion.class, SecurityContext.class, String.class), OperationType.READ);
this::isOptional);
ReflectiveOperationInvoker invoker = new ReflectiveOperationInvoker(this.target, operationMethod, ReflectiveOperationInvoker invoker = new ReflectiveOperationInvoker(this.target, operationMethod,
this.parameterValueMapper); this.parameterValueMapper);
Object result = invoker Object result = invoker
@ -121,10 +114,6 @@ class ReflectiveOperationInvokerTests {
assertThat(result).isEqualTo("4321"); assertThat(result).isEqualTo("4321");
} }
private boolean isOptional(Parameter parameter) {
return MergedAnnotations.from(parameter).isPresent(TestOptional.class);
}
static class Example { static class Example {
String reverse(ApiVersion apiVersion, SecurityContext securityContext, String name) { String reverse(ApiVersion apiVersion, SecurityContext securityContext, String name) {
@ -133,7 +122,7 @@ class ReflectiveOperationInvokerTests {
return new StringBuilder(name).reverse().toString(); return new StringBuilder(name).reverse().toString();
} }
String reverseOptional(ApiVersion apiVersion, SecurityContext securityContext, @TestOptional String name) { String reverseOptional(ApiVersion apiVersion, SecurityContext securityContext, @Nullable String name) {
assertThat(apiVersion).isEqualTo(ApiVersion.LATEST); assertThat(apiVersion).isEqualTo(ApiVersion.LATEST);
assertThat(securityContext).isNotNull(); assertThat(securityContext).isNotNull();
return new StringBuilder(String.valueOf(name)).reverse().toString(); return new StringBuilder(String.valueOf(name)).reverse().toString();
@ -141,11 +130,4 @@ class ReflectiveOperationInvokerTests {
} }
@Target({ ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestOptional {
}
} }

28
module/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/invoker/cache/CachingOperationInvokerAdvisorTests.java vendored

@ -16,14 +16,10 @@
package org.springframework.boot.actuate.endpoint.invoker.cache; package org.springframework.boot.actuate.endpoint.invoker.cache;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.function.Function; import java.util.function.Function;
import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@ -38,7 +34,6 @@ import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
import org.springframework.boot.actuate.endpoint.invoke.OperationParameters; import org.springframework.boot.actuate.endpoint.invoke.OperationParameters;
import org.springframework.boot.actuate.endpoint.invoke.reflect.OperationMethod; import org.springframework.boot.actuate.endpoint.invoke.reflect.OperationMethod;
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace; import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -163,34 +158,32 @@ class CachingOperationInvokerAdvisorTests {
private OperationMethod getOperationMethod(String methodName, Class<?>... parameterTypes) { private OperationMethod getOperationMethod(String methodName, Class<?>... parameterTypes) {
Method method = ReflectionUtils.findMethod(TestOperations.class, methodName, parameterTypes); Method method = ReflectionUtils.findMethod(TestOperations.class, methodName, parameterTypes);
return new OperationMethod(method, OperationType.READ, return new OperationMethod(method, OperationType.READ);
(parameter) -> MergedAnnotations.from(parameter).isPresent(TestOptional.class));
} }
@SuppressWarnings("deprecation")
static class TestOperations { static class TestOperations {
String get() { String get() {
return ""; return "";
} }
String getWithParameters(@TestOptional String foo, String bar) { String getWithParameters(@Nullable String foo, String bar) {
return ""; return "";
} }
String getWithAllOptionalParameters(@TestOptional String foo, @TestOptional String bar) { String getWithAllOptionalParameters(@Nullable String foo, @Nullable String bar) {
return ""; return "";
} }
String getWithSecurityContext(SecurityContext securityContext, @TestOptional String bar) { String getWithSecurityContext(SecurityContext securityContext, @Nullable String bar) {
return ""; return "";
} }
String getWithApiVersion(ApiVersion apiVersion, @TestOptional String bar) { String getWithApiVersion(ApiVersion apiVersion, @Nullable String bar) {
return ""; return "";
} }
String getWithServerNamespace(WebServerNamespace serverNamespace, @TestOptional String bar) { String getWithServerNamespace(WebServerNamespace serverNamespace, @Nullable String bar) {
return ""; return "";
} }
@ -200,11 +193,4 @@ class CachingOperationInvokerAdvisorTests {
} }
@Target({ ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestOptional {
}
} }

6
module/spring-boot-cache/src/main/java/org/springframework/boot/cache/actuate/endpoint/CachesEndpoint.java vendored

@ -27,7 +27,6 @@ import org.jspecify.annotations.Nullable;
import org.springframework.boot.actuate.endpoint.OperationResponseBody; import org.springframework.boot.actuate.endpoint.OperationResponseBody;
import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation; import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.OptionalParameter;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector; import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.cache.Cache; import org.springframework.cache.Cache;
@ -82,8 +81,7 @@ public class CachesEndpoint {
* {@code cacheManager} was provided to identify a unique candidate * {@code cacheManager} was provided to identify a unique candidate
*/ */
@ReadOperation @ReadOperation
public @Nullable CacheEntryDescriptor cache(@Selector String cache, public @Nullable CacheEntryDescriptor cache(@Selector String cache, @Nullable String cacheManager) {
@OptionalParameter @Nullable String cacheManager) {
return extractUniqueCacheEntry(cache, getCacheEntries((name) -> name.equals(cache), isNameMatch(cacheManager))); return extractUniqueCacheEntry(cache, getCacheEntries((name) -> name.equals(cache), isNameMatch(cacheManager)));
} }
@ -105,7 +103,7 @@ public class CachesEndpoint {
* {@code cacheManager} was provided to identify a unique candidate * {@code cacheManager} was provided to identify a unique candidate
*/ */
@DeleteOperation @DeleteOperation
public boolean clearCache(@Selector String cache, @OptionalParameter @Nullable String cacheManager) { public boolean clearCache(@Selector String cache, @Nullable String cacheManager) {
CacheEntryDescriptor entry = extractUniqueCacheEntry(cache, CacheEntryDescriptor entry = extractUniqueCacheEntry(cache,
getCacheEntries((name) -> name.equals(cache), isNameMatch(cacheManager))); getCacheEntries((name) -> name.equals(cache), isNameMatch(cacheManager)));
return (entry != null && clearCache(entry)); return (entry != null && clearCache(entry));

7
module/spring-boot-cache/src/main/java/org/springframework/boot/cache/actuate/endpoint/CachesEndpointWebExtension.java vendored

@ -19,7 +19,6 @@ package org.springframework.boot.cache.actuate.endpoint;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation; import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation;
import org.springframework.boot.actuate.endpoint.annotation.OptionalParameter;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector; import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
@ -42,8 +41,7 @@ public class CachesEndpointWebExtension {
} }
@ReadOperation @ReadOperation
public WebEndpointResponse<CacheEntryDescriptor> cache(@Selector String cache, public WebEndpointResponse<CacheEntryDescriptor> cache(@Selector String cache, @Nullable String cacheManager) {
@OptionalParameter @Nullable String cacheManager) {
try { try {
CacheEntryDescriptor entry = this.delegate.cache(cache, cacheManager); CacheEntryDescriptor entry = this.delegate.cache(cache, cacheManager);
int status = (entry != null) ? WebEndpointResponse.STATUS_OK : WebEndpointResponse.STATUS_NOT_FOUND; int status = (entry != null) ? WebEndpointResponse.STATUS_OK : WebEndpointResponse.STATUS_NOT_FOUND;
@ -55,8 +53,7 @@ public class CachesEndpointWebExtension {
} }
@DeleteOperation @DeleteOperation
public WebEndpointResponse<Void> clearCache(@Selector String cache, public WebEndpointResponse<Void> clearCache(@Selector String cache, @Nullable String cacheManager) {
@OptionalParameter @Nullable String cacheManager) {
try { try {
boolean cleared = this.delegate.clearCache(cache, cacheManager); boolean cleared = this.delegate.clearCache(cache, cacheManager);
int status = (cleared ? WebEndpointResponse.STATUS_NO_CONTENT : WebEndpointResponse.STATUS_NOT_FOUND); int status = (cleared ? WebEndpointResponse.STATUS_NO_CONTENT : WebEndpointResponse.STATUS_NOT_FOUND);

4
module/spring-boot-micrometer-metrics/src/main/java/org/springframework/boot/micrometer/metrics/actuate/endpoint/MetricsEndpoint.java

@ -37,7 +37,6 @@ import org.jspecify.annotations.Nullable;
import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException; import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException;
import org.springframework.boot.actuate.endpoint.OperationResponseBody; import org.springframework.boot.actuate.endpoint.OperationResponseBody;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.OptionalParameter;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.annotation.Selector; import org.springframework.boot.actuate.endpoint.annotation.Selector;
@ -78,8 +77,7 @@ public class MetricsEndpoint {
} }
@ReadOperation @ReadOperation
public @Nullable MetricDescriptor metric(@Selector String requiredMetricName, public @Nullable MetricDescriptor metric(@Selector String requiredMetricName, @Nullable List<String> tag) {
@OptionalParameter @Nullable List<String> tag) {
List<Tag> tags = parseTags(tag); List<Tag> tags = parseTags(tag);
Collection<Meter> meters = findFirstMatchingMeters(this.registry, requiredMetricName, tags); Collection<Meter> meters = findFirstMatchingMeters(this.registry, requiredMetricName, tags);
if (meters.isEmpty()) { if (meters.isEmpty()) {

Loading…
Cancel
Save