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