Use ParameterNameDiscoverer to detect operation's parameter names
Closes gh-10117
This commit is contained in:
@@ -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<Method, Map<String, Parameter>> {
|
||||
|
||||
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<String, Parameter> apply(Method method) {
|
||||
String[] parameterNames = this.parameterNameDiscoverer.getParameterNames(
|
||||
method);
|
||||
Map<String, Parameter> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Method, Map<String, Parameter>> 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<Method, Map<String, Parameter>> 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<String, Object> arguments) {
|
||||
validateRequiredParameters(arguments);
|
||||
Map<String, Parameter> 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<String, Object> arguments) {
|
||||
Set<String> missingParameters = Stream.of(this.method.getParameters())
|
||||
.filter((p) -> isMissing(p, arguments)).map(Parameter::getName)
|
||||
private void validateRequiredParameters(Map<String, Parameter> parameters,
|
||||
Map<String, Object> arguments) {
|
||||
Set<String> 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<String, Object> arguments) {
|
||||
Object resolved = arguments.get(parameter.getName());
|
||||
private boolean isMissing(String name, Parameter parameter,
|
||||
Map<String, Object> 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<String, Object> arguments) {
|
||||
return Stream.of(this.method.getParameters())
|
||||
.map((parameter) -> resolveArgument(parameter, arguments))
|
||||
private Object[] resolveArguments(Map<String, Parameter> parameters,
|
||||
Map<String, Object> 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<String, Object> arguments) {
|
||||
Object resolved = arguments.get(parameter.getName());
|
||||
private Object resolveArgument(String name, Parameter parameter, Map<String, Object> arguments) {
|
||||
Object resolved = arguments.get(name);
|
||||
return this.parameterMapper.mapParameter(resolved, parameter.getType());
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Method, Map<String, Parameter>> parameterNameMapper = new ParameterNameMapper();
|
||||
|
||||
JmxEndpointOperationFactory(OperationParameterMapper parameterMapper) {
|
||||
this.parameterMapper = parameterMapper;
|
||||
}
|
||||
@@ -122,7 +127,7 @@ public class JmxAnnotationEndpointDiscoverer
|
||||
() -> "Invoke " + operationName + " for endpoint " + endpointId);
|
||||
List<JmxEndpointOperationParameterInfo> 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<JmxEndpointOperationParameterInfo> getParametersInfo(
|
||||
Parameter[] methodParameters) {
|
||||
private List<JmxEndpointOperationParameterInfo> getParametersInfo(Method method) {
|
||||
Map<String, Parameter> methodParameters = this.parameterNameMapper
|
||||
.apply(method);
|
||||
List<JmxEndpointOperationParameterInfo> parameters = new ArrayList<>();
|
||||
for (Parameter methodParameter : methodParameters) {
|
||||
String name = methodParameter.getName();
|
||||
Class<?> type = mapParameterType(methodParameter.getType());
|
||||
for (Map.Entry<String, Parameter> entry : methodParameters.entrySet()) {
|
||||
String name = entry.getKey();
|
||||
Class<?> type = mapParameterType(entry.getValue().getType());
|
||||
parameters.add(new JmxEndpointOperationParameterInfo(name, type, null));
|
||||
}
|
||||
return parameters;
|
||||
|
||||
@@ -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<Method, Map<String, Parameter>> 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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user