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}