Skip to content

Commit

Permalink
Expose all exception causes as provided handler method arguments
Browse files Browse the repository at this point in the history
Closes gh-26317
  • Loading branch information
jhoeller committed Jan 11, 2021
1 parent dd8ea89 commit b587a16
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -410,24 +410,27 @@ protected ModelAndView doResolveHandlerMethodException(HttpServletRequest reques
ServletWebRequest webRequest = new ServletWebRequest(request, response);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();

ArrayList<Throwable> 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...
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -66,6 +66,7 @@
* @author Arjen Poutsma
* @author Kazuki Shimizu
* @author Brian Clozel
* @author Rodolphe Lecocq
* @since 3.1
*/
@SuppressWarnings("unused")
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand All @@ -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);
Expand Down

0 comments on commit b587a16

Please sign in to comment.