From 00e0d61ee4cc36fb467fb3ce22cef74415eb4936 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 18 Oct 2017 16:08:10 +0200 Subject: [PATCH] Use ParameterNameDiscoverer to detect operation's parameter names Closes gh-10117 --- .../actuate/endpoint/ParameterNameMapper.java | 71 +++++++++++++++++++ .../endpoint/ReflectiveOperationInvoker.java | 35 +++++---- .../JmxAnnotationEndpointDiscoverer.java | 20 ++++-- .../WebAnnotationEndpointDiscoverer.java | 8 ++- 4 files changed, 113 insertions(+), 21 deletions(-) create mode 100644 spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ParameterNameMapper.java diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ParameterNameMapper.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ParameterNameMapper.java new file mode 100644 index 0000000000..f0e84cd38a --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ParameterNameMapper.java @@ -0,0 +1,71 @@ +/* + * Copyright 2012-2017 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 + * + * http://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; + +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Function; + +import org.springframework.core.DefaultParameterNameDiscoverer; +import org.springframework.core.ParameterNameDiscoverer; + +/** + * Map method's parameter by name. + * + * @author Stephane Nicoll + * @since 2.0.0 + * @see ParameterNameDiscoverer + */ +public class ParameterNameMapper implements Function> { + + private final ParameterNameDiscoverer parameterNameDiscoverer; + + public ParameterNameMapper(ParameterNameDiscoverer parameterNameDiscoverer) { + this.parameterNameDiscoverer = parameterNameDiscoverer; + } + + public ParameterNameMapper() { + this(new DefaultParameterNameDiscoverer()); + } + + /** + * Map the {@link Parameter parameters} of the specified {@link Method} by parameter + * name. + * @param method the method to handle + * @return the parameters of the {@code method}, mapped by parameter name + */ + @Override + public Map apply(Method method) { + String[] parameterNames = this.parameterNameDiscoverer.getParameterNames( + method); + Map parameters = new LinkedHashMap<>(); + if (parameterNames != null) { + for (int i = 0; i < parameterNames.length; i++) { + parameters.put(parameterNames[i], method.getParameters()[i]); + } + return parameters; + } + else { + throw new IllegalStateException("Failed to extract parameter names for " + + method); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ReflectiveOperationInvoker.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ReflectiveOperationInvoker.java index e0547cb815..58887b6b19 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ReflectiveOperationInvoker.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/ReflectiveOperationInvoker.java @@ -20,8 +20,8 @@ import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.Map; import java.util.Set; +import java.util.function.Function; import java.util.stream.Collectors; -import java.util.stream.Stream; import org.springframework.lang.Nullable; import org.springframework.util.ReflectionUtils; @@ -30,12 +30,15 @@ import org.springframework.util.ReflectionUtils; * An {@code OperationInvoker} that invokes an operation using reflection. * * @author Andy Wilkinson + * @author Stephane Nicoll * @since 2.0.0 */ public class ReflectiveOperationInvoker implements OperationInvoker { private final OperationParameterMapper parameterMapper; + private final Function> parameterNameMapper; + private final Object target; private final Method method; @@ -49,8 +52,10 @@ public class ReflectiveOperationInvoker implements OperationInvoker { * @param method the method to call */ public ReflectiveOperationInvoker(OperationParameterMapper parameterMapper, + Function> parameterNameMapper, Object target, Method method) { this.parameterMapper = parameterMapper; + this.parameterNameMapper = parameterNameMapper; this.target = target; ReflectionUtils.makeAccessible(method); this.method = method; @@ -58,22 +63,25 @@ public class ReflectiveOperationInvoker implements OperationInvoker { @Override public Object invoke(Map arguments) { - validateRequiredParameters(arguments); + Map parameters = this.parameterNameMapper.apply(this.method); + validateRequiredParameters(parameters, arguments); return ReflectionUtils.invokeMethod(this.method, this.target, - resolveArguments(arguments)); + resolveArguments(parameters, arguments)); } - private void validateRequiredParameters(Map arguments) { - Set missingParameters = Stream.of(this.method.getParameters()) - .filter((p) -> isMissing(p, arguments)).map(Parameter::getName) + private void validateRequiredParameters(Map parameters, + Map arguments) { + Set missingParameters = parameters.keySet().stream() + .filter((n) -> isMissing(n, parameters.get(n), arguments)) .collect(Collectors.toSet()); if (!missingParameters.isEmpty()) { throw new ParametersMissingException(missingParameters); } } - private boolean isMissing(Parameter parameter, Map arguments) { - Object resolved = arguments.get(parameter.getName()); + private boolean isMissing(String name, Parameter parameter, + Map arguments) { + Object resolved = arguments.get(name); return (resolved == null && !isExplicitNullable(parameter)); } @@ -81,15 +89,16 @@ public class ReflectiveOperationInvoker implements OperationInvoker { return (parameter.getAnnotationsByType(Nullable.class).length != 0); } - private Object[] resolveArguments(Map arguments) { - return Stream.of(this.method.getParameters()) - .map((parameter) -> resolveArgument(parameter, arguments)) + private Object[] resolveArguments(Map parameters, + Map arguments) { + return parameters.keySet().stream() + .map((name) -> resolveArgument(name, parameters.get(name), arguments)) .collect(Collectors.collectingAndThen(Collectors.toList(), (list) -> list.toArray(new Object[list.size()]))); } - private Object resolveArgument(Parameter parameter, Map arguments) { - Object resolved = arguments.get(parameter.getName()); + private Object resolveArgument(String name, Parameter parameter, Map arguments) { + Object resolved = arguments.get(name); return this.parameterMapper.mapParameter(resolved, parameter.getType()); } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxAnnotationEndpointDiscoverer.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxAnnotationEndpointDiscoverer.java index f4b7e31ef2..7ed2bd8382 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxAnnotationEndpointDiscoverer.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/jmx/annotation/JmxAnnotationEndpointDiscoverer.java @@ -23,11 +23,14 @@ import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.Map; +import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; import org.springframework.boot.actuate.endpoint.EndpointExposure; import org.springframework.boot.actuate.endpoint.EndpointInfo; +import org.springframework.boot.actuate.endpoint.ParameterNameMapper; import org.springframework.boot.actuate.endpoint.OperationInvoker; import org.springframework.boot.actuate.endpoint.OperationParameterMapper; import org.springframework.boot.actuate.endpoint.OperationType; @@ -108,6 +111,8 @@ public class JmxAnnotationEndpointDiscoverer private final OperationParameterMapper parameterMapper; + private final Function> parameterNameMapper = new ParameterNameMapper(); + JmxEndpointOperationFactory(OperationParameterMapper parameterMapper) { this.parameterMapper = parameterMapper; } @@ -122,7 +127,7 @@ public class JmxAnnotationEndpointDiscoverer () -> "Invoke " + operationName + " for endpoint " + endpointId); List parameters = getParameters(method); OperationInvoker invoker = new ReflectiveOperationInvoker( - this.parameterMapper, target, method); + this.parameterMapper, this.parameterNameMapper, target, method); if (timeToLive > 0) { invoker = new CachingOperationInvoker(invoker, timeToLive); } @@ -148,17 +153,18 @@ public class JmxAnnotationEndpointDiscoverer ManagedOperationParameter[] managedOperationParameters = jmxAttributeSource .getManagedOperationParameters(method); if (managedOperationParameters.length == 0) { - return getParametersInfo(methodParameters); + return getParametersInfo(method); } return getParametersInfo(methodParameters, managedOperationParameters); } - private List getParametersInfo( - Parameter[] methodParameters) { + private List getParametersInfo(Method method) { + Map methodParameters = this.parameterNameMapper + .apply(method); List parameters = new ArrayList<>(); - for (Parameter methodParameter : methodParameters) { - String name = methodParameter.getName(); - Class type = mapParameterType(methodParameter.getType()); + for (Map.Entry entry : methodParameters.entrySet()) { + String name = entry.getKey(); + Class type = mapParameterType(entry.getValue().getType()); parameters.add(new JmxEndpointOperationParameterInfo(name, type, null)); } return parameters; diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/WebAnnotationEndpointDiscoverer.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/WebAnnotationEndpointDiscoverer.java index ae949d753c..3220c893ab 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/WebAnnotationEndpointDiscoverer.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/annotation/WebAnnotationEndpointDiscoverer.java @@ -17,11 +17,14 @@ package org.springframework.boot.actuate.endpoint.web.annotation; import java.lang.reflect.Method; +import java.lang.reflect.Parameter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -29,6 +32,7 @@ import org.reactivestreams.Publisher; import org.springframework.boot.actuate.endpoint.EndpointExposure; import org.springframework.boot.actuate.endpoint.EndpointInfo; +import org.springframework.boot.actuate.endpoint.ParameterNameMapper; import org.springframework.boot.actuate.endpoint.OperationInvoker; import org.springframework.boot.actuate.endpoint.OperationParameterMapper; import org.springframework.boot.actuate.endpoint.OperationType; @@ -127,6 +131,8 @@ public class WebAnnotationEndpointDiscoverer extends private final EndpointPathResolver endpointPathResolver; + private final Function> parameterNameMapper = new ParameterNameMapper(); + private WebEndpointOperationFactory(OperationParameterMapper parameterMapper, EndpointMediaTypes endpointMediaTypes, EndpointPathResolver endpointPathResolver) { @@ -146,7 +152,7 @@ public class WebAnnotationEndpointDiscoverer extends determineProducedMediaTypes( operationAttributes.getStringArray("produces"), method)); OperationInvoker invoker = new ReflectiveOperationInvoker( - this.parameterMapper, target, method); + this.parameterMapper, this.parameterNameMapper, target, method); if (timeToLive > 0) { invoker = new CachingOperationInvoker(invoker, timeToLive); }