Use OperationParameter consistently

Closes gh-31240
This commit is contained in:
Moritz Halbritter 2023-01-16 11:17:13 +01:00
parent a2f9e30e77
commit 96175a8e50
7 changed files with 77 additions and 29 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,6 +16,7 @@
package org.springframework.boot.actuate.autoconfigure.endpoint; package org.springframework.boot.actuate.autoconfigure.endpoint;
import java.lang.annotation.Annotation;
import java.util.Collections; import java.util.Collections;
import java.util.Set; import java.util.Set;
@ -200,6 +201,11 @@ class EndpointAutoConfigurationTests {
return false; return false;
} }
@Override
public <T extends Annotation> T getAnnotation(Class<T> annotation) {
return null;
}
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,10 +16,13 @@
package org.springframework.boot.actuate.endpoint.invoke; package org.springframework.boot.actuate.endpoint.invoke;
import java.lang.annotation.Annotation;
/** /**
* A single operation parameter. * A single operation parameter.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Moritz Halbritter
* @since 2.0.0 * @since 2.0.0
*/ */
public interface OperationParameter { public interface OperationParameter {
@ -42,4 +45,13 @@ public interface OperationParameter {
*/ */
boolean isMandatory(); boolean isMandatory();
/**
* Returns this element's annotation for the specified type if such an annotation is
* present, else null.
* @param annotation class of the annotation
* @return annotation value
* @param <T> type of the annotation
*/
<T extends Annotation> T getAnnotation(Class<T> annotation);
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2021 the original author or authors. * Copyright 2012-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,6 +16,7 @@
package org.springframework.boot.actuate.endpoint.invoke.reflect; package org.springframework.boot.actuate.endpoint.invoke.reflect;
import java.lang.annotation.Annotation;
import java.lang.reflect.Parameter; import java.lang.reflect.Parameter;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
@ -32,6 +33,7 @@ import org.springframework.util.ObjectUtils;
* {@link OperationParameter} created from an {@link OperationMethod}. * {@link OperationParameter} created from an {@link OperationMethod}.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Moritz Halbritter
*/ */
class OperationMethodParameter implements OperationParameter { class OperationMethodParameter implements OperationParameter {
@ -69,6 +71,11 @@ class OperationMethodParameter implements OperationParameter {
return (jsr305Present) ? new Jsr305().isMandatory(this.parameter) : true; return (jsr305Present) ? new Jsr305().isMandatory(this.parameter) : true;
} }
@Override
public <T extends Annotation> T getAnnotation(Class<T> annotation) {
return this.parameter.getAnnotation(annotation);
}
@Override @Override
public String toString() { public String toString() {
return this.name + " of type " + this.parameter.getType().getName(); return this.name + " of type " + this.parameter.getType().getName();

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,10 +16,7 @@
package org.springframework.boot.actuate.endpoint.web.annotation; package org.springframework.boot.actuate.endpoint.web.annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.reactivestreams.Publisher; import org.reactivestreams.Publisher;
@ -28,6 +25,8 @@ import org.springframework.boot.actuate.endpoint.annotation.AbstractDiscoveredOp
import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod; import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod;
import org.springframework.boot.actuate.endpoint.annotation.Selector; import org.springframework.boot.actuate.endpoint.annotation.Selector;
import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker; import org.springframework.boot.actuate.endpoint.invoke.OperationInvoker;
import org.springframework.boot.actuate.endpoint.invoke.OperationParameter;
import org.springframework.boot.actuate.endpoint.invoke.reflect.OperationMethod;
import org.springframework.boot.actuate.endpoint.web.WebOperation; import org.springframework.boot.actuate.endpoint.web.WebOperation;
import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate; import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate;
import org.springframework.core.style.ToStringCreator; import org.springframework.core.style.ToStringCreator;
@ -39,6 +38,7 @@ import org.springframework.util.ClassUtils;
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Phillip Webb * @author Phillip Webb
* @author Moritz Halbritter
*/ */
class DiscoveredWebOperation extends AbstractDiscoveredOperation implements WebOperation { class DiscoveredWebOperation extends AbstractDiscoveredOperation implements WebOperation {
@ -54,27 +54,26 @@ class DiscoveredWebOperation extends AbstractDiscoveredOperation implements WebO
DiscoveredWebOperation(EndpointId endpointId, DiscoveredOperationMethod operationMethod, OperationInvoker invoker, DiscoveredWebOperation(EndpointId endpointId, DiscoveredOperationMethod operationMethod, OperationInvoker invoker,
WebOperationRequestPredicate requestPredicate) { WebOperationRequestPredicate requestPredicate) {
super(operationMethod, invoker); super(operationMethod, invoker);
Method method = operationMethod.getMethod(); this.id = getId(endpointId, operationMethod);
this.id = getId(endpointId, method); this.blocking = getBlocking(operationMethod);
this.blocking = getBlocking(method);
this.requestPredicate = requestPredicate; this.requestPredicate = requestPredicate;
} }
private String getId(EndpointId endpointId, Method method) { private String getId(EndpointId endpointId, OperationMethod method) {
return endpointId + Stream.of(method.getParameters()).filter(this::hasSelector).map(this::dashName) return endpointId + method.getParameters().stream().filter(this::hasSelector).map(this::dashName)
.collect(Collectors.joining()); .collect(Collectors.joining());
} }
private boolean hasSelector(Parameter parameter) { private boolean hasSelector(OperationParameter parameter) {
return parameter.getAnnotation(Selector.class) != null; return parameter.getAnnotation(Selector.class) != null;
} }
private String dashName(Parameter parameter) { private String dashName(OperationParameter parameter) {
return "-" + parameter.getName(); return "-" + parameter.getName();
} }
private boolean getBlocking(Method method) { private boolean getBlocking(OperationMethod method) {
return !REACTIVE_STREAMS_PRESENT || !Publisher.class.isAssignableFrom(method.getReturnType()); return !REACTIVE_STREAMS_PRESENT || !Publisher.class.isAssignableFrom(method.getMethod().getReturnType());
} }
@Override @Override

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,8 +17,6 @@
package org.springframework.boot.actuate.endpoint.web.annotation; package org.springframework.boot.actuate.endpoint.web.annotation;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -27,6 +25,7 @@ import org.springframework.boot.actuate.endpoint.OperationType;
import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod; import org.springframework.boot.actuate.endpoint.annotation.DiscoveredOperationMethod;
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.boot.actuate.endpoint.invoke.OperationParameter;
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes; import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
import org.springframework.boot.actuate.endpoint.web.WebEndpointHttpMethod; import org.springframework.boot.actuate.endpoint.web.WebEndpointHttpMethod;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse; import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
@ -41,6 +40,7 @@ import org.springframework.util.Assert;
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Phillip Webb * @author Phillip Webb
* @author Moritz Halbritter
*/ */
class RequestPredicateFactory { class RequestPredicateFactory {
@ -53,9 +53,9 @@ class RequestPredicateFactory {
WebOperationRequestPredicate getRequestPredicate(String rootPath, DiscoveredOperationMethod operationMethod) { WebOperationRequestPredicate getRequestPredicate(String rootPath, DiscoveredOperationMethod operationMethod) {
Method method = operationMethod.getMethod(); Method method = operationMethod.getMethod();
Parameter[] selectorParameters = Arrays.stream(method.getParameters()).filter(this::hasSelector) OperationParameter[] selectorParameters = operationMethod.getParameters().stream().filter(this::hasSelector)
.toArray(Parameter[]::new); .toArray(OperationParameter[]::new);
Parameter allRemainingPathSegmentsParameter = getAllRemainingPathSegmentsParameter(selectorParameters); OperationParameter allRemainingPathSegmentsParameter = getAllRemainingPathSegmentsParameter(selectorParameters);
String path = getPath(rootPath, selectorParameters, allRemainingPathSegmentsParameter != null); String path = getPath(rootPath, selectorParameters, allRemainingPathSegmentsParameter != null);
WebEndpointHttpMethod httpMethod = determineHttpMethod(operationMethod.getOperationType()); WebEndpointHttpMethod httpMethod = determineHttpMethod(operationMethod.getOperationType());
Collection<String> consumes = getConsumes(httpMethod, method); Collection<String> consumes = getConsumes(httpMethod, method);
@ -63,9 +63,9 @@ class RequestPredicateFactory {
return new WebOperationRequestPredicate(path, httpMethod, consumes, produces); return new WebOperationRequestPredicate(path, httpMethod, consumes, produces);
} }
private Parameter getAllRemainingPathSegmentsParameter(Parameter[] selectorParameters) { private OperationParameter getAllRemainingPathSegmentsParameter(OperationParameter[] selectorParameters) {
Parameter trailingPathsParameter = null; OperationParameter trailingPathsParameter = null;
for (Parameter selectorParameter : selectorParameters) { for (OperationParameter selectorParameter : selectorParameters) {
Selector selector = selectorParameter.getAnnotation(Selector.class); Selector selector = selectorParameter.getAnnotation(Selector.class);
if (selector.match() == Match.ALL_REMAINING) { if (selector.match() == Match.ALL_REMAINING) {
Assert.state(trailingPathsParameter == null, Assert.state(trailingPathsParameter == null,
@ -80,7 +80,8 @@ class RequestPredicateFactory {
return trailingPathsParameter; return trailingPathsParameter;
} }
private String getPath(String rootPath, Parameter[] selectorParameters, boolean matchRemainingPathSegments) { private String getPath(String rootPath, OperationParameter[] selectorParameters,
boolean matchRemainingPathSegments) {
StringBuilder path = new StringBuilder(rootPath); StringBuilder path = new StringBuilder(rootPath);
for (int i = 0; i < selectorParameters.length; i++) { for (int i = 0; i < selectorParameters.length; i++) {
path.append("/{"); path.append("/{");
@ -93,7 +94,7 @@ class RequestPredicateFactory {
return path.toString(); return path.toString();
} }
private boolean hasSelector(Parameter parameter) { private boolean hasSelector(OperationParameter parameter) {
return parameter.getAnnotation(Selector.class) != null; return parameter.getAnnotation(Selector.class) != null;
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2022 the original author or authors. * Copyright 2012-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,6 +16,7 @@
package org.springframework.boot.actuate.endpoint.invoke.convert; package org.springframework.boot.actuate.endpoint.invoke.convert;
import java.lang.annotation.Annotation;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -104,6 +105,11 @@ class ConversionServiceParameterValueMapperTests {
return false; return false;
} }
@Override
public <T extends Annotation> T getAnnotation(Class<T> annotation) {
return null;
}
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2021 the original author or authors. * Copyright 2012-2023 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -26,6 +26,8 @@ import javax.annotation.meta.When;
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.Match;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
@ -35,6 +37,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* Tests for {@link OperationMethodParameter}. * Tests for {@link OperationMethodParameter}.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Moritz Halbritter
*/ */
class OperationMethodParameterTests { class OperationMethodParameterTests {
@ -48,6 +51,8 @@ class OperationMethodParameterTests {
private Method exampleJsr305NonNull = ReflectionUtils.findMethod(getClass(), "exampleJsr305NonNull", String.class, private Method exampleJsr305NonNull = ReflectionUtils.findMethod(getClass(), "exampleJsr305NonNull", String.class,
String.class); String.class);
private 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]);
@ -93,6 +98,15 @@ class OperationMethodParameterTests {
assertThat(parameter.isMandatory()).isTrue(); assertThat(parameter.isMandatory()).isTrue();
} }
@Test
void getAnnotationShouldReturnAnnotation() {
OperationMethodParameter parameter = new OperationMethodParameter("name",
this.exampleAnnotation.getParameters()[0]);
Selector annotation = parameter.getAnnotation(Selector.class);
assertThat(annotation).isNotNull();
assertThat(annotation.match()).isEqualTo(Match.ALL_REMAINING);
}
void example(String one, @Nullable String two) { void example(String one, @Nullable String two) {
} }
@ -105,6 +119,9 @@ class OperationMethodParameterTests {
void exampleJsr305NonNull(String one, @javax.annotation.Nonnull String two) { void exampleJsr305NonNull(String one, @javax.annotation.Nonnull String two) {
} }
void exampleAnnotation(@Selector(match = Match.ALL_REMAINING) String allRemaining) {
}
@TypeQualifier @TypeQualifier
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Nonnull(when = When.MAYBE) @Nonnull(when = When.MAYBE)