/*
* Copyright 2002-2016 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc.method.annotation;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.Collections;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.beans.FatalBeanException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.ModelMethodProcessor;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.FlashMap;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.web.util.NestedServletException;
import static org.junit.Assert.*;
/**
* Test fixture with {@link ExceptionHandlerExceptionResolver}.
*
* @author Rossen Stoyanchev
* @author Arjen Poutsma
* @author Kazuki Shimizu
* @author Brian Clozel
* @since 3.1
*/
@SuppressWarnings("unused")
public class ExceptionHandlerExceptionResolverTests {
private static int RESOLVER_COUNT;
private static int HANDLER_COUNT;
private ExceptionHandlerExceptionResolver resolver;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
@BeforeClass
public static void setupOnce() {
ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
resolver.afterPropertiesSet();
RESOLVER_COUNT = resolver.getArgumentResolvers().getResolvers().size();
HANDLER_COUNT = resolver.getReturnValueHandlers().getHandlers().size();
}
@Before
public void setup() throws Exception {
this.resolver = new ExceptionHandlerExceptionResolver();
this.resolver.setWarnLogCategory(this.resolver.getClass().getName());
this.request = new MockHttpServletRequest("GET", "/");
this.request.setAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
this.response = new MockHttpServletResponse();
}
@SuppressWarnings("ConstantConditions")
@Test
public void nullHandler() {
Object handler = null;
this.resolver.afterPropertiesSet();
ModelAndView mav = this.resolver.resolveException(this.request, this.response, handler, null);
assertNull("Exception can be resolved only if there is a HandlerMethod", mav);
}
@Test
public void setCustomArgumentResolvers() throws Exception {
HandlerMethodArgumentResolver resolver = new ServletRequestMethodArgumentResolver();
this.resolver.setCustomArgumentResolvers(Collections.singletonList(resolver));
this.resolver.afterPropertiesSet();
assertTrue(this.resolver.getArgumentResolvers().getResolvers().contains(resolver));
assertMethodProcessorCount(RESOLVER_COUNT + 1, HANDLER_COUNT);
}
@Test
public void setArgumentResolvers() throws Exception {
HandlerMethodArgumentResolver resolver = new ServletRequestMethodArgumentResolver();
this.resolver.setArgumentResolvers(Collections.singletonList(resolver));
this.resolver.afterPropertiesSet();
assertMethodProcessorCount(1, HANDLER_COUNT);
}
@Test
public void setCustomReturnValueHandlers() {
HandlerMethodReturnValueHandler handler = new ViewNameMethodReturnValueHandler();
this.resolver.setCustomReturnValueHandlers(Collections.singletonList(handler));
this.resolver.afterPropertiesSet();
assertTrue(this.resolver.getReturnValueHandlers().getHandlers().contains(handler));
assertMethodProcessorCount(RESOLVER_COUNT, HANDLER_COUNT + 1);
}
@Test
public void setReturnValueHandlers() {
HandlerMethodReturnValueHandler handler = new ModelMethodProcessor();
this.resolver.setReturnValueHandlers(Collections.singletonList(handler));
this.resolver.afterPropertiesSet();
assertMethodProcessorCount(RESOLVER_COUNT, 1);
}
@Test
public void resolveNoExceptionHandlerForException() throws NoSuchMethodException {
Exception npe = new NullPointerException();
HandlerMethod handlerMethod = new HandlerMethod(new IoExceptionController(), "handle");
this.resolver.afterPropertiesSet();
ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod, npe);
assertNull("NPE should not have been handled", mav);
}
@Test
public void resolveExceptionModelAndView() throws NoSuchMethodException {
IllegalArgumentException ex = new IllegalArgumentException("Bad argument");
HandlerMethod handlerMethod = new HandlerMethod(new ModelAndViewController(), "handle");
this.resolver.afterPropertiesSet();
ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod, ex);
assertNotNull(mav);
assertFalse(mav.isEmpty());
assertEquals("errorView", mav.getViewName());
assertEquals("Bad argument", mav.getModel().get("detail"));
}
@Test
public void resolveExceptionResponseBody() throws UnsupportedEncodingException, NoSuchMethodException {
IllegalArgumentException ex = new IllegalArgumentException();
HandlerMethod handlerMethod = new HandlerMethod(new ResponseBodyController(), "handle");
this.resolver.afterPropertiesSet();
ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod, ex);
assertNotNull(mav);
assertTrue(mav.isEmpty());
assertEquals("IllegalArgumentException", this.response.getContentAsString());
}
@Test
public void resolveExceptionResponseWriter() throws Exception {
IllegalArgumentException ex = new IllegalArgumentException();
HandlerMethod handlerMethod = new HandlerMethod(new ResponseWriterController(), "handle");
this.resolver.afterPropertiesSet();
ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod, ex);
assertNotNull(mav);
assertTrue(mav.isEmpty());
assertEquals("IllegalArgumentException", this.response.getContentAsString());
}
@Test // SPR-13546
public void resolveExceptionModelAtArgument() throws Exception {
IllegalArgumentException ex = new IllegalArgumentException();
HandlerMethod handlerMethod = new HandlerMethod(new ModelArgumentController(), "handle");
this.resolver.afterPropertiesSet();
ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod, ex);
assertNotNull(mav);
assertEquals(1, mav.getModelMap().size());
assertEquals("IllegalArgumentException", mav.getModelMap().get("exceptionClassName"));
}
@Test // SPR-14651
public void resolveRedirectAttributesAtArgument() throws Exception {
IllegalArgumentException ex = new IllegalArgumentException();
HandlerMethod handlerMethod = new HandlerMethod(new RedirectAttributesController(), "handle");
this.resolver.afterPropertiesSet();
ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod, ex);
assertNotNull(mav);
assertEquals("redirect:/", mav.getViewName());
FlashMap flashMap = (FlashMap) this.request.getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE);
assertNotNull("output FlashMap should exist", flashMap);
assertEquals("IllegalArgumentException", flashMap.get("exceptionClassName"));
}
@Test
public void resolveExceptionGlobalHandler() throws Exception {
AnnotationConfigApplicationContext cxt = new AnnotationConfigApplicationContext(MyConfig.class);
this.resolver.setApplicationContext(cxt);
this.resolver.afterPropertiesSet();
IllegalAccessException ex = new IllegalAccessException();
HandlerMethod handlerMethod = new HandlerMethod(new ResponseBodyController(), "handle");
ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod, ex);
assertNotNull("Exception was not handled", mav);
assertTrue(mav.isEmpty());
assertEquals("AnotherTestExceptionResolver: IllegalAccessException", this.response.getContentAsString());
}
@Test
public void resolveExceptionGlobalHandlerOrdered() throws Exception {
AnnotationConfigApplicationContext cxt = new AnnotationConfigApplicationContext(MyConfig.class);
this.resolver.setApplicationContext(cxt);
this.resolver.afterPropertiesSet();
IllegalStateException ex = new IllegalStateException();
HandlerMethod handlerMethod = new HandlerMethod(new ResponseBodyController(), "handle");
ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod, ex);
assertNotNull("Exception was not handled", mav);
assertTrue(mav.isEmpty());
assertEquals("TestExceptionResolver: IllegalStateException", this.response.getContentAsString());
}
@Test // SPR-12605
public void resolveExceptionWithHandlerMethodArg() throws Exception {
AnnotationConfigApplicationContext cxt = new AnnotationConfigApplicationContext(MyConfig.class);
this.resolver.setApplicationContext(cxt);
this.resolver.afterPropertiesSet();
ArrayIndexOutOfBoundsException ex = new ArrayIndexOutOfBoundsException();
HandlerMethod handlerMethod = new HandlerMethod(new ResponseBodyController(), "handle");
ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod, ex);
assertNotNull("Exception was not handled", mav);
assertTrue(mav.isEmpty());
assertEquals("HandlerMethod: handle", this.response.getContentAsString());
}
@Test
public void resolveExceptionWithAssertionError() throws Exception {
AnnotationConfigApplicationContext cxt = new AnnotationConfigApplicationContext(MyConfig.class);
this.resolver.setApplicationContext(cxt);
this.resolver.afterPropertiesSet();
AssertionError err = new AssertionError("argh");
HandlerMethod handlerMethod = new HandlerMethod(new ResponseBodyController(), "handle");
ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod,
new NestedServletException("Handler dispatch failed", err));
assertNotNull("Exception was not handled", mav);
assertTrue(mav.isEmpty());
assertEquals(err.toString(), this.response.getContentAsString());
}
@Test
public void resolveExceptionWithAssertionErrorAsRootCause() throws Exception {
AnnotationConfigApplicationContext cxt = new AnnotationConfigApplicationContext(MyConfig.class);
this.resolver.setApplicationContext(cxt);
this.resolver.afterPropertiesSet();
AssertionError err = new AssertionError("argh");
FatalBeanException ex = new FatalBeanException("wrapped", err);
HandlerMethod handlerMethod = new HandlerMethod(new ResponseBodyController(), "handle");
ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod, ex);
assertNotNull("Exception was not handled", mav);
assertTrue(mav.isEmpty());
assertEquals(err.toString(), this.response.getContentAsString());
}
@Test
public void resolveExceptionControllerAdviceHandler() throws Exception {
AnnotationConfigApplicationContext cxt = new AnnotationConfigApplicationContext(MyControllerAdviceConfig.class);
this.resolver.setApplicationContext(cxt);
this.resolver.afterPropertiesSet();
IllegalStateException ex = new IllegalStateException();
HandlerMethod handlerMethod = new HandlerMethod(new ResponseBodyController(), "handle");
ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod, ex);
assertNotNull("Exception was not handled", mav);
assertTrue(mav.isEmpty());
assertEquals("BasePackageTestExceptionResolver: IllegalStateException", this.response.getContentAsString());
}
@Test
public void resolveExceptionControllerAdviceNoHandler() throws Exception {
AnnotationConfigApplicationContext cxt = new AnnotationConfigApplicationContext(MyControllerAdviceConfig.class);
this.resolver.setApplicationContext(cxt);
this.resolver.afterPropertiesSet();
IllegalStateException ex = new IllegalStateException();
ModelAndView mav = this.resolver.resolveException(this.request, this.response, null, ex);
assertNotNull("Exception was not handled", mav);
assertTrue(mav.isEmpty());
assertEquals("DefaultTestExceptionResolver: IllegalStateException", this.response.getContentAsString());
}
private void assertMethodProcessorCount(int resolverCount, int handlerCount) {
assertEquals(resolverCount, this.resolver.getArgumentResolvers().getResolvers().size());
assertEquals(handlerCount, this.resolver.getReturnValueHandlers().getHandlers().size());
}
@Controller
static class ModelAndViewController {
public void handle() {}
@ExceptionHandler
public ModelAndView handle(Exception ex) throws IOException {
return new ModelAndView("errorView", "detail", ex.getMessage());
}
}
@Controller
static class ResponseWriterController {
public void handle() {}
@ExceptionHandler
public void handleException(Exception ex, Writer writer) throws IOException {
writer.write(ClassUtils.getShortName(ex.getClass()));
}
}
@Controller
static class ResponseBodyController {
public void handle() {}
@ExceptionHandler
@ResponseBody
public String handleException(IllegalArgumentException ex) {
return ClassUtils.getShortName(ex.getClass());
}
}
@Controller
static class IoExceptionController {
public void handle() {}
@ExceptionHandler(value = IOException.class)
public void handleException() {
}
}
@Controller
static class ModelArgumentController {
public void handle() {}
@ExceptionHandler
public void handleException(Exception ex, Model model) {
model.addAttribute("exceptionClassName", ClassUtils.getShortName(ex.getClass()));
}
}
@Controller
static class RedirectAttributesController {
public void handle() {}
@ExceptionHandler
public String handleException(Exception ex, RedirectAttributes redirectAttributes) {
redirectAttributes.addFlashAttribute("exceptionClassName", ClassUtils.getShortName(ex.getClass()));
return "redirect:/";
}
}
@RestControllerAdvice
@Order(1)
static class TestExceptionResolver {
@ExceptionHandler
public String handleException(IllegalStateException ex) {
return "TestExceptionResolver: " + ClassUtils.getShortName(ex.getClass());
}
@ExceptionHandler(ArrayIndexOutOfBoundsException.class)
public String handleWithHandlerMethod(HandlerMethod handlerMethod) {
return "HandlerMethod: " + handlerMethod.getMethod().getName();
}
@ExceptionHandler(AssertionError.class)
public String handleAssertionError(Error err) {
return err.toString();
}
}
@RestControllerAdvice
@Order(2)
static class AnotherTestExceptionResolver {
@ExceptionHandler({IllegalStateException.class, IllegalAccessException.class})
public String handleException(Exception ex) {
return "AnotherTestExceptionResolver: " + ClassUtils.getShortName(ex.getClass());
}
}
@Configuration
static class MyConfig {
@Bean
public TestExceptionResolver testExceptionResolver() {
return new TestExceptionResolver();
}
@Bean
public AnotherTestExceptionResolver anotherTestExceptionResolver() {
return new AnotherTestExceptionResolver();
}
}
@RestControllerAdvice("java.lang")
@Order(1)
static class NotCalledTestExceptionResolver {
@ExceptionHandler
public String handleException(IllegalStateException ex) {
return "NotCalledTestExceptionResolver: " + ClassUtils.getShortName(ex.getClass());
}
}
@RestControllerAdvice("org.springframework.web.servlet.mvc.method.annotation")
@Order(2)
static class BasePackageTestExceptionResolver {
@ExceptionHandler
public String handleException(IllegalStateException ex) {
return "BasePackageTestExceptionResolver: " + ClassUtils.getShortName(ex.getClass());
}
}
@RestControllerAdvice
@Order(3)
static class DefaultTestExceptionResolver {
@ExceptionHandler
public String handleException(IllegalStateException ex) {
return "DefaultTestExceptionResolver: " + ClassUtils.getShortName(ex.getClass());
}
}
@Configuration
static class MyControllerAdviceConfig {
@Bean
public NotCalledTestExceptionResolver notCalledTestExceptionResolver() {
return new NotCalledTestExceptionResolver();
}
@Bean
public BasePackageTestExceptionResolver basePackageTestExceptionResolver() {
return new BasePackageTestExceptionResolver();
}
@Bean
public DefaultTestExceptionResolver defaultTestExceptionResolver() {
return new DefaultTestExceptionResolver();
}
}
}