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;
018
019import java.lang.reflect.InvocationTargetException;
020import java.lang.reflect.Method;
021import java.lang.reflect.Type;
022import java.util.Arrays;
023
024import org.springframework.core.DefaultParameterNameDiscoverer;
025import org.springframework.core.MethodParameter;
026import org.springframework.core.ParameterNameDiscoverer;
027import org.springframework.core.ResolvableType;
028import org.springframework.lang.Nullable;
029import org.springframework.messaging.Message;
030import org.springframework.messaging.handler.HandlerMethod;
031import org.springframework.util.ObjectUtils;
032import org.springframework.util.ReflectionUtils;
033
034/**
035 * Extension of {@link HandlerMethod} that invokes the underlying method with
036 * argument values resolved from the current HTTP request through a list of
037 * {@link HandlerMethodArgumentResolver}.
038 *
039 * @author Rossen Stoyanchev
040 * @author Juergen Hoeller
041 * @since 4.0
042 */
043public class InvocableHandlerMethod extends HandlerMethod {
044
045        private static final Object[] EMPTY_ARGS = new Object[0];
046
047
048        private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite();
049
050        private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
051
052
053        /**
054         * Create an instance from a {@code HandlerMethod}.
055         */
056        public InvocableHandlerMethod(HandlerMethod handlerMethod) {
057                super(handlerMethod);
058        }
059
060        /**
061         * Create an instance from a bean instance and a method.
062         */
063        public InvocableHandlerMethod(Object bean, Method method) {
064                super(bean, method);
065        }
066
067        /**
068         * Construct a new handler method with the given bean instance, method name and parameters.
069         * @param bean the object bean
070         * @param methodName the method name
071         * @param parameterTypes the method parameter types
072         * @throws NoSuchMethodException when the method cannot be found
073         */
074        public InvocableHandlerMethod(Object bean, String methodName, Class<?>... parameterTypes)
075                        throws NoSuchMethodException {
076
077                super(bean, methodName, parameterTypes);
078        }
079
080
081        /**
082         * Set {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers} to use to use for resolving method argument values.
083         */
084        public void setMessageMethodArgumentResolvers(HandlerMethodArgumentResolverComposite argumentResolvers) {
085                this.resolvers = argumentResolvers;
086        }
087
088        /**
089         * Set the ParameterNameDiscoverer for resolving parameter names when needed
090         * (e.g. default request attribute name).
091         * <p>Default is a {@link org.springframework.core.DefaultParameterNameDiscoverer}.
092         */
093        public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
094                this.parameterNameDiscoverer = parameterNameDiscoverer;
095        }
096
097
098        /**
099         * Invoke the method after resolving its argument values in the context of the given message.
100         * <p>Argument values are commonly resolved through
101         * {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}.
102         * The {@code providedArgs} parameter however may supply argument values to be used directly,
103         * i.e. without argument resolution.
104         * <p>Delegates to {@link #getMethodArgumentValues} and calls {@link #doInvoke} with the
105         * resolved arguments.
106         * @param message the current message being processed
107         * @param providedArgs "given" arguments matched by type, not resolved
108         * @return the raw value returned by the invoked method
109         * @throws Exception raised if no suitable argument resolver can be found,
110         * or if the method raised an exception
111         * @see #getMethodArgumentValues
112         * @see #doInvoke
113         */
114        @Nullable
115        public Object invoke(Message<?> message, Object... providedArgs) throws Exception {
116                Object[] args = getMethodArgumentValues(message, providedArgs);
117                if (logger.isTraceEnabled()) {
118                        logger.trace("Arguments: " + Arrays.toString(args));
119                }
120                return doInvoke(args);
121        }
122
123        /**
124         * Get the method argument values for the current message, checking the provided
125         * argument values and falling back to the configured argument resolvers.
126         * <p>The resulting array will be passed into {@link #doInvoke}.
127         * @since 5.1.2
128         */
129        protected Object[] getMethodArgumentValues(Message<?> message, Object... providedArgs) throws Exception {
130                MethodParameter[] parameters = getMethodParameters();
131                if (ObjectUtils.isEmpty(parameters)) {
132                        return EMPTY_ARGS;
133                }
134
135                Object[] args = new Object[parameters.length];
136                for (int i = 0; i < parameters.length; i++) {
137                        MethodParameter parameter = parameters[i];
138                        parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
139                        args[i] = findProvidedArgument(parameter, providedArgs);
140                        if (args[i] != null) {
141                                continue;
142                        }
143                        if (!this.resolvers.supportsParameter(parameter)) {
144                                throw new MethodArgumentResolutionException(
145                                                message, parameter, formatArgumentError(parameter, "No suitable resolver"));
146                        }
147                        try {
148                                args[i] = this.resolvers.resolveArgument(parameter, message);
149                        }
150                        catch (Exception ex) {
151                                // Leave stack trace for later, exception may actually be resolved and handled...
152                                if (logger.isDebugEnabled()) {
153                                        String exMsg = ex.getMessage();
154                                        if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
155                                                logger.debug(formatArgumentError(parameter, exMsg));
156                                        }
157                                }
158                                throw ex;
159                        }
160                }
161                return args;
162        }
163
164        /**
165         * Invoke the handler method with the given argument values.
166         */
167        @Nullable
168        protected Object doInvoke(Object... args) throws Exception {
169                ReflectionUtils.makeAccessible(getBridgedMethod());
170                try {
171                        return getBridgedMethod().invoke(getBean(), args);
172                }
173                catch (IllegalArgumentException ex) {
174                        assertTargetBean(getBridgedMethod(), getBean(), args);
175                        String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
176                        throw new IllegalStateException(formatInvokeError(text, args), ex);
177                }
178                catch (InvocationTargetException ex) {
179                        // Unwrap for HandlerExceptionResolvers ...
180                        Throwable targetException = ex.getTargetException();
181                        if (targetException instanceof RuntimeException) {
182                                throw (RuntimeException) targetException;
183                        }
184                        else if (targetException instanceof Error) {
185                                throw (Error) targetException;
186                        }
187                        else if (targetException instanceof Exception) {
188                                throw (Exception) targetException;
189                        }
190                        else {
191                                throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
192                        }
193                }
194        }
195
196        MethodParameter getAsyncReturnValueType(@Nullable Object returnValue) {
197                return new AsyncResultMethodParameter(returnValue);
198        }
199
200
201        private class AsyncResultMethodParameter extends HandlerMethodParameter {
202
203                @Nullable
204                private final Object returnValue;
205
206                private final ResolvableType returnType;
207
208                public AsyncResultMethodParameter(@Nullable Object returnValue) {
209                        super(-1);
210                        this.returnValue = returnValue;
211                        this.returnType = ResolvableType.forType(super.getGenericParameterType()).getGeneric();
212                }
213
214                protected AsyncResultMethodParameter(AsyncResultMethodParameter original) {
215                        super(original);
216                        this.returnValue = original.returnValue;
217                        this.returnType = original.returnType;
218                }
219
220                @Override
221                public Class<?> getParameterType() {
222                        if (this.returnValue != null) {
223                                return this.returnValue.getClass();
224                        }
225                        if (!ResolvableType.NONE.equals(this.returnType)) {
226                                return this.returnType.toClass();
227                        }
228                        return super.getParameterType();
229                }
230
231                @Override
232                public Type getGenericParameterType() {
233                        return this.returnType.getType();
234                }
235
236                @Override
237                public AsyncResultMethodParameter clone() {
238                        return new AsyncResultMethodParameter(this);
239                }
240        }
241
242}