001/* 002 * Copyright 2002-2019 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.messaging.handler.invocation.reactive; 018 019import java.lang.reflect.InvocationTargetException; 020import java.lang.reflect.Method; 021import java.lang.reflect.ParameterizedType; 022import java.lang.reflect.Type; 023import java.util.ArrayList; 024import java.util.List; 025import java.util.stream.Stream; 026 027import reactor.core.publisher.Mono; 028 029import org.springframework.core.CoroutinesUtils; 030import org.springframework.core.DefaultParameterNameDiscoverer; 031import org.springframework.core.KotlinDetector; 032import org.springframework.core.MethodParameter; 033import org.springframework.core.ParameterNameDiscoverer; 034import org.springframework.core.ReactiveAdapter; 035import org.springframework.core.ReactiveAdapterRegistry; 036import org.springframework.lang.Nullable; 037import org.springframework.messaging.Message; 038import org.springframework.messaging.handler.HandlerMethod; 039import org.springframework.messaging.handler.invocation.MethodArgumentResolutionException; 040import org.springframework.util.ObjectUtils; 041import org.springframework.util.ReflectionUtils; 042 043/** 044 * Extension of {@link HandlerMethod} that invokes the underlying method with 045 * argument values resolved from the current HTTP request through a list of 046 * {@link HandlerMethodArgumentResolver}. 047 * 048 * @author Rossen Stoyanchev 049 * @since 5.2 050 */ 051public class InvocableHandlerMethod extends HandlerMethod { 052 053 private static final Mono<Object[]> EMPTY_ARGS = Mono.just(new Object[0]); 054 055 private static final Object NO_ARG_VALUE = new Object(); 056 057 058 private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite(); 059 060 private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); 061 062 private ReactiveAdapterRegistry reactiveAdapterRegistry = ReactiveAdapterRegistry.getSharedInstance(); 063 064 065 /** 066 * Create an instance from a {@code HandlerMethod}. 067 */ 068 public InvocableHandlerMethod(HandlerMethod handlerMethod) { 069 super(handlerMethod); 070 } 071 072 /** 073 * Create an instance from a bean instance and a method. 074 */ 075 public InvocableHandlerMethod(Object bean, Method method) { 076 super(bean, method); 077 } 078 079 080 /** 081 * Configure the argument resolvers to use to use for resolving method 082 * argument values against a {@code ServerWebExchange}. 083 */ 084 public void setArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { 085 this.resolvers.addResolvers(resolvers); 086 } 087 088 /** 089 * Return the configured argument resolvers. 090 */ 091 public List<HandlerMethodArgumentResolver> getResolvers() { 092 return this.resolvers.getResolvers(); 093 } 094 095 /** 096 * Set the ParameterNameDiscoverer for resolving parameter names when needed 097 * (e.g. default request attribute name). 098 * <p>Default is a {@link DefaultParameterNameDiscoverer}. 099 */ 100 public void setParameterNameDiscoverer(ParameterNameDiscoverer nameDiscoverer) { 101 this.parameterNameDiscoverer = nameDiscoverer; 102 } 103 104 /** 105 * Return the configured parameter name discoverer. 106 */ 107 public ParameterNameDiscoverer getParameterNameDiscoverer() { 108 return this.parameterNameDiscoverer; 109 } 110 111 /** 112 * Configure a reactive adapter registry. This is needed for async return values. 113 * <p>By default this is a {@link ReactiveAdapterRegistry} with default settings. 114 */ 115 public void setReactiveAdapterRegistry(ReactiveAdapterRegistry registry) { 116 this.reactiveAdapterRegistry = registry; 117 } 118 119 120 /** 121 * Invoke the method for the given exchange. 122 * @param message the current message 123 * @param providedArgs optional list of argument values to match by type 124 * @return a Mono with the result from the invocation 125 */ 126 @SuppressWarnings("KotlinInternalInJava") 127 public Mono<Object> invoke(Message<?> message, Object... providedArgs) { 128 return getMethodArgumentValues(message, providedArgs).flatMap(args -> { 129 Object value; 130 boolean isSuspendingFunction = false; 131 try { 132 Method method = getBridgedMethod(); 133 ReflectionUtils.makeAccessible(method); 134 if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(method.getDeclaringClass()) 135 && CoroutinesUtils.isSuspendingFunction(method)) { 136 isSuspendingFunction = true; 137 value = CoroutinesUtils.invokeSuspendingFunction(method, getBean(), args); 138 } 139 else { 140 value = method.invoke(getBean(), args); 141 } 142 } 143 catch (IllegalArgumentException ex) { 144 assertTargetBean(getBridgedMethod(), getBean(), args); 145 String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument"); 146 return Mono.error(new IllegalStateException(formatInvokeError(text, args), ex)); 147 } 148 catch (InvocationTargetException ex) { 149 return Mono.error(ex.getTargetException()); 150 } 151 catch (Throwable ex) { 152 // Unlikely to ever get here, but it must be handled... 153 return Mono.error(new IllegalStateException(formatInvokeError("Invocation failure", args), ex)); 154 } 155 156 MethodParameter returnType = getReturnType(); 157 Class<?> reactiveType = (isSuspendingFunction ? value.getClass() : returnType.getParameterType()); 158 ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(reactiveType); 159 return (isAsyncVoidReturnType(returnType, adapter) ? 160 Mono.from(adapter.toPublisher(value)) : Mono.justOrEmpty(value)); 161 }); 162 } 163 164 private Mono<Object[]> getMethodArgumentValues(Message<?> message, Object... providedArgs) { 165 MethodParameter[] parameters = getMethodParameters(); 166 if (ObjectUtils.isEmpty(getMethodParameters())) { 167 return EMPTY_ARGS; 168 } 169 170 List<Mono<Object>> argMonos = new ArrayList<>(parameters.length); 171 for (MethodParameter parameter : parameters) { 172 parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); 173 Object providedArg = findProvidedArgument(parameter, providedArgs); 174 if (providedArg != null) { 175 argMonos.add(Mono.just(providedArg)); 176 continue; 177 } 178 if (!this.resolvers.supportsParameter(parameter)) { 179 return Mono.error(new MethodArgumentResolutionException( 180 message, parameter, formatArgumentError(parameter, "No suitable resolver"))); 181 } 182 try { 183 argMonos.add(this.resolvers.resolveArgument(parameter, message) 184 .defaultIfEmpty(NO_ARG_VALUE) 185 .doOnError(ex -> logArgumentErrorIfNecessary(parameter, ex))); 186 } 187 catch (Exception ex) { 188 logArgumentErrorIfNecessary(parameter, ex); 189 argMonos.add(Mono.error(ex)); 190 } 191 } 192 return Mono.zip(argMonos, values -> 193 Stream.of(values).map(value -> value != NO_ARG_VALUE ? value : null).toArray()); 194 } 195 196 private void logArgumentErrorIfNecessary(MethodParameter parameter, Throwable ex) { 197 // Leave stack trace for later, if error is not handled... 198 String exMsg = ex.getMessage(); 199 if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) { 200 if (logger.isDebugEnabled()) { 201 logger.debug(formatArgumentError(parameter, exMsg)); 202 } 203 } 204 } 205 206 private boolean isAsyncVoidReturnType(MethodParameter returnType, @Nullable ReactiveAdapter reactiveAdapter) { 207 if (reactiveAdapter != null && reactiveAdapter.supportsEmpty()) { 208 if (reactiveAdapter.isNoValue()) { 209 return true; 210 } 211 Type parameterType = returnType.getGenericParameterType(); 212 if (parameterType instanceof ParameterizedType) { 213 ParameterizedType type = (ParameterizedType) parameterType; 214 if (type.getActualTypeArguments().length == 1) { 215 return Void.class.equals(type.getActualTypeArguments()[0]); 216 } 217 } 218 } 219 return false; 220 } 221 222}