001/*
002 * Copyright 2002-2015 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.servlet. mvc.multiaction;
018
019import java.lang.reflect.InvocationTargetException;
020import java.lang.reflect.Method;
021import java.util.ArrayList;
022import java.util.HashMap;
023import java.util.List;
024import java.util.Map;
025import javax.servlet.http.HttpServletRequest;
026import javax.servlet.http.HttpServletResponse;
027import javax.servlet.http.HttpSession;
028
029import org.apache.commons.logging.Log;
030import org.apache.commons.logging.LogFactory;
031
032import org.springframework.beans.BeanUtils;
033import org.springframework.util.Assert;
034import org.springframework.util.ReflectionUtils;
035import org.springframework.validation.ValidationUtils;
036import org.springframework.validation.Validator;
037import org.springframework.web.HttpSessionRequiredException;
038import org.springframework.web.bind.ServletRequestDataBinder;
039import org.springframework.web.bind.support.WebBindingInitializer;
040import org.springframework.web.context.request.ServletWebRequest;
041import org.springframework.web.servlet.ModelAndView;
042import org.springframework.web.servlet.mvc.AbstractController;
043import org.springframework.web.servlet.mvc.LastModified;
044
045/**
046 * {@link org.springframework.web.servlet.mvc.Controller Controller}
047 * implementation that allows multiple request types to be handled by the same
048 * class. Subclasses of this class can handle several different types of
049 * request with methods of the form
050 *
051 * <pre class="code">public (ModelAndView | Map | String | void) actionName(HttpServletRequest request, HttpServletResponse response, [,HttpSession] [,AnyObject]);</pre>
052 *
053 * A Map return value indicates a model that is supposed to be passed to a default view
054 * (determined through a {@link org.springframework.web.servlet.RequestToViewNameTranslator}).
055 * A String return value indicates the name of a view to be rendered without a specific model.
056 *
057 * <p>May take a third parameter (of type {@link HttpSession}) in which an
058 * existing session will be required, or a third parameter of an arbitrary
059 * class that gets treated as the command (that is, an instance of the class
060 * gets created, and request parameters get bound to it)
061 *
062 * <p>These methods can throw any kind of exception, but should only let
063 * propagate those that they consider fatal, or which their class or superclass
064 * is prepared to catch by implementing an exception handler.
065 *
066 * <p>When returning just a {@link Map} instance view name translation will be
067 * used to generate the view name. The configured
068 * {@link org.springframework.web.servlet.RequestToViewNameTranslator} will be
069 * used to determine the view name.
070 *
071 * <p>When returning {@code void} a return value of {@code null} is
072 * assumed meaning that the handler method is responsible for writing the
073 * response directly to the supplied {@link HttpServletResponse}.
074 *
075 * <p>This model allows for rapid coding, but loses the advantage of
076 * compile-time checking. It is similar to a Struts {@code DispatchAction},
077 * but more sophisticated. Also supports delegation to another object.
078 *
079 * <p>An implementation of the {@link MethodNameResolver} interface defined in
080 * this package should return a method name for a given request, based on any
081 * aspect of the request, such as its URL or an "action" parameter. The actual
082 * strategy can be configured via the "methodNameResolver" bean property, for
083 * each {@code MultiActionController}.
084 *
085 * <p>The default {@code MethodNameResolver} is
086 * {@link InternalPathMethodNameResolver}; further included strategies are
087 * {@link PropertiesMethodNameResolver} and {@link ParameterMethodNameResolver}.
088 *
089 * <p>Subclasses can implement custom exception handler methods with names such
090 * as:
091 *
092 * <pre class="code">public ModelAndView anyMeaningfulName(HttpServletRequest request, HttpServletResponse response, ExceptionClass exception);</pre>
093 *
094 * The third parameter can be any subclass or {@link Exception} or
095 * {@link RuntimeException}.
096 *
097 * <p>There can also be an optional {@code xxxLastModified} method for
098 * handlers, of signature:
099 *
100 * <pre class="code">public long anyMeaningfulNameLastModified(HttpServletRequest request)</pre>
101 *
102 * If such a method is present, it will be invoked. Default return from
103 * {@code getLastModified} is -1, meaning that the content must always be
104 * regenerated.
105 *
106 * <p><b>Note that all handler methods need to be public and that
107 * method overloading is <i>not</i> allowed.</b>
108 *
109 * <p>See also the description of the workflow performed by
110 * {@link AbstractController the superclass} (in that section of the class
111 * level Javadoc entitled 'workflow').
112 *
113 * <p><b>Note:</b> For maximum data binding flexibility, consider direct usage of a
114 * {@link ServletRequestDataBinder} in your controller method, instead of relying
115 * on a declared command argument. This allows for full control over the entire
116 * binder setup and usage, including the invocation of {@link Validator Validators}
117 * and the subsequent evaluation of binding/validation errors.
118 *
119 * @author Rod Johnson
120 * @author Juergen Hoeller
121 * @author Colin Sampaleanu
122 * @author Rob Harrop
123 * @author Sam Brannen
124 * @see MethodNameResolver
125 * @see InternalPathMethodNameResolver
126 * @see PropertiesMethodNameResolver
127 * @see ParameterMethodNameResolver
128 * @see org.springframework.web.servlet.mvc.LastModified#getLastModified
129 * @see org.springframework.web.bind.ServletRequestDataBinder
130 * @deprecated as of 4.3, in favor of annotation-driven handler methods
131 */
132@Deprecated
133public class MultiActionController extends AbstractController implements LastModified {
134
135        /** Suffix for last-modified methods */
136        public static final String LAST_MODIFIED_METHOD_SUFFIX = "LastModified";
137
138        /** Default command name used for binding command objects: "command" */
139        public static final String DEFAULT_COMMAND_NAME = "command";
140
141        /**
142         * Log category to use when no mapped handler is found for a request.
143         * @see #pageNotFoundLogger
144         */
145        public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound";
146
147
148        /**
149         * Additional logger to use when no mapped handler is found for a request.
150         * @see #PAGE_NOT_FOUND_LOG_CATEGORY
151         */
152        protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY);
153
154        /** Object we'll invoke methods on. Defaults to this. */
155        private Object delegate;
156
157        /** Delegate that knows how to determine method names from incoming requests */
158        private MethodNameResolver methodNameResolver = new InternalPathMethodNameResolver();
159
160        /** List of Validators to apply to commands */
161        private Validator[] validators;
162
163        /** Optional strategy for pre-initializing data binding */
164        private WebBindingInitializer webBindingInitializer;
165
166        /** Handler methods, keyed by name */
167        private final Map<String, Method> handlerMethodMap = new HashMap<String, Method>();
168
169        /** LastModified methods, keyed by handler method name (without LAST_MODIFIED_SUFFIX) */
170        private final Map<String, Method> lastModifiedMethodMap = new HashMap<String, Method>();
171
172        /** Methods, keyed by exception class */
173        private final Map<Class<?>, Method> exceptionHandlerMap = new HashMap<Class<?>, Method>();
174
175
176        /**
177         * Constructor for {@code MultiActionController} that looks for
178         * handler methods in the present subclass.
179         */
180        public MultiActionController() {
181                this.delegate = this;
182                registerHandlerMethods(this.delegate);
183                // We'll accept no handler methods found here - a delegate might be set later on.
184        }
185
186        /**
187         * Constructor for {@code MultiActionController} that looks for
188         * handler methods in delegate, rather than a subclass of this class.
189         * @param delegate handler object. This does not need to implement any
190         * particular interface, as everything is done using reflection.
191         */
192        public MultiActionController(Object delegate) {
193                setDelegate(delegate);
194        }
195
196
197        /**
198         * Set the delegate used by this class; the default is {@code this},
199         * assuming that handler methods have been added by a subclass.
200         * <p>This method does not get invoked once the class is configured.
201         * @param delegate an object containing handler methods
202         * @throws IllegalStateException if no handler methods are found
203         */
204        public final void setDelegate(Object delegate) {
205                Assert.notNull(delegate, "Delegate must not be null");
206                this.delegate = delegate;
207                registerHandlerMethods(this.delegate);
208                // There must be SOME handler methods.
209                if (this.handlerMethodMap.isEmpty()) {
210                        throw new IllegalStateException("No handler methods in class [" + this.delegate.getClass() + "]");
211                }
212        }
213
214        /**
215         * Set the method name resolver that this class should use.
216         * <p>Allows parameterization of handler method mappings.
217         */
218        public final void setMethodNameResolver(MethodNameResolver methodNameResolver) {
219                this.methodNameResolver = methodNameResolver;
220        }
221
222        /**
223         * Return the MethodNameResolver used by this class.
224         */
225        public final MethodNameResolver getMethodNameResolver() {
226                return this.methodNameResolver;
227        }
228
229        /**
230         * Set the {@link Validator Validators} for this controller.
231         * <p>The {@code Validators} must support the specified command class.
232         */
233        public final void setValidators(Validator[] validators) {
234                this.validators = validators;
235        }
236
237        /**
238         * Return the Validators for this controller.
239         */
240        public final Validator[] getValidators() {
241                return this.validators;
242        }
243
244        /**
245         * Specify a WebBindingInitializer which will apply pre-configured
246         * configuration to every DataBinder that this controller uses.
247         * <p>Allows for factoring out the entire binder configuration
248         * to separate objects, as an alternative to {@link #initBinder}.
249         */
250        public final void setWebBindingInitializer(WebBindingInitializer webBindingInitializer) {
251                this.webBindingInitializer = webBindingInitializer;
252        }
253
254        /**
255         * Return the WebBindingInitializer (if any) which will apply pre-configured
256         * configuration to every DataBinder that this controller uses.
257         */
258        public final WebBindingInitializer getWebBindingInitializer() {
259                return this.webBindingInitializer;
260        }
261
262
263        /**
264         * Registers all handlers methods on the delegate object.
265         */
266        private void registerHandlerMethods(Object delegate) {
267                this.handlerMethodMap.clear();
268                this.lastModifiedMethodMap.clear();
269                this.exceptionHandlerMap.clear();
270
271                // Look at all methods in the subclass, trying to find
272                // methods that are validators according to our criteria
273                Method[] methods = delegate.getClass().getMethods();
274                for (Method method : methods) {
275                        // We're looking for methods with given parameters.
276                        if (isExceptionHandlerMethod(method)) {
277                                registerExceptionHandlerMethod(method);
278                        }
279                        else if (isHandlerMethod(method)) {
280                                registerHandlerMethod(method);
281                                registerLastModifiedMethodIfExists(delegate, method);
282                        }
283                }
284        }
285
286        /**
287         * Is the supplied method a valid handler method?
288         * <p>Does not consider {@code Controller.handleRequest} itself
289         * as handler method (to avoid potential stack overflow).
290         */
291        private boolean isHandlerMethod(Method method) {
292                Class<?> returnType = method.getReturnType();
293                if (ModelAndView.class == returnType || Map.class == returnType || String.class == returnType ||
294                                void.class == returnType) {
295                        Class<?>[] parameterTypes = method.getParameterTypes();
296                        return (parameterTypes.length >= 2 &&
297                                        HttpServletRequest.class == parameterTypes[0] &&
298                                        HttpServletResponse.class == parameterTypes[1] &&
299                                        !("handleRequest".equals(method.getName()) && parameterTypes.length == 2));
300                }
301                return false;
302        }
303
304        /**
305         * Is the supplied method a valid exception handler method?
306         */
307        private boolean isExceptionHandlerMethod(Method method) {
308                return (isHandlerMethod(method) &&
309                                method.getParameterTypes().length == 3 &&
310                                Throwable.class.isAssignableFrom(method.getParameterTypes()[2]));
311        }
312
313        /**
314         * Registers the supplied method as a request handler.
315         */
316        private void registerHandlerMethod(Method method) {
317                if (logger.isDebugEnabled()) {
318                        logger.debug("Found action method [" + method + "]");
319                }
320                this.handlerMethodMap.put(method.getName(), method);
321        }
322
323        /**
324         * Registers a last-modified handler method for the supplied handler method
325         * if one exists.
326         */
327        private void registerLastModifiedMethodIfExists(Object delegate, Method method) {
328                // Look for corresponding LastModified method.
329                try {
330                        Method lastModifiedMethod = delegate.getClass().getMethod(
331                                        method.getName() + LAST_MODIFIED_METHOD_SUFFIX,
332                                        new Class<?>[] {HttpServletRequest.class});
333                        Class<?> returnType = lastModifiedMethod.getReturnType();
334                        if (!(long.class == returnType || Long.class == returnType)) {
335                                throw new IllegalStateException("last-modified method [" + lastModifiedMethod +
336                                                "] declares an invalid return type - needs to be 'long' or 'Long'");
337                        }
338                        // Put in cache, keyed by handler method name.
339                        this.lastModifiedMethodMap.put(method.getName(), lastModifiedMethod);
340                        if (logger.isDebugEnabled()) {
341                                logger.debug("Found last-modified method for handler method [" + method + "]");
342                        }
343                }
344                catch (NoSuchMethodException ex) {
345                        // No last modified method. That's ok.
346                }
347        }
348
349        /**
350         * Registers the supplied method as an exception handler.
351         */
352        private void registerExceptionHandlerMethod(Method method) {
353                this.exceptionHandlerMap.put(method.getParameterTypes()[2], method);
354                if (logger.isDebugEnabled()) {
355                        logger.debug("Found exception handler method [" + method + "]");
356                }
357        }
358
359
360        //---------------------------------------------------------------------
361        // Implementation of LastModified
362        //---------------------------------------------------------------------
363
364        /**
365         * Try to find an XXXXLastModified method, where XXXX is the name of a handler.
366         * Return -1 if there's no such handler, indicating that content must be updated.
367         * @see org.springframework.web.servlet.mvc.LastModified#getLastModified(HttpServletRequest)
368         */
369        @Override
370        public long getLastModified(HttpServletRequest request) {
371                try {
372                        String handlerMethodName = this.methodNameResolver.getHandlerMethodName(request);
373                        Method lastModifiedMethod = this.lastModifiedMethodMap.get(handlerMethodName);
374                        if (lastModifiedMethod != null) {
375                                try {
376                                        // Invoke the last-modified method...
377                                        Long wrappedLong = (Long) lastModifiedMethod.invoke(this.delegate, request);
378                                        return (wrappedLong != null ? wrappedLong : -1);
379                                }
380                                catch (Exception ex) {
381                                        // We encountered an error invoking the last-modified method.
382                                        // We can't do anything useful except log this, as we can't throw an exception.
383                                        logger.error("Failed to invoke last-modified method", ex);
384                                }
385                        }
386                }
387                catch (NoSuchRequestHandlingMethodException ex) {
388                        // No handler method for this request. This shouldn't happen, as this
389                        // method shouldn't be called unless a previous invocation of this class
390                        // has generated content. Do nothing, that's OK: We'll return default.
391                }
392                return -1L;
393        }
394
395
396        //---------------------------------------------------------------------
397        // Implementation of AbstractController
398        //---------------------------------------------------------------------
399
400        /**
401         * Determine a handler method and invoke it.
402         * @see MethodNameResolver#getHandlerMethodName
403         * @see #invokeNamedMethod
404         * @see #handleNoSuchRequestHandlingMethod
405         */
406        @Override
407        protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
408                        throws Exception {
409                try {
410                        String methodName = this.methodNameResolver.getHandlerMethodName(request);
411                        return invokeNamedMethod(methodName, request, response);
412                }
413                catch (NoSuchRequestHandlingMethodException ex) {
414                        return handleNoSuchRequestHandlingMethod(ex, request, response);
415                }
416        }
417
418        /**
419         * Handle the case where no request handler method was found.
420         * <p>The default implementation logs a warning and sends an HTTP 404 error.
421         * Alternatively, a fallback view could be chosen, or the
422         * NoSuchRequestHandlingMethodException could be rethrown as-is.
423         * @param ex the NoSuchRequestHandlingMethodException to be handled
424         * @param request current HTTP request
425         * @param response current HTTP response
426         * @return a ModelAndView to render, or {@code null} if handled directly
427         * @throws Exception an Exception that should be thrown as result of the servlet request
428         */
429        protected ModelAndView handleNoSuchRequestHandlingMethod(
430                        NoSuchRequestHandlingMethodException ex, HttpServletRequest request, HttpServletResponse response)
431                        throws Exception {
432
433                pageNotFoundLogger.warn(ex.getMessage());
434                response.sendError(HttpServletResponse.SC_NOT_FOUND);
435                return null;
436        }
437
438        /**
439         * Invokes the named method.
440         * <p>Uses a custom exception handler if possible; otherwise, throw an
441         * unchecked exception; wrap a checked exception or Throwable.
442         */
443        protected final ModelAndView invokeNamedMethod(
444                        String methodName, HttpServletRequest request, HttpServletResponse response) throws Exception {
445
446                Method method = this.handlerMethodMap.get(methodName);
447                if (method == null) {
448                        throw new NoSuchRequestHandlingMethodException(methodName, getClass());
449                }
450
451                try {
452                        Class<?>[] paramTypes = method.getParameterTypes();
453                        List<Object> params = new ArrayList<Object>(4);
454                        params.add(request);
455                        params.add(response);
456
457                        if (paramTypes.length >= 3 && HttpSession.class == paramTypes[2]) {
458                                HttpSession session = request.getSession(false);
459                                if (session == null) {
460                                        throw new HttpSessionRequiredException(
461                                                        "Pre-existing session required for handler method '" + methodName + "'");
462                                }
463                                params.add(session);
464                        }
465
466                        // If last parameter isn't of HttpSession type, it's a command.
467                        if (paramTypes.length >= 3 && HttpSession.class != paramTypes[paramTypes.length - 1]) {
468                                Object command = newCommandObject(paramTypes[paramTypes.length - 1]);
469                                params.add(command);
470                                bind(request, command);
471                        }
472
473                        Object returnValue = method.invoke(this.delegate, params.toArray(new Object[params.size()]));
474                        return massageReturnValueIfNecessary(returnValue);
475                }
476                catch (InvocationTargetException ex) {
477                        // The handler method threw an exception.
478                        return handleException(request, response, ex.getTargetException());
479                }
480                catch (Exception ex) {
481                        // The binding process threw an exception.
482                        return handleException(request, response, ex);
483                }
484        }
485
486        /**
487         * Processes the return value of a handler method to ensure that it either returns
488         * {@code null} or an instance of {@link ModelAndView}. When returning a {@link Map},
489         * the {@link Map} instance is wrapped in a new {@link ModelAndView} instance.
490         */
491        @SuppressWarnings("unchecked")
492        private ModelAndView massageReturnValueIfNecessary(Object returnValue) {
493                if (returnValue instanceof ModelAndView) {
494                        return (ModelAndView) returnValue;
495                }
496                else if (returnValue instanceof Map) {
497                        return new ModelAndView().addAllObjects((Map<String, ?>) returnValue);
498                }
499                else if (returnValue instanceof String) {
500                        return new ModelAndView((String) returnValue);
501                }
502                else {
503                        // Either returned null or was 'void' return.
504                        // We'll assume that the handle method already wrote the response.
505                        return null;
506                }
507        }
508
509
510        /**
511         * Create a new command object of the given class.
512         * <p>This implementation uses {@code BeanUtils.instantiateClass},
513         * so commands need to have public no-arg constructors.
514         * Subclasses can override this implementation if desired.
515         * @throws Exception if the command object could not be instantiated
516         * @see org.springframework.beans.BeanUtils#instantiateClass(Class)
517         */
518        protected Object newCommandObject(Class<?> clazz) throws Exception {
519                if (logger.isDebugEnabled()) {
520                        logger.debug("Creating new command of class [" + clazz.getName() + "]");
521                }
522                return BeanUtils.instantiateClass(clazz);
523        }
524
525        /**
526         * Bind request parameters onto the given command bean
527         * @param request request from which parameters will be bound
528         * @param command command object, that must be a JavaBean
529         * @throws Exception in case of invalid state or arguments
530         */
531        protected void bind(HttpServletRequest request, Object command) throws Exception {
532                logger.debug("Binding request parameters onto MultiActionController command");
533                ServletRequestDataBinder binder = createBinder(request, command);
534                binder.bind(request);
535                if (this.validators != null) {
536                        for (Validator validator : this.validators) {
537                                if (validator.supports(command.getClass())) {
538                                        ValidationUtils.invokeValidator(validator, command, binder.getBindingResult());
539                                }
540                        }
541                }
542                binder.closeNoCatch();
543        }
544
545        /**
546         * Create a new binder instance for the given command and request.
547         * <p>Called by {@code bind}. Can be overridden to plug in custom
548         * ServletRequestDataBinder subclasses.
549         * <p>The default implementation creates a standard ServletRequestDataBinder,
550         * and invokes {@code initBinder}. Note that {@code initBinder}
551         * will not be invoked if you override this method!
552         * @param request current HTTP request
553         * @param command the command to bind onto
554         * @return the new binder instance
555         * @throws Exception in case of invalid state or arguments
556         * @see #bind
557         * @see #initBinder
558         */
559        protected ServletRequestDataBinder createBinder(HttpServletRequest request, Object command) throws Exception {
560                ServletRequestDataBinder binder = new ServletRequestDataBinder(command, getCommandName(command));
561                initBinder(request, binder);
562                return binder;
563        }
564
565        /**
566         * Return the command name to use for the given command object.
567         * <p>Default is "command".
568         * @param command the command object
569         * @return the command name to use
570         * @see #DEFAULT_COMMAND_NAME
571         */
572        protected String getCommandName(Object command) {
573                return DEFAULT_COMMAND_NAME;
574        }
575
576        /**
577         * Initialize the given binder instance, for example with custom editors.
578         * Called by {@code createBinder}.
579         * <p>This method allows you to register custom editors for certain fields of your
580         * command class. For instance, you will be able to transform Date objects into a
581         * String pattern and back, in order to allow your JavaBeans to have Date properties
582         * and still be able to set and display them in an HTML interface.
583         * <p>The default implementation is empty.
584         * <p>Note: the command object is not directly passed to this method, but it's available
585         * via {@link org.springframework.validation.DataBinder#getTarget()}
586         * @param request current HTTP request
587         * @param binder new binder instance
588         * @throws Exception in case of invalid state or arguments
589         * @see #createBinder
590         * @see org.springframework.validation.DataBinder#registerCustomEditor
591         * @see org.springframework.beans.propertyeditors.CustomDateEditor
592         */
593        protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
594                if (this.webBindingInitializer != null) {
595                        this.webBindingInitializer.initBinder(binder, new ServletWebRequest(request));
596                }
597        }
598
599
600        /**
601         * Determine the exception handler method for the given exception.
602         * <p>Can return {@code null} if not found.
603         * @return a handler for the given exception type, or {@code null}
604         * @param exception the exception to handle
605         */
606        protected Method getExceptionHandler(Throwable exception) {
607                Class<?> exceptionClass = exception.getClass();
608                if (logger.isDebugEnabled()) {
609                        logger.debug("Trying to find handler for exception class [" + exceptionClass.getName() + "]");
610                }
611                Method handler = this.exceptionHandlerMap.get(exceptionClass);
612                while (handler == null && exceptionClass != Throwable.class) {
613                        if (logger.isDebugEnabled()) {
614                                logger.debug("Trying to find handler for exception superclass [" + exceptionClass.getName() + "]");
615                        }
616                        exceptionClass = exceptionClass.getSuperclass();
617                        handler = this.exceptionHandlerMap.get(exceptionClass);
618                }
619                return handler;
620        }
621
622        /**
623         * We've encountered an exception thrown from a handler method.
624         * Invoke an appropriate exception handler method, if any.
625         * @param request current HTTP request
626         * @param response current HTTP response
627         * @param ex the exception that got thrown
628         * @return a ModelAndView to render the response
629         */
630        private ModelAndView handleException(HttpServletRequest request, HttpServletResponse response, Throwable ex)
631                        throws Exception {
632
633                Method handler = getExceptionHandler(ex);
634                if (handler != null) {
635                        if (logger.isDebugEnabled()) {
636                                logger.debug("Invoking exception handler [" + handler + "] for exception: " + ex);
637                        }
638                        try {
639                                Object returnValue = handler.invoke(this.delegate, request, response, ex);
640                                return massageReturnValueIfNecessary(returnValue);
641                        }
642                        catch (InvocationTargetException ex2) {
643                                logger.error("Original exception overridden by exception handling failure", ex);
644                                ReflectionUtils.rethrowException(ex2.getTargetException());
645                        }
646                        catch (Exception ex2) {
647                                logger.error("Failed to invoke exception handler method", ex2);
648                        }
649                }
650                else {
651                        // If we get here, there was no custom handler or we couldn't invoke it.
652                        ReflectionUtils.rethrowException(ex);
653                }
654                throw new IllegalStateException("Should never get here");
655        }
656
657}