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}