Use ParameterNameDiscoverer to detect operation's parameter names

Closes gh-10117
This commit is contained in:
Stephane Nicoll 2017-10-18 16:08:10 +02:00
parent 56afc25304
commit 00e0d61ee4
4 changed files with 113 additions and 21 deletions

View File

@ -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);
}
}
}

View File

@ -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());
}

View File

@ -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;

View File

@ -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);
}