Replace Actuator @Nullable with @OptionalParameter
Closes gh-45389
This commit is contained in:
@@ -22,8 +22,8 @@ import java.util.List;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.OperationResponseBody;
|
||||
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.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -43,8 +43,8 @@ public class AuditEventsEndpoint {
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public AuditEventsDescriptor events(@Nullable String principal, @Nullable OffsetDateTime after,
|
||||
@Nullable String type) {
|
||||
public AuditEventsDescriptor events(@OptionalParameter String principal, @OptionalParameter OffsetDateTime after,
|
||||
@OptionalParameter String type) {
|
||||
List<AuditEvent> events = this.auditEventRepository.find(principal, getInstant(after), type);
|
||||
return new AuditEventsDescriptor(events);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
* Copyright 2012-2025 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.
|
||||
@@ -25,11 +25,11 @@ import java.util.function.Predicate;
|
||||
import org.springframework.boot.actuate.endpoint.OperationResponseBody;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation;
|
||||
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.Selector;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link Endpoint @Endpoint} to expose available {@link Cache caches}.
|
||||
@@ -79,7 +79,7 @@ public class CachesEndpoint {
|
||||
* {@code cacheManager} was provided to identify a unique candidate
|
||||
*/
|
||||
@ReadOperation
|
||||
public CacheEntryDescriptor cache(@Selector String cache, @Nullable String cacheManager) {
|
||||
public CacheEntryDescriptor cache(@Selector String cache, @OptionalParameter String cacheManager) {
|
||||
return extractUniqueCacheEntry(cache, getCacheEntries((name) -> name.equals(cache), isNameMatch(cacheManager)));
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ public class CachesEndpoint {
|
||||
* {@code cacheManager} was provided to identify a unique candidate
|
||||
*/
|
||||
@DeleteOperation
|
||||
public boolean clearCache(@Selector String cache, @Nullable String cacheManager) {
|
||||
public boolean clearCache(@Selector String cache, @OptionalParameter String cacheManager) {
|
||||
CacheEntryDescriptor entry = extractUniqueCacheEntry(cache,
|
||||
getCacheEntries((name) -> name.equals(cache), isNameMatch(cacheManager)));
|
||||
return (entry != null && clearCache(entry));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2022 the original author or authors.
|
||||
* Copyright 2012-2025 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.
|
||||
@@ -18,11 +18,11 @@ package org.springframework.boot.actuate.cache;
|
||||
|
||||
import org.springframework.boot.actuate.cache.CachesEndpoint.CacheEntryDescriptor;
|
||||
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.Selector;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link EndpointWebExtension @EndpointWebExtension} for the {@link CachesEndpoint}.
|
||||
@@ -40,7 +40,8 @@ public class CachesEndpointWebExtension {
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public WebEndpointResponse<CacheEntryDescriptor> cache(@Selector String cache, @Nullable String cacheManager) {
|
||||
public WebEndpointResponse<CacheEntryDescriptor> cache(@Selector String cache,
|
||||
@OptionalParameter String cacheManager) {
|
||||
try {
|
||||
CacheEntryDescriptor entry = this.delegate.cache(cache, cacheManager);
|
||||
int status = (entry != null) ? WebEndpointResponse.STATUS_OK : WebEndpointResponse.STATUS_NOT_FOUND;
|
||||
@@ -52,7 +53,7 @@ public class CachesEndpointWebExtension {
|
||||
}
|
||||
|
||||
@DeleteOperation
|
||||
public WebEndpointResponse<Void> clearCache(@Selector String cache, @Nullable String cacheManager) {
|
||||
public WebEndpointResponse<Void> clearCache(@Selector String cache, @OptionalParameter String cacheManager) {
|
||||
try {
|
||||
boolean cleared = this.delegate.clearCache(cache, cacheManager);
|
||||
int status = (cleared ? WebEndpointResponse.STATUS_NO_CONTENT : WebEndpointResponse.STATUS_NOT_FOUND);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.boot.actuate.endpoint.annotation;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -26,6 +27,7 @@ import org.springframework.boot.actuate.endpoint.OperationType;
|
||||
import org.springframework.boot.actuate.endpoint.Producible;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.reflect.OperationMethod;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -40,7 +42,7 @@ public class DiscoveredOperationMethod extends OperationMethod {
|
||||
|
||||
public DiscoveredOperationMethod(Method method, OperationType operationType,
|
||||
AnnotationAttributes annotationAttributes) {
|
||||
super(method, operationType);
|
||||
super(method, operationType, DiscoveredOperationMethod::isOptionalParameter);
|
||||
Assert.notNull(annotationAttributes, "'annotationAttributes' must not be null");
|
||||
List<String> producesMediaTypes = new ArrayList<>();
|
||||
producesMediaTypes.addAll(Arrays.asList(annotationAttributes.getStringArray("produces")));
|
||||
@@ -48,6 +50,10 @@ public class DiscoveredOperationMethod extends OperationMethod {
|
||||
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(
|
||||
AnnotationAttributes annotationAttributes) {
|
||||
Class<?> type = getProducesFrom(annotationAttributes);
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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 {
|
||||
|
||||
}
|
||||
@@ -17,7 +17,9 @@
|
||||
package org.springframework.boot.actuate.endpoint.invoke.reflect;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.OperationType;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationParameters;
|
||||
@@ -46,13 +48,28 @@ public class OperationMethod {
|
||||
* Create a new {@link OperationMethod} instance.
|
||||
* @param method the source method
|
||||
* @param operationType the operation type
|
||||
* @deprecated since 4.0.0 for removal in 4.2.0 in favor of
|
||||
* {@link #OperationMethod(Method, OperationType, Predicate)}p
|
||||
*/
|
||||
@Deprecated(since = "4.0.0", forRemoval = true)
|
||||
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(operationType, "'operationType' must not be null");
|
||||
this.method = method;
|
||||
this.operationType = operationType;
|
||||
this.operationParameters = new OperationMethodParameters(method, DEFAULT_PARAMETER_NAME_DISCOVERER);
|
||||
this.operationParameters = new OperationMethodParameters(method, DEFAULT_PARAMETER_NAME_DISCOVERER,
|
||||
optionalParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2024 the original author or authors.
|
||||
* Copyright 2012-2025 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.
|
||||
@@ -18,6 +18,7 @@ package org.springframework.boot.actuate.endpoint.invoke.reflect;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.meta.When;
|
||||
@@ -27,7 +28,6 @@ import org.springframework.core.annotation.MergedAnnotation;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* {@link OperationParameter} created from an {@link OperationMethod}.
|
||||
@@ -43,14 +43,18 @@ class OperationMethodParameter implements OperationParameter {
|
||||
|
||||
private final Parameter parameter;
|
||||
|
||||
private final Predicate<Parameter> optional;
|
||||
|
||||
/**
|
||||
* Create a new {@link OperationMethodParameter} instance.
|
||||
* @param name the parameter name
|
||||
* @param parameter the parameter
|
||||
* @param optionalParameters predicate to test if a parameter is optional
|
||||
*/
|
||||
OperationMethodParameter(String name, Parameter parameter) {
|
||||
OperationMethodParameter(String name, Parameter parameter, Predicate<Parameter> optionalParameters) {
|
||||
this.name = name;
|
||||
this.parameter = parameter;
|
||||
this.optional = optionalParameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -65,7 +69,7 @@ class OperationMethodParameter implements OperationParameter {
|
||||
|
||||
@Override
|
||||
public boolean isMandatory() {
|
||||
if (!ObjectUtils.isEmpty(this.parameter.getAnnotationsByType(Nullable.class))) {
|
||||
if (isOptional()) {
|
||||
return false;
|
||||
}
|
||||
if (jsr305Present) {
|
||||
@@ -74,6 +78,11 @@ class OperationMethodParameter implements OperationParameter {
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private boolean isOptional() {
|
||||
return this.parameter.getAnnotationsByType(Nullable.class).length > 0 || this.optional.test(this.parameter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Annotation> T getAnnotation(Class<T> annotation) {
|
||||
return this.parameter.getAnnotation(annotation);
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationParameter;
|
||||
@@ -42,20 +43,24 @@ class OperationMethodParameters implements OperationParameters {
|
||||
* Create a new {@link OperationMethodParameters} instance.
|
||||
* @param method the source method
|
||||
* @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(parameterNameDiscoverer, "'parameterNameDiscoverer' must not be null");
|
||||
Assert.notNull(optionalParameters, "'optionalParameters' must not be null");
|
||||
String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
|
||||
Parameter[] parameters = method.getParameters();
|
||||
Assert.state(parameterNames != null, () -> "Failed to extract parameter names for " + method);
|
||||
this.operationParameters = getOperationParameters(parameters, parameterNames);
|
||||
this.operationParameters = getOperationParameters(parameters, parameterNames, optionalParameters);
|
||||
}
|
||||
|
||||
private List<OperationParameter> getOperationParameters(Parameter[] parameters, String[] names) {
|
||||
private List<OperationParameter> getOperationParameters(Parameter[] parameters, String[] names,
|
||||
Predicate<Parameter> optionalParameters) {
|
||||
List<OperationParameter> operationParameters = new ArrayList<>(parameters.length);
|
||||
for (int i = 0; i < names.length; i++) {
|
||||
operationParameters.add(new OperationMethodParameter(names[i], parameters[i]));
|
||||
operationParameters.add(new OperationMethodParameter(names[i], parameters[i], optionalParameters));
|
||||
}
|
||||
return Collections.unmodifiableList(operationParameters);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2024 the original author or authors.
|
||||
* Copyright 2012-2025 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.
|
||||
@@ -33,6 +33,7 @@ import org.springframework.boot.actuate.endpoint.Sanitizer;
|
||||
import org.springframework.boot.actuate.endpoint.SanitizingFunction;
|
||||
import org.springframework.boot.actuate.endpoint.Show;
|
||||
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.Selector;
|
||||
import org.springframework.boot.context.properties.bind.PlaceholdersResolver;
|
||||
@@ -47,7 +48,6 @@ import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.env.MutablePropertySources;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -80,7 +80,7 @@ public class EnvironmentEndpoint {
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public EnvironmentDescriptor environment(@Nullable String pattern) {
|
||||
public EnvironmentDescriptor environment(@OptionalParameter String pattern) {
|
||||
boolean showUnsanitized = this.showValues.isShown(true);
|
||||
return getEnvironmentDescriptor(pattern, showUnsanitized);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2022 the original author or authors.
|
||||
* Copyright 2012-2025 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.
|
||||
@@ -20,13 +20,13 @@ import java.util.Set;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
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.Selector;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
|
||||
import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentDescriptor;
|
||||
import org.springframework.boot.actuate.env.EnvironmentEndpoint.EnvironmentEntryDescriptor;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link EndpointWebExtension @EndpointWebExtension} for the {@link EnvironmentEndpoint}.
|
||||
@@ -51,7 +51,7 @@ public class EnvironmentEndpointWebExtension {
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public EnvironmentDescriptor environment(SecurityContext securityContext, @Nullable String pattern) {
|
||||
public EnvironmentDescriptor environment(SecurityContext securityContext, @OptionalParameter String pattern) {
|
||||
boolean showUnsanitized = this.showValues.isShown(securityContext, this.roles);
|
||||
return this.delegate.getEnvironmentDescriptor(pattern, showUnsanitized);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import java.util.TreeSet;
|
||||
import org.springframework.aot.hint.annotation.RegisterReflectionForBinding;
|
||||
import org.springframework.boot.actuate.endpoint.OperationResponseBody;
|
||||
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.Selector;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
|
||||
@@ -39,7 +40,6 @@ import org.springframework.boot.logging.LoggerConfiguration.LevelConfiguration;
|
||||
import org.springframework.boot.logging.LoggerGroup;
|
||||
import org.springframework.boot.logging.LoggerGroups;
|
||||
import org.springframework.boot.logging.LoggingSystem;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -98,7 +98,7 @@ public class LoggersEndpoint {
|
||||
}
|
||||
|
||||
@WriteOperation
|
||||
public void configureLogLevel(@Selector String name, @Nullable LogLevel configuredLevel) {
|
||||
public void configureLogLevel(@Selector String name, @OptionalParameter LogLevel configuredLevel) {
|
||||
Assert.notNull(name, "'name' must not be empty");
|
||||
LoggerGroup group = this.loggerGroups.get(name);
|
||||
if (group != null && group.hasMembers()) {
|
||||
|
||||
@@ -37,12 +37,12 @@ import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
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.web.WebEndpointResponse;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpoint;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
@@ -74,7 +74,7 @@ public class HeapDumpWebEndpoint {
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public WebEndpointResponse<Resource> heapDump(@Nullable Boolean live) {
|
||||
public WebEndpointResponse<Resource> heapDump(@OptionalParameter Boolean live) {
|
||||
try {
|
||||
if (this.lock.tryLock(this.timeout, TimeUnit.MILLISECONDS)) {
|
||||
try {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
* Copyright 2012-2025 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.
|
||||
@@ -36,9 +36,9 @@ import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
|
||||
import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException;
|
||||
import org.springframework.boot.actuate.endpoint.OperationResponseBody;
|
||||
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.Selector;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* An {@link Endpoint @Endpoint} for exposing the metrics held by a {@link MeterRegistry}.
|
||||
@@ -77,7 +77,7 @@ public class MetricsEndpoint {
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public MetricDescriptor metric(@Selector String requiredMetricName, @Nullable List<String> tag) {
|
||||
public MetricDescriptor metric(@Selector String requiredMetricName, @OptionalParameter List<String> tag) {
|
||||
List<Tag> tags = parseTags(tag);
|
||||
Collection<Meter> meters = findFirstMatchingMeters(this.registry, requiredMetricName, tags);
|
||||
if (meters.isEmpty()) {
|
||||
|
||||
@@ -28,10 +28,10 @@ import io.prometheus.metrics.model.registry.PrometheusRegistry;
|
||||
import io.prometheus.metrics.model.snapshots.MetricSnapshots;
|
||||
|
||||
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.web.WebEndpointResponse;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpoint;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link Endpoint @Endpoint} that outputs metrics in a format that can be scraped by the
|
||||
@@ -68,7 +68,8 @@ public class PrometheusScrapeEndpoint {
|
||||
}
|
||||
|
||||
@ReadOperation(producesFrom = PrometheusOutputFormat.class)
|
||||
public WebEndpointResponse<byte[]> scrape(PrometheusOutputFormat format, @Nullable Set<String> includedNames) {
|
||||
public WebEndpointResponse<byte[]> scrape(PrometheusOutputFormat format,
|
||||
@OptionalParameter Set<String> includedNames) {
|
||||
try {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(this.nextMetricsScrapeSize);
|
||||
MetricSnapshots metricSnapshots = (includedNames != null)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
* Copyright 2012-2025 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.
|
||||
@@ -16,9 +16,13 @@
|
||||
|
||||
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.Parameter;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.meta.TypeQualifier;
|
||||
@@ -28,6 +32,7 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Selector.Match;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
@@ -43,6 +48,9 @@ class OperationMethodParameterTests {
|
||||
|
||||
private final Method example = ReflectionUtils.findMethod(getClass(), "example", String.class, String.class);
|
||||
|
||||
private final Method exampleSpringNullable = ReflectionUtils.findMethod(getClass(), "exampleSpringNullable",
|
||||
String.class, String.class);
|
||||
|
||||
private final Method exampleJsr305 = ReflectionUtils.findMethod(getClass(), "exampleJsr305", String.class,
|
||||
String.class);
|
||||
|
||||
@@ -56,59 +64,78 @@ class OperationMethodParameterTests {
|
||||
|
||||
@Test
|
||||
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");
|
||||
}
|
||||
|
||||
@Test
|
||||
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);
|
||||
}
|
||||
|
||||
@Test
|
||||
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();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isMandatoryWhenNullableAnnotationShouldReturnFalse() {
|
||||
OperationMethodParameter parameter = new OperationMethodParameter("name", this.example.getParameters()[1]);
|
||||
void isMandatoryWhenOptionalAnnotationShouldReturnFalse() {
|
||||
OperationMethodParameter parameter = new OperationMethodParameter("name", this.example.getParameters()[1],
|
||||
this::isOptionalParameter);
|
||||
assertThat(parameter.isMandatory()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isMandatoryWhenSpringNullableAnnotationShouldReturnFalse() {
|
||||
OperationMethodParameter parameter = new OperationMethodParameter("name",
|
||||
this.exampleSpringNullable.getParameters()[1], this::isOptionalParameter);
|
||||
assertThat(parameter.isMandatory()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isMandatoryWhenJsrNullableAnnotationShouldReturnFalse() {
|
||||
OperationMethodParameter parameter = new OperationMethodParameter("name",
|
||||
this.exampleJsr305.getParameters()[1]);
|
||||
OperationMethodParameter parameter = new OperationMethodParameter("name", this.exampleJsr305.getParameters()[1],
|
||||
this::isOptionalParameter);
|
||||
assertThat(parameter.isMandatory()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isMandatoryWhenJsrMetaNullableAnnotationShouldReturnFalse() {
|
||||
OperationMethodParameter parameter = new OperationMethodParameter("name",
|
||||
this.exampleMetaJsr305.getParameters()[1]);
|
||||
this.exampleMetaJsr305.getParameters()[1], this::isOptionalParameter);
|
||||
assertThat(parameter.isMandatory()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isMandatoryWhenJsrNonnullAnnotationShouldReturnTrue() {
|
||||
OperationMethodParameter parameter = new OperationMethodParameter("name",
|
||||
this.exampleJsr305NonNull.getParameters()[1]);
|
||||
this.exampleJsr305NonNull.getParameters()[1], this::isOptionalParameter);
|
||||
assertThat(parameter.isMandatory()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAnnotationShouldReturnAnnotation() {
|
||||
OperationMethodParameter parameter = new OperationMethodParameter("name",
|
||||
this.exampleAnnotation.getParameters()[0]);
|
||||
this.exampleAnnotation.getParameters()[0], this::isOptionalParameter);
|
||||
Selector annotation = parameter.getAnnotation(Selector.class);
|
||||
assertThat(annotation).isNotNull();
|
||||
assertThat(annotation.match()).isEqualTo(Match.ALL_REMAINING);
|
||||
}
|
||||
|
||||
void example(String one, @Nullable String two) {
|
||||
private boolean isOptionalParameter(Parameter parameter) {
|
||||
return MergedAnnotations.from(parameter).isPresent(TestOptional.class);
|
||||
}
|
||||
|
||||
void example(String one, @TestOptional String two) {
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
void exampleSpringNullable(String one, @Nullable String two) {
|
||||
}
|
||||
|
||||
void exampleJsr305(String one, @javax.annotation.Nullable String two) {
|
||||
@@ -130,4 +157,11 @@ class OperationMethodParameterTests {
|
||||
|
||||
}
|
||||
|
||||
@Target({ ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface TestOptional {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,10 +17,12 @@
|
||||
package org.springframework.boot.actuate.endpoint.invoke.reflect;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Spliterator;
|
||||
import java.util.Spliterators;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
@@ -43,6 +45,8 @@ import static org.mockito.Mockito.mock;
|
||||
*/
|
||||
class OperationMethodParametersTests {
|
||||
|
||||
private static final Predicate<Parameter> NON_OPTIONAL = (parameter) -> false;
|
||||
|
||||
private final Method exampleMethod = ReflectionUtils.findMethod(getClass(), "example", String.class);
|
||||
|
||||
private final Method exampleNoParamsMethod = ReflectionUtils.findMethod(getClass(), "exampleNoParams");
|
||||
@@ -50,48 +54,50 @@ class OperationMethodParametersTests {
|
||||
@Test
|
||||
void createWhenMethodIsNullShouldThrowException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new OperationMethodParameters(null, mock(ParameterNameDiscoverer.class)))
|
||||
.isThrownBy(() -> new OperationMethodParameters(null, mock(ParameterNameDiscoverer.class), NON_OPTIONAL))
|
||||
.withMessageContaining("'method' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenParameterNameDiscovererIsNullShouldThrowException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new OperationMethodParameters(this.exampleMethod, null))
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new OperationMethodParameters(this.exampleMethod, null, NON_OPTIONAL))
|
||||
.withMessageContaining("'parameterNameDiscoverer' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenParameterNameDiscovererReturnsNullShouldThrowException() {
|
||||
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");
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasParametersWhenHasParametersShouldReturnTrue() {
|
||||
OperationMethodParameters parameters = new OperationMethodParameters(this.exampleMethod,
|
||||
new DefaultParameterNameDiscoverer());
|
||||
new DefaultParameterNameDiscoverer(), NON_OPTIONAL);
|
||||
assertThat(parameters.hasParameters()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void hasParametersWhenHasNoParametersShouldReturnFalse() {
|
||||
OperationMethodParameters parameters = new OperationMethodParameters(this.exampleNoParamsMethod,
|
||||
new DefaultParameterNameDiscoverer());
|
||||
new DefaultParameterNameDiscoverer(), NON_OPTIONAL);
|
||||
assertThat(parameters.hasParameters()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getParameterCountShouldReturnParameterCount() {
|
||||
OperationMethodParameters parameters = new OperationMethodParameters(this.exampleMethod,
|
||||
new DefaultParameterNameDiscoverer());
|
||||
new DefaultParameterNameDiscoverer(), NON_OPTIONAL);
|
||||
assertThat(parameters.getParameterCount()).isOne();
|
||||
}
|
||||
|
||||
@Test
|
||||
void iteratorShouldIterateOperationParameters() {
|
||||
OperationMethodParameters parameters = new OperationMethodParameters(this.exampleMethod,
|
||||
new DefaultParameterNameDiscoverer());
|
||||
new DefaultParameterNameDiscoverer(), NON_OPTIONAL);
|
||||
Iterator<OperationParameter> iterator = parameters.iterator();
|
||||
assertParameters(
|
||||
StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false));
|
||||
@@ -100,7 +106,7 @@ class OperationMethodParametersTests {
|
||||
@Test
|
||||
void streamShouldStreamOperationParameters() {
|
||||
OperationMethodParameters parameters = new OperationMethodParameters(this.exampleMethod,
|
||||
new DefaultParameterNameDiscoverer());
|
||||
new DefaultParameterNameDiscoverer(), NON_OPTIONAL);
|
||||
assertParameters(parameters.stream());
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
package org.springframework.boot.actuate.endpoint.invoke.reflect;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -34,35 +36,39 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
|
||||
*/
|
||||
class OperationMethodTests {
|
||||
|
||||
private static final Predicate<Parameter> NON_OPTIONAL = (parameter) -> false;
|
||||
|
||||
private final Method exampleMethod = ReflectionUtils.findMethod(getClass(), "example", String.class);
|
||||
|
||||
@Test
|
||||
void createWhenMethodIsNullShouldThrowException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new OperationMethod(null, OperationType.READ))
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new OperationMethod(null, OperationType.READ, NON_OPTIONAL))
|
||||
.withMessageContaining("'method' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenOperationTypeIsNullShouldThrowException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new OperationMethod(this.exampleMethod, null))
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new OperationMethod(this.exampleMethod, null, NON_OPTIONAL))
|
||||
.withMessageContaining("'operationType' must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getMethodShouldReturnMethod() {
|
||||
OperationMethod operationMethod = new OperationMethod(this.exampleMethod, OperationType.READ);
|
||||
OperationMethod operationMethod = new OperationMethod(this.exampleMethod, OperationType.READ, NON_OPTIONAL);
|
||||
assertThat(operationMethod.getMethod()).isEqualTo(this.exampleMethod);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getOperationTypeShouldReturnOperationType() {
|
||||
OperationMethod operationMethod = new OperationMethod(this.exampleMethod, OperationType.READ);
|
||||
OperationMethod operationMethod = new OperationMethod(this.exampleMethod, OperationType.READ, NON_OPTIONAL);
|
||||
assertThat(operationMethod.getOperationType()).isEqualTo(OperationType.READ);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getParametersShouldReturnParameters() {
|
||||
OperationMethod operationMethod = new OperationMethod(this.exampleMethod, OperationType.READ);
|
||||
OperationMethod operationMethod = new OperationMethod(this.exampleMethod, OperationType.READ, NON_OPTIONAL);
|
||||
OperationParameters parameters = operationMethod.getParameters();
|
||||
assertThat(parameters.getParameterCount()).isOne();
|
||||
assertThat(parameters.iterator().next().getName()).isEqualTo("name");
|
||||
|
||||
@@ -16,6 +16,12 @@
|
||||
|
||||
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 org.junit.jupiter.api.BeforeEach;
|
||||
@@ -27,7 +33,7 @@ import org.springframework.boot.actuate.endpoint.OperationType;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.MissingParametersException;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.ParameterValueMapper;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -52,7 +58,7 @@ class ReflectiveOperationInvokerTests {
|
||||
void setup() {
|
||||
this.target = new Example();
|
||||
this.operationMethod = new OperationMethod(ReflectionUtils.findMethod(Example.class, "reverse",
|
||||
ApiVersion.class, SecurityContext.class, String.class), OperationType.READ);
|
||||
ApiVersion.class, SecurityContext.class, String.class), OperationType.READ, this::isOptional);
|
||||
this.parameterValueMapper = (parameter, value) -> (value != null) ? value.toString() : null;
|
||||
}
|
||||
|
||||
@@ -87,7 +93,7 @@ class ReflectiveOperationInvokerTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void invokeWhenMissingNonNullableArgumentShouldThrowException() {
|
||||
void invokeWhenMissingNonOptionalArgumentShouldThrowException() {
|
||||
ReflectiveOperationInvoker invoker = new ReflectiveOperationInvoker(this.target, this.operationMethod,
|
||||
this.parameterValueMapper);
|
||||
assertThatExceptionOfType(MissingParametersException.class).isThrownBy(() -> invoker
|
||||
@@ -95,9 +101,10 @@ class ReflectiveOperationInvokerTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void invokeWhenMissingNullableArgumentShouldInvoke() {
|
||||
void invokeWhenMissingOptionalArgumentShouldInvoke() {
|
||||
OperationMethod operationMethod = new OperationMethod(ReflectionUtils.findMethod(Example.class,
|
||||
"reverseNullable", 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,
|
||||
this.parameterValueMapper);
|
||||
Object result = invoker
|
||||
@@ -114,6 +121,10 @@ class ReflectiveOperationInvokerTests {
|
||||
assertThat(result).isEqualTo("4321");
|
||||
}
|
||||
|
||||
private boolean isOptional(Parameter parameter) {
|
||||
return MergedAnnotations.from(parameter).isPresent(TestOptional.class);
|
||||
}
|
||||
|
||||
static class Example {
|
||||
|
||||
String reverse(ApiVersion apiVersion, SecurityContext securityContext, String name) {
|
||||
@@ -122,7 +133,7 @@ class ReflectiveOperationInvokerTests {
|
||||
return new StringBuilder(name).reverse().toString();
|
||||
}
|
||||
|
||||
String reverseNullable(ApiVersion apiVersion, SecurityContext securityContext, @Nullable String name) {
|
||||
String reverseOptional(ApiVersion apiVersion, SecurityContext securityContext, @TestOptional String name) {
|
||||
assertThat(apiVersion).isEqualTo(ApiVersion.LATEST);
|
||||
assertThat(securityContext).isNotNull();
|
||||
return new StringBuilder(String.valueOf(name)).reverse().toString();
|
||||
@@ -130,4 +141,11 @@ class ReflectiveOperationInvokerTests {
|
||||
|
||||
}
|
||||
|
||||
@Target({ ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface TestOptional {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2022 the original author or authors.
|
||||
* Copyright 2012-2025 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.
|
||||
@@ -16,6 +16,11 @@
|
||||
|
||||
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.util.function.Function;
|
||||
|
||||
@@ -33,7 +38,7 @@ import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.OperationParameters;
|
||||
import org.springframework.boot.actuate.endpoint.invoke.reflect.OperationMethod;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -158,32 +163,34 @@ class CachingOperationInvokerAdvisorTests {
|
||||
|
||||
private OperationMethod getOperationMethod(String methodName, Class<?>... 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 {
|
||||
|
||||
String get() {
|
||||
return "";
|
||||
}
|
||||
|
||||
String getWithParameters(@Nullable String foo, String bar) {
|
||||
String getWithParameters(@TestOptional String foo, String bar) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String getWithAllOptionalParameters(@Nullable String foo, @Nullable String bar) {
|
||||
String getWithAllOptionalParameters(@TestOptional String foo, @TestOptional String bar) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String getWithSecurityContext(SecurityContext securityContext, @Nullable String bar) {
|
||||
String getWithSecurityContext(SecurityContext securityContext, @TestOptional String bar) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String getWithApiVersion(ApiVersion apiVersion, @Nullable String bar) {
|
||||
String getWithApiVersion(ApiVersion apiVersion, @TestOptional String bar) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String getWithServerNamespace(WebServerNamespace serverNamespace, @Nullable String bar) {
|
||||
String getWithServerNamespace(WebServerNamespace serverNamespace, @TestOptional String bar) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -193,4 +200,11 @@ class CachingOperationInvokerAdvisorTests {
|
||||
|
||||
}
|
||||
|
||||
@Target({ ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface TestOptional {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2024 the original author or authors.
|
||||
* Copyright 2012-2025 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.
|
||||
@@ -34,6 +34,7 @@ import reactor.core.publisher.Mono;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.DeleteOperation;
|
||||
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.Selector;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Selector.Match;
|
||||
@@ -51,7 +52,6 @@ import org.springframework.core.io.ByteArrayResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -982,7 +982,7 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
|
||||
}
|
||||
|
||||
@WriteOperation
|
||||
void write(@Nullable String foo, @Nullable String bar) {
|
||||
void write(@OptionalParameter String foo, @OptionalParameter String bar) {
|
||||
this.endpointDelegate.write(foo, bar);
|
||||
}
|
||||
|
||||
@@ -1167,7 +1167,7 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
|
||||
static class RequiredParametersEndpoint {
|
||||
|
||||
@ReadOperation
|
||||
String read(String foo, @Nullable String bar) {
|
||||
String read(String foo, @OptionalParameter String bar) {
|
||||
return foo;
|
||||
}
|
||||
|
||||
@@ -1177,7 +1177,7 @@ public abstract class AbstractWebEndpointIntegrationTests<T extends Configurable
|
||||
static class PrincipalEndpoint {
|
||||
|
||||
@ReadOperation
|
||||
String read(@Nullable Principal principal) {
|
||||
String read(@OptionalParameter Principal principal) {
|
||||
return (principal != null) ? principal.getName() : "None";
|
||||
}
|
||||
|
||||
|
||||
@@ -106,6 +106,8 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
|
||||
|
||||
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 ENDPOINT_ACCESS_ENUM = "org.springframework.boot.actuate.endpoint.Access";
|
||||
@@ -155,6 +157,10 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
|
||||
return NAME_ANNOTATION;
|
||||
}
|
||||
|
||||
protected String optionalParameterAnnotation() {
|
||||
return OPTIONAL_PARAMETER_ANNOTATION;
|
||||
}
|
||||
|
||||
protected String endpointAccessEnum() {
|
||||
return ENDPOINT_ACCESS_ENUM;
|
||||
}
|
||||
@@ -177,7 +183,7 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
|
||||
this.metadataEnv = new MetadataGenerationEnvironment(env, configurationPropertiesAnnotation(),
|
||||
nestedConfigurationPropertyAnnotation(), deprecatedConfigurationPropertyAnnotation(),
|
||||
constructorBindingAnnotation(), autowiredAnnotation(), defaultValueAnnotation(), endpointAnnotations(),
|
||||
readOperationAnnotation(), nameAnnotation());
|
||||
readOperationAnnotation(), optionalParameterAnnotation(), nameAnnotation());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -355,13 +361,18 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor
|
||||
|
||||
private boolean hasNoOrOptionalParameters(ExecutableElement method) {
|
||||
for (VariableElement parameter : method.getParameters()) {
|
||||
if (!this.metadataEnv.hasNullableAnnotation(parameter)) {
|
||||
if (!isOptionalParameter(parameter)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isOptionalParameter(VariableElement parameter) {
|
||||
return this.metadataEnv.hasNullableAnnotation(parameter)
|
||||
|| this.metadataEnv.hasOptionalParameterAnnotation(parameter);
|
||||
}
|
||||
|
||||
private String getPrefix(AnnotationMirror annotation) {
|
||||
String prefix = this.metadataEnv.getAnnotationElementStringValue(annotation, "prefix");
|
||||
if (prefix != null) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2024 the original author or authors.
|
||||
* Copyright 2012-2025 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.
|
||||
@@ -91,6 +91,8 @@ class MetadataGenerationEnvironment {
|
||||
|
||||
private final String readOperationAnnotation;
|
||||
|
||||
private final String optionalParameterAnnotation;
|
||||
|
||||
private final String nameAnnotation;
|
||||
|
||||
private final String autowiredAnnotation;
|
||||
@@ -98,7 +100,8 @@ class MetadataGenerationEnvironment {
|
||||
MetadataGenerationEnvironment(ProcessingEnvironment environment, String configurationPropertiesAnnotation,
|
||||
String nestedConfigurationPropertyAnnotation, String deprecatedConfigurationPropertyAnnotation,
|
||||
String constructorBindingAnnotation, String autowiredAnnotation, String defaultValueAnnotation,
|
||||
Set<String> endpointAnnotations, String readOperationAnnotation, String nameAnnotation) {
|
||||
Set<String> endpointAnnotations, String readOperationAnnotation, String optionalParameterAnnotation,
|
||||
String nameAnnotation) {
|
||||
this.typeUtils = new TypeUtils(environment);
|
||||
this.elements = environment.getElementUtils();
|
||||
this.messager = environment.getMessager();
|
||||
@@ -111,6 +114,7 @@ class MetadataGenerationEnvironment {
|
||||
this.defaultValueAnnotation = defaultValueAnnotation;
|
||||
this.endpointAnnotations = endpointAnnotations;
|
||||
this.readOperationAnnotation = readOperationAnnotation;
|
||||
this.optionalParameterAnnotation = optionalParameterAnnotation;
|
||||
this.nameAnnotation = nameAnnotation;
|
||||
}
|
||||
|
||||
@@ -337,6 +341,10 @@ class MetadataGenerationEnvironment {
|
||||
return getAnnotation(element, NULLABLE_ANNOTATION) != null;
|
||||
}
|
||||
|
||||
boolean hasOptionalParameterAnnotation(Element element) {
|
||||
return getAnnotation(element, this.optionalParameterAnnotation) != null;
|
||||
}
|
||||
|
||||
private boolean isElementDeprecated(Element element) {
|
||||
return hasAnnotation(element, "java.lang.Deprecated")
|
||||
|| hasAnnotation(element, this.deprecatedConfigurationPropertyAnnotation);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2024 the original author or authors.
|
||||
* Copyright 2012-2025 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.
|
||||
@@ -171,7 +171,7 @@ class EndpointMetadataGenerationTests extends AbstractMetadataGenerationTests {
|
||||
assertThat(metadata).has(access("incremental", Access.UNRESTRICTED));
|
||||
assertThat(metadata).has(cacheTtl("incremental"));
|
||||
assertThat(metadata.getItems()).hasSize(4);
|
||||
project.replaceText(IncrementalEndpoint.class, "@Nullable String param", "String param");
|
||||
project.replaceText(IncrementalEndpoint.class, "@OptionalParameter String param", "String param");
|
||||
metadata = project.compile();
|
||||
assertThat(metadata)
|
||||
.has(Metadata.withGroup("management.endpoint.incremental").fromSource(IncrementalEndpoint.class));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2022 the original author or authors.
|
||||
* Copyright 2012-2025 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.
|
||||
@@ -48,6 +48,7 @@ class MetadataGenerationEnvironmentFactory implements Function<ProcessingEnviron
|
||||
TestConfigurationMetadataAnnotationProcessor.AUTOWIRED_ANNOTATION,
|
||||
TestConfigurationMetadataAnnotationProcessor.DEFAULT_VALUE_ANNOTATION, endpointAnnotations,
|
||||
TestConfigurationMetadataAnnotationProcessor.READ_OPERATION_ANNOTATION,
|
||||
TestConfigurationMetadataAnnotationProcessor.OPTIONAL_PARAMETER_ANNOTATION,
|
||||
TestConfigurationMetadataAnnotationProcessor.NAME_ANNOTATION);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2024 the original author or authors.
|
||||
* Copyright 2012-2025 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.
|
||||
@@ -72,6 +72,8 @@ public class TestConfigurationMetadataAnnotationProcessor extends ConfigurationM
|
||||
|
||||
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 ENDPOINT_ACCESS_ENUM = "org.springframework.boot.configurationsample.Access";
|
||||
@@ -120,6 +122,11 @@ public class TestConfigurationMetadataAnnotationProcessor extends ConfigurationM
|
||||
return READ_OPERATION_ANNOTATION;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String optionalParameterAnnotation() {
|
||||
return OPTIONAL_PARAMETER_ANNOTATION;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String nameAnnotation() {
|
||||
return NAME_ANNOTATION;
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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 {
|
||||
|
||||
}
|
||||
@@ -16,9 +16,9 @@
|
||||
|
||||
package org.springframework.boot.configurationsample.endpoint;
|
||||
|
||||
import org.springframework.boot.configurationsample.OptionalParameter;
|
||||
import org.springframework.boot.configurationsample.ReadOperation;
|
||||
import org.springframework.boot.configurationsample.WebEndpoint;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* A meta-annotated endpoint. Also with a package private read operation that has an
|
||||
@@ -31,7 +31,7 @@ import org.springframework.lang.Nullable;
|
||||
public class SpecificEndpoint {
|
||||
|
||||
@ReadOperation
|
||||
String invoke(@Nullable String param) {
|
||||
String invoke(@OptionalParameter String param) {
|
||||
return "test";
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2025 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.
|
||||
@@ -17,8 +17,8 @@
|
||||
package org.springframework.boot.configurationsample.endpoint.incremental;
|
||||
|
||||
import org.springframework.boot.configurationsample.Endpoint;
|
||||
import org.springframework.boot.configurationsample.OptionalParameter;
|
||||
import org.springframework.boot.configurationsample.ReadOperation;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* An endpoint that is enabled by default.
|
||||
@@ -29,7 +29,7 @@ import org.springframework.lang.Nullable;
|
||||
public class IncrementalEndpoint {
|
||||
|
||||
@ReadOperation
|
||||
public String invoke(@Nullable String param) {
|
||||
public String invoke(@OptionalParameter String param) {
|
||||
return "test";
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user