From b587a16d460ad10a98874796c389b933ff85e457 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 11 Jan 2021 11:10:07 +0100 Subject: [PATCH] Expose all exception causes as provided handler method arguments Closes gh-26317 --- .../ExceptionHandlerExceptionResolver.java | 25 +++++---- ...xceptionHandlerExceptionResolverTests.java | 52 +++++++++++++++++-- 2 files changed, 62 insertions(+), 15 deletions(-) diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java index 9d731f6101a6..bdb29fc15a2b 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -410,24 +410,27 @@ protected ModelAndView doResolveHandlerMethodException(HttpServletRequest reques ServletWebRequest webRequest = new ServletWebRequest(request, response); ModelAndViewContainer mavContainer = new ModelAndViewContainer(); + ArrayList exceptions = new ArrayList<>(); try { if (logger.isDebugEnabled()) { logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod); } - Throwable cause = exception.getCause(); - if (cause != null) { - // Expose cause as provided argument as well - exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod); - } - else { - // Otherwise, just the given exception as-is - exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod); + // Expose causes as provided arguments as well + Throwable exToExpose = exception; + while (exToExpose != null) { + exceptions.add(exToExpose); + Throwable cause = exToExpose.getCause(); + exToExpose = (cause != exToExpose ? cause : null); } + Object[] arguments = new Object[exceptions.size() + 1]; + exceptions.toArray(arguments); // efficient arraycopy call in ArrayList + arguments[arguments.length - 1] = handlerMethod; + exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, arguments); } catch (Throwable invocationEx) { - // Any other than the original exception (or its cause) is unintended here, + // Any other than the original exception (or a cause) is unintended here, // probably an accident (e.g. failed assertion or the like). - if (invocationEx != exception && invocationEx != exception.getCause() && logger.isWarnEnabled()) { + if (!exceptions.contains(invocationEx) && logger.isWarnEnabled()) { logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx); } // Continue with default processing of the original exception... diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java index b4f721776000..b3757c556eb4 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -66,6 +66,7 @@ * @author Arjen Poutsma * @author Kazuki Shimizu * @author Brian Clozel + * @author Rodolphe Lecocq * @since 3.1 */ @SuppressWarnings("unused") @@ -189,6 +190,18 @@ void resolveExceptionResponseBody() throws UnsupportedEncodingException, NoSuchM assertThat(this.response.getContentAsString()).isEqualTo("IllegalArgumentException"); } + @Test // gh-26317 + void resolveExceptionResponseBodyMatchingCauseLevel2() throws UnsupportedEncodingException, NoSuchMethodException { + Exception ex = new Exception(new Exception(new IllegalArgumentException())); + HandlerMethod handlerMethod = new HandlerMethod(new ResponseBodyController(), "handle"); + this.resolver.afterPropertiesSet(); + ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod, ex); + + assertThat(mav).isNotNull(); + assertThat(mav.isEmpty()).isTrue(); + assertThat(this.response.getContentAsString()).isEqualTo("IllegalArgumentException"); + } + @Test void resolveExceptionResponseWriter() throws Exception { IllegalArgumentException ex = new IllegalArgumentException(); @@ -257,6 +270,21 @@ void resolveExceptionGlobalHandlerOrdered() throws Exception { assertThat(this.response.getContentAsString()).isEqualTo("TestExceptionResolver: IllegalStateException"); } + @Test // gh-26317 + void resolveExceptionGlobalHandlerOrderedMatchingCauseLevel2() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyConfig.class); + this.resolver.setApplicationContext(ctx); + this.resolver.afterPropertiesSet(); + + Exception ex = new Exception(new Exception(new IllegalStateException())); + HandlerMethod handlerMethod = new HandlerMethod(new ResponseBodyController(), "handle"); + ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod, ex); + + assertThat(mav).as("Exception was not handled").isNotNull(); + assertThat(mav.isEmpty()).isTrue(); + assertThat(this.response.getContentAsString()).isEqualTo("TestExceptionResolver: IllegalStateException"); + } + @Test // SPR-12605 void resolveExceptionWithHandlerMethodArg() throws Exception { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyConfig.class); @@ -294,14 +322,15 @@ void resolveExceptionWithAssertionErrorAsRootCause() throws Exception { this.resolver.setApplicationContext(ctx); this.resolver.afterPropertiesSet(); - AssertionError err = new AssertionError("argh"); - FatalBeanException ex = new FatalBeanException("wrapped", err); + AssertionError rootCause = new AssertionError("argh"); + FatalBeanException cause = new FatalBeanException("wrapped", rootCause); + Exception ex = new Exception(cause); // gh-26317 HandlerMethod handlerMethod = new HandlerMethod(new ResponseBodyController(), "handle"); ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod, ex); assertThat(mav).as("Exception was not handled").isNotNull(); assertThat(mav.isEmpty()).isTrue(); - assertThat(this.response.getContentAsString()).isEqualTo(err.toString()); + assertThat(this.response.getContentAsString()).isEqualTo(rootCause.toString()); } @Test @@ -319,6 +348,21 @@ void resolveExceptionControllerAdviceHandler() throws Exception { assertThat(this.response.getContentAsString()).isEqualTo("BasePackageTestExceptionResolver: IllegalStateException"); } + @Test // gh-26317 + void resolveExceptionControllerAdviceHandlerMatchingCauseLevel2() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyControllerAdviceConfig.class); + this.resolver.setApplicationContext(ctx); + this.resolver.afterPropertiesSet(); + + Exception ex = new Exception(new IllegalStateException()); + HandlerMethod handlerMethod = new HandlerMethod(new ResponseBodyController(), "handle"); + ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod, ex); + + assertThat(mav).as("Exception was not handled").isNotNull(); + assertThat(mav.isEmpty()).isTrue(); + assertThat(this.response.getContentAsString()).isEqualTo("BasePackageTestExceptionResolver: IllegalStateException"); + } + @Test void resolveExceptionControllerAdviceNoHandler() throws Exception { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyControllerAdviceConfig.class);