/*
* Copyright 2002-2015 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.web.reactive.result.method;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import reactor.core.publisher.Mono;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.ui.ModelMap;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.reactive.HandlerResult;
import org.springframework.web.server.ServerWebExchange;
/**
* @author Rossen Stoyanchev
*/
public class InvocableHandlerMethod extends HandlerMethod {
private static final Mono<Object[]> NO_ARGS = Mono.just(new Object[0]);
private final static Object NO_VALUE = new Object();
private List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
public InvocableHandlerMethod(HandlerMethod handlerMethod) {
super(handlerMethod);
}
public InvocableHandlerMethod(Object bean, Method method) {
super(bean, method);
}
public void setHandlerMethodArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
this.resolvers.clear();
this.resolvers.addAll(resolvers);
}
@Override
protected Method getBridgedMethod() {
return super.getBridgedMethod();
}
/**
* Invoke the method and return a Publisher for the return value.
* @param exchange the current exchange
* @param model the model for request handling
* @param providedArgs optional list of argument values to check by type
* (via {@code instanceof}) for resolving method arguments.
* @return Publisher that produces a single HandlerResult or an error signal;
* never throws an exception
*/
public Mono<HandlerResult> invokeForRequest(ServerWebExchange exchange, ModelMap model,
Object... providedArgs) {
return resolveArguments(exchange, model, providedArgs).then(args -> {
try {
Object value = doInvoke(args);
HandlerResult handlerResult = new HandlerResult(this, value, getReturnType(), model);
return Mono.just(handlerResult);
}
catch (InvocationTargetException ex) {
return Mono.error(ex.getTargetException());
}
catch (Throwable ex) {
String s = getInvocationErrorMessage(args);
return Mono.error(new IllegalStateException(s));
}
});
}
private Mono<Object[]> resolveArguments(ServerWebExchange exchange, ModelMap model, Object... providedArgs) {
if (ObjectUtils.isEmpty(getMethodParameters())) {
return NO_ARGS;
}
try {
List<Mono<Object>> monos = Stream.of(getMethodParameters())
.map(param -> {
param.initParameterNameDiscovery(this.parameterNameDiscoverer);
GenericTypeResolver.resolveParameterType(param, getBean().getClass());
if (!ObjectUtils.isEmpty(providedArgs)) {
for (Object providedArg : providedArgs) {
if (param.getParameterType().isInstance(providedArg)) {
return Mono.just(providedArg).log("reactor.resolved");
}
}
}
HandlerMethodArgumentResolver resolver = this.resolvers.stream()
.filter(r -> r.supportsParameter(param))
.findFirst()
.orElseThrow(() -> getArgError("No resolver for ", param, null));
try {
return resolver.resolveArgument(param, model, exchange)
.defaultIfEmpty(NO_VALUE)
.otherwise(ex -> Mono.error(getArgError("Error resolving ", param, ex)))
.log("reactor.unresolved");
}
catch (Exception ex) {
throw getArgError("Error resolving ", param, ex);
}
})
.collect(Collectors.toList());
return Mono.when(monos).log("reactor.unresolved").map(args ->
Stream.of(args).map(o -> o != NO_VALUE ? o : null).toArray());
}
catch (Throwable ex) {
return Mono.error(ex);
}
}
private IllegalStateException getArgError(String message, MethodParameter param, Throwable cause) {
return new IllegalStateException(message +
"argument [" + param.getParameterIndex() + "] " +
"of type [" + param.getParameterType().getName() + "] " +
"on method [" + getBridgedMethod().toGenericString() + "]", cause);
}
private Object doInvoke(Object[] args) throws Exception {
if (logger.isTraceEnabled()) {
String target = getBeanType().getSimpleName() + "." + getMethod().getName();
logger.trace("Invoking [" + target + "] method with arguments " + Arrays.toString(args));
}
ReflectionUtils.makeAccessible(getBridgedMethod());
Object returnValue = getBridgedMethod().invoke(getBean(), args);
if (logger.isTraceEnabled()) {
String target = getBeanType().getSimpleName() + "." + getMethod().getName();
logger.trace("Method [" + target + "] returned [" + returnValue + "]");
}
return returnValue;
}
private String getInvocationErrorMessage(Object[] args) {
String argumentDetails = IntStream.range(0, args.length)
.mapToObj(i -> (args[i] != null ?
"[" + i + "][type=" + args[i].getClass().getName() + "][value=" + args[i] + "]" :
"[" + i + "][null]"))
.collect(Collectors.joining(",", " ", " "));
return "Failed to invoke controller with resolved arguments:" + argumentDetails +
"on method [" + getBridgedMethod().toGenericString() + "]";
}
}