001/* 002 * Copyright 2002-2020 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.web.reactive.result.method; 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.http.HttpStatus; 037import org.springframework.http.server.reactive.ServerHttpResponse; 038import org.springframework.lang.Nullable; 039import org.springframework.util.ObjectUtils; 040import org.springframework.util.ReflectionUtils; 041import org.springframework.web.method.HandlerMethod; 042import org.springframework.web.reactive.BindingContext; 043import org.springframework.web.reactive.HandlerResult; 044import org.springframework.web.server.ServerWebExchange; 045 046/** 047 * Extension of {@link HandlerMethod} that invokes the underlying method with 048 * argument values resolved from the current HTTP request through a list of 049 * {@link HandlerMethodArgumentResolver}. 050 * 051 * @author Rossen Stoyanchev 052 * @author Juergen Hoeller 053 * @author Sebastien Deleuze 054 * @since 5.0 055 */ 056public class InvocableHandlerMethod extends HandlerMethod { 057 058 private static final Mono<Object[]> EMPTY_ARGS = Mono.just(new Object[0]); 059 060 private static final Object NO_ARG_VALUE = new Object(); 061 062 063 private final HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite(); 064 065 private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); 066 067 private ReactiveAdapterRegistry reactiveAdapterRegistry = ReactiveAdapterRegistry.getSharedInstance(); 068 069 070 /** 071 * Create an instance from a {@code HandlerMethod}. 072 */ 073 public InvocableHandlerMethod(HandlerMethod handlerMethod) { 074 super(handlerMethod); 075 } 076 077 /** 078 * Create an instance from a bean instance and a method. 079 */ 080 public InvocableHandlerMethod(Object bean, Method method) { 081 super(bean, method); 082 } 083 084 085 /** 086 * Configure the argument resolvers to use to use for resolving method 087 * argument values against a {@code ServerWebExchange}. 088 */ 089 public void setArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { 090 this.resolvers.addResolvers(resolvers); 091 } 092 093 /** 094 * Return the configured argument resolvers. 095 */ 096 public List<HandlerMethodArgumentResolver> getResolvers() { 097 return this.resolvers.getResolvers(); 098 } 099 100 /** 101 * Set the ParameterNameDiscoverer for resolving parameter names when needed 102 * (e.g. default request attribute name). 103 * <p>Default is a {@link DefaultParameterNameDiscoverer}. 104 */ 105 public void setParameterNameDiscoverer(ParameterNameDiscoverer nameDiscoverer) { 106 this.parameterNameDiscoverer = nameDiscoverer; 107 } 108 109 /** 110 * Return the configured parameter name discoverer. 111 */ 112 public ParameterNameDiscoverer getParameterNameDiscoverer() { 113 return this.parameterNameDiscoverer; 114 } 115 116 /** 117 * Configure a reactive adapter registry. This is needed for cases where the response is 118 * fully handled within the controller in combination with an async void return value. 119 * <p>By default this is a {@link ReactiveAdapterRegistry} with default settings. 120 */ 121 public void setReactiveAdapterRegistry(ReactiveAdapterRegistry registry) { 122 this.reactiveAdapterRegistry = registry; 123 } 124 125 126 /** 127 * Invoke the method for the given exchange. 128 * @param exchange the current exchange 129 * @param bindingContext the binding context to use 130 * @param providedArgs optional list of argument values to match by type 131 * @return a Mono with a {@link HandlerResult} 132 */ 133 @SuppressWarnings("KotlinInternalInJava") 134 public Mono<HandlerResult> invoke( 135 ServerWebExchange exchange, BindingContext bindingContext, Object... providedArgs) { 136 137 return getMethodArgumentValues(exchange, bindingContext, providedArgs).flatMap(args -> { 138 Object value; 139 try { 140 ReflectionUtils.makeAccessible(getBridgedMethod()); 141 Method method = getBridgedMethod(); 142 if (KotlinDetector.isKotlinReflectPresent() && 143 KotlinDetector.isKotlinType(method.getDeclaringClass()) && 144 CoroutinesUtils.isSuspendingFunction(method)) { 145 value = CoroutinesUtils.invokeSuspendingFunction(method, getBean(), args); 146 } 147 else { 148 value = method.invoke(getBean(), args); 149 } 150 } 151 catch (IllegalArgumentException ex) { 152 assertTargetBean(getBridgedMethod(), getBean(), args); 153 String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument"); 154 return Mono.error(new IllegalStateException(formatInvokeError(text, args), ex)); 155 } 156 catch (InvocationTargetException ex) { 157 return Mono.error(ex.getTargetException()); 158 } 159 catch (Throwable ex) { 160 // Unlikely to ever get here, but it must be handled... 161 return Mono.error(new IllegalStateException(formatInvokeError("Invocation failure", args), ex)); 162 } 163 164 HttpStatus status = getResponseStatus(); 165 if (status != null) { 166 exchange.getResponse().setStatusCode(status); 167 } 168 169 MethodParameter returnType = getReturnType(); 170 ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(returnType.getParameterType()); 171 boolean asyncVoid = isAsyncVoidReturnType(returnType, adapter); 172 if ((value == null || asyncVoid) && isResponseHandled(args, exchange)) { 173 return (asyncVoid ? Mono.from(adapter.toPublisher(value)) : Mono.empty()); 174 } 175 176 HandlerResult result = new HandlerResult(this, value, returnType, bindingContext); 177 return Mono.just(result); 178 }); 179 } 180 181 private Mono<Object[]> getMethodArgumentValues( 182 ServerWebExchange exchange, BindingContext bindingContext, Object... providedArgs) { 183 184 MethodParameter[] parameters = getMethodParameters(); 185 if (ObjectUtils.isEmpty(parameters)) { 186 return EMPTY_ARGS; 187 } 188 189 List<Mono<Object>> argMonos = new ArrayList<>(parameters.length); 190 for (MethodParameter parameter : parameters) { 191 parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); 192 Object providedArg = findProvidedArgument(parameter, providedArgs); 193 if (providedArg != null) { 194 argMonos.add(Mono.just(providedArg)); 195 continue; 196 } 197 if (!this.resolvers.supportsParameter(parameter)) { 198 return Mono.error(new IllegalStateException( 199 formatArgumentError(parameter, "No suitable resolver"))); 200 } 201 try { 202 argMonos.add(this.resolvers.resolveArgument(parameter, bindingContext, exchange) 203 .defaultIfEmpty(NO_ARG_VALUE) 204 .doOnError(ex -> logArgumentErrorIfNecessary(exchange, parameter, ex))); 205 } 206 catch (Exception ex) { 207 logArgumentErrorIfNecessary(exchange, parameter, ex); 208 argMonos.add(Mono.error(ex)); 209 } 210 } 211 return Mono.zip(argMonos, values -> 212 Stream.of(values).map(value -> value != NO_ARG_VALUE ? value : null).toArray()); 213 } 214 215 private void logArgumentErrorIfNecessary(ServerWebExchange exchange, MethodParameter parameter, Throwable ex) { 216 // Leave stack trace for later, if error is not handled... 217 String exMsg = ex.getMessage(); 218 if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) { 219 if (logger.isDebugEnabled()) { 220 logger.debug(exchange.getLogPrefix() + formatArgumentError(parameter, exMsg)); 221 } 222 } 223 } 224 225 private static boolean isAsyncVoidReturnType(MethodParameter returnType, @Nullable ReactiveAdapter adapter) { 226 if (adapter != null && adapter.supportsEmpty()) { 227 if (adapter.isNoValue()) { 228 return true; 229 } 230 Type parameterType = returnType.getGenericParameterType(); 231 if (parameterType instanceof ParameterizedType) { 232 ParameterizedType type = (ParameterizedType) parameterType; 233 if (type.getActualTypeArguments().length == 1) { 234 return Void.class.equals(type.getActualTypeArguments()[0]); 235 } 236 } 237 } 238 return false; 239 } 240 241 private boolean isResponseHandled(Object[] args, ServerWebExchange exchange) { 242 if (getResponseStatus() != null || exchange.isNotModified()) { 243 return true; 244 } 245 for (Object arg : args) { 246 if (arg instanceof ServerHttpResponse || arg instanceof ServerWebExchange) { 247 return true; 248 } 249 } 250 return false; 251 } 252 253}