Commit 00e0d61e authored by Stephane Nicoll's avatar Stephane Nicoll

Use ParameterNameDiscoverer to detect operation's parameter names

Closes gh-10117
parent 56afc253
/*
* 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; ...@@ -20,8 +20,8 @@ import java.lang.reflect.Method;
import java.lang.reflect.Parameter; import java.lang.reflect.Parameter;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
...@@ -30,12 +30,15 @@ import org.springframework.util.ReflectionUtils; ...@@ -30,12 +30,15 @@ import org.springframework.util.ReflectionUtils;
* An {@code OperationInvoker} that invokes an operation using reflection. * An {@code OperationInvoker} that invokes an operation using reflection.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Stephane Nicoll
* @since 2.0.0 * @since 2.0.0
*/ */
public class ReflectiveOperationInvoker implements OperationInvoker { public class ReflectiveOperationInvoker implements OperationInvoker {
private final OperationParameterMapper parameterMapper; private final OperationParameterMapper parameterMapper;
private final Function<Method, Map<String, Parameter>> parameterNameMapper;
private final Object target; private final Object target;
private final Method method; private final Method method;
...@@ -49,8 +52,10 @@ public class ReflectiveOperationInvoker implements OperationInvoker { ...@@ -49,8 +52,10 @@ public class ReflectiveOperationInvoker implements OperationInvoker {
* @param method the method to call * @param method the method to call
*/ */
public ReflectiveOperationInvoker(OperationParameterMapper parameterMapper, public ReflectiveOperationInvoker(OperationParameterMapper parameterMapper,
Function<Method, Map<String, Parameter>> parameterNameMapper,
Object target, Method method) { Object target, Method method) {
this.parameterMapper = parameterMapper; this.parameterMapper = parameterMapper;
this.parameterNameMapper = parameterNameMapper;
this.target = target; this.target = target;
ReflectionUtils.makeAccessible(method); ReflectionUtils.makeAccessible(method);
this.method = method; this.method = method;
...@@ -58,22 +63,25 @@ public class ReflectiveOperationInvoker implements OperationInvoker { ...@@ -58,22 +63,25 @@ public class ReflectiveOperationInvoker implements OperationInvoker {
@Override @Override
public Object invoke(Map<String, Object> arguments) { 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, return ReflectionUtils.invokeMethod(this.method, this.target,
resolveArguments(arguments)); resolveArguments(parameters, arguments));
} }
private void validateRequiredParameters(Map<String, Object> arguments) { private void validateRequiredParameters(Map<String, Parameter> parameters,
Set<String> missingParameters = Stream.of(this.method.getParameters()) Map<String, Object> arguments) {
.filter((p) -> isMissing(p, arguments)).map(Parameter::getName) Set<String> missingParameters = parameters.keySet().stream()
.filter((n) -> isMissing(n, parameters.get(n), arguments))
.collect(Collectors.toSet()); .collect(Collectors.toSet());
if (!missingParameters.isEmpty()) { if (!missingParameters.isEmpty()) {
throw new ParametersMissingException(missingParameters); throw new ParametersMissingException(missingParameters);
} }
} }
private boolean isMissing(Parameter parameter, Map<String, Object> arguments) { private boolean isMissing(String name, Parameter parameter,
Object resolved = arguments.get(parameter.getName()); Map<String, Object> arguments) {
Object resolved = arguments.get(name);
return (resolved == null && !isExplicitNullable(parameter)); return (resolved == null && !isExplicitNullable(parameter));
} }
...@@ -81,15 +89,16 @@ public class ReflectiveOperationInvoker implements OperationInvoker { ...@@ -81,15 +89,16 @@ public class ReflectiveOperationInvoker implements OperationInvoker {
return (parameter.getAnnotationsByType(Nullable.class).length != 0); return (parameter.getAnnotationsByType(Nullable.class).length != 0);
} }
private Object[] resolveArguments(Map<String, Object> arguments) { private Object[] resolveArguments(Map<String, Parameter> parameters,
return Stream.of(this.method.getParameters()) Map<String, Object> arguments) {
.map((parameter) -> resolveArgument(parameter, arguments)) return parameters.keySet().stream()
.map((name) -> resolveArgument(name, parameters.get(name), arguments))
.collect(Collectors.collectingAndThen(Collectors.toList(), .collect(Collectors.collectingAndThen(Collectors.toList(),
(list) -> list.toArray(new Object[list.size()]))); (list) -> list.toArray(new Object[list.size()])));
} }
private Object resolveArgument(Parameter parameter, Map<String, Object> arguments) { private Object resolveArgument(String name, Parameter parameter, Map<String, Object> arguments) {
Object resolved = arguments.get(parameter.getName()); Object resolved = arguments.get(name);
return this.parameterMapper.mapParameter(resolved, parameter.getType()); return this.parameterMapper.mapParameter(resolved, parameter.getType());
} }
......
...@@ -23,11 +23,14 @@ import java.util.Collection; ...@@ -23,11 +23,14 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.springframework.boot.actuate.endpoint.EndpointExposure; import org.springframework.boot.actuate.endpoint.EndpointExposure;
import org.springframework.boot.actuate.endpoint.EndpointInfo; 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.OperationInvoker;
import org.springframework.boot.actuate.endpoint.OperationParameterMapper; import org.springframework.boot.actuate.endpoint.OperationParameterMapper;
import org.springframework.boot.actuate.endpoint.OperationType; import org.springframework.boot.actuate.endpoint.OperationType;
...@@ -108,6 +111,8 @@ public class JmxAnnotationEndpointDiscoverer ...@@ -108,6 +111,8 @@ public class JmxAnnotationEndpointDiscoverer
private final OperationParameterMapper parameterMapper; private final OperationParameterMapper parameterMapper;
private final Function<Method, Map<String, Parameter>> parameterNameMapper = new ParameterNameMapper();
JmxEndpointOperationFactory(OperationParameterMapper parameterMapper) { JmxEndpointOperationFactory(OperationParameterMapper parameterMapper) {
this.parameterMapper = parameterMapper; this.parameterMapper = parameterMapper;
} }
...@@ -122,7 +127,7 @@ public class JmxAnnotationEndpointDiscoverer ...@@ -122,7 +127,7 @@ public class JmxAnnotationEndpointDiscoverer
() -> "Invoke " + operationName + " for endpoint " + endpointId); () -> "Invoke " + operationName + " for endpoint " + endpointId);
List<JmxEndpointOperationParameterInfo> parameters = getParameters(method); List<JmxEndpointOperationParameterInfo> parameters = getParameters(method);
OperationInvoker invoker = new ReflectiveOperationInvoker( OperationInvoker invoker = new ReflectiveOperationInvoker(
this.parameterMapper, target, method); this.parameterMapper, this.parameterNameMapper, target, method);
if (timeToLive > 0) { if (timeToLive > 0) {
invoker = new CachingOperationInvoker(invoker, timeToLive); invoker = new CachingOperationInvoker(invoker, timeToLive);
} }
...@@ -148,17 +153,18 @@ public class JmxAnnotationEndpointDiscoverer ...@@ -148,17 +153,18 @@ public class JmxAnnotationEndpointDiscoverer
ManagedOperationParameter[] managedOperationParameters = jmxAttributeSource ManagedOperationParameter[] managedOperationParameters = jmxAttributeSource
.getManagedOperationParameters(method); .getManagedOperationParameters(method);
if (managedOperationParameters.length == 0) { if (managedOperationParameters.length == 0) {
return getParametersInfo(methodParameters); return getParametersInfo(method);
} }
return getParametersInfo(methodParameters, managedOperationParameters); return getParametersInfo(methodParameters, managedOperationParameters);
} }
private List<JmxEndpointOperationParameterInfo> getParametersInfo( private List<JmxEndpointOperationParameterInfo> getParametersInfo(Method method) {
Parameter[] methodParameters) { Map<String, Parameter> methodParameters = this.parameterNameMapper
.apply(method);
List<JmxEndpointOperationParameterInfo> parameters = new ArrayList<>(); List<JmxEndpointOperationParameterInfo> parameters = new ArrayList<>();
for (Parameter methodParameter : methodParameters) { for (Map.Entry<String, Parameter> entry : methodParameters.entrySet()) {
String name = methodParameter.getName(); String name = entry.getKey();
Class<?> type = mapParameterType(methodParameter.getType()); Class<?> type = mapParameterType(entry.getValue().getType());
parameters.add(new JmxEndpointOperationParameterInfo(name, type, null)); parameters.add(new JmxEndpointOperationParameterInfo(name, type, null));
} }
return parameters; return parameters;
......
...@@ -17,11 +17,14 @@ ...@@ -17,11 +17,14 @@
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.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
...@@ -29,6 +32,7 @@ import org.reactivestreams.Publisher; ...@@ -29,6 +32,7 @@ import org.reactivestreams.Publisher;
import org.springframework.boot.actuate.endpoint.EndpointExposure; import org.springframework.boot.actuate.endpoint.EndpointExposure;
import org.springframework.boot.actuate.endpoint.EndpointInfo; 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.OperationInvoker;
import org.springframework.boot.actuate.endpoint.OperationParameterMapper; import org.springframework.boot.actuate.endpoint.OperationParameterMapper;
import org.springframework.boot.actuate.endpoint.OperationType; import org.springframework.boot.actuate.endpoint.OperationType;
...@@ -127,6 +131,8 @@ public class WebAnnotationEndpointDiscoverer extends ...@@ -127,6 +131,8 @@ public class WebAnnotationEndpointDiscoverer extends
private final EndpointPathResolver endpointPathResolver; private final EndpointPathResolver endpointPathResolver;
private final Function<Method, Map<String, Parameter>> parameterNameMapper = new ParameterNameMapper();
private WebEndpointOperationFactory(OperationParameterMapper parameterMapper, private WebEndpointOperationFactory(OperationParameterMapper parameterMapper,
EndpointMediaTypes endpointMediaTypes, EndpointMediaTypes endpointMediaTypes,
EndpointPathResolver endpointPathResolver) { EndpointPathResolver endpointPathResolver) {
...@@ -146,7 +152,7 @@ public class WebAnnotationEndpointDiscoverer extends ...@@ -146,7 +152,7 @@ public class WebAnnotationEndpointDiscoverer extends
determineProducedMediaTypes( determineProducedMediaTypes(
operationAttributes.getStringArray("produces"), method)); operationAttributes.getStringArray("produces"), method));
OperationInvoker invoker = new ReflectiveOperationInvoker( OperationInvoker invoker = new ReflectiveOperationInvoker(
this.parameterMapper, target, method); this.parameterMapper, this.parameterNameMapper, target, method);
if (timeToLive > 0) { if (timeToLive > 0) {
invoker = new CachingOperationInvoker(invoker, timeToLive); invoker = new CachingOperationInvoker(invoker, timeToLive);
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment