View Javadoc
1   /*
2    * Copyright 2002-2013 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.web.servlet.mvc.method.annotation;
18  
19  import java.io.IOException;
20  import java.io.UnsupportedEncodingException;
21  import java.io.Writer;
22  import java.util.Arrays;
23  
24  import org.junit.Before;
25  import org.junit.BeforeClass;
26  import org.junit.Test;
27  
28  import org.springframework.context.annotation.AnnotationConfigApplicationContext;
29  import org.springframework.context.annotation.Bean;
30  import org.springframework.context.annotation.Configuration;
31  import org.springframework.core.annotation.Order;
32  import org.springframework.mock.web.test.MockHttpServletRequest;
33  import org.springframework.mock.web.test.MockHttpServletResponse;
34  import org.springframework.stereotype.Controller;
35  import org.springframework.util.ClassUtils;
36  import org.springframework.web.bind.annotation.ControllerAdvice;
37  import org.springframework.web.bind.annotation.ExceptionHandler;
38  import org.springframework.web.bind.annotation.ResponseBody;
39  import org.springframework.web.method.HandlerMethod;
40  import org.springframework.web.method.annotation.ModelMethodProcessor;
41  import org.springframework.web.method.support.HandlerMethodArgumentResolver;
42  import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
43  import org.springframework.web.servlet.ModelAndView;
44  
45  import static org.junit.Assert.*;
46  
47  /**
48   * Test fixture with {@link ExceptionHandlerExceptionResolver}.
49   *
50   * @author Rossen Stoyanchev
51   * @author Arjen Poutsma
52   * @since 3.1
53   */
54  public class ExceptionHandlerExceptionResolverTests {
55  
56  	private static int RESOLVER_COUNT;
57  
58  	private static int HANDLER_COUNT;
59  
60  	private ExceptionHandlerExceptionResolver resolver;
61  
62  	private MockHttpServletRequest request;
63  
64  	private MockHttpServletResponse response;
65  
66  	@BeforeClass
67  	public static void setupOnce() {
68  		ExceptionHandlerExceptionResolver r = new ExceptionHandlerExceptionResolver();
69  		r.afterPropertiesSet();
70  		RESOLVER_COUNT = r.getArgumentResolvers().getResolvers().size();
71  		HANDLER_COUNT = r.getReturnValueHandlers().getHandlers().size();
72  	}
73  
74  	@Before
75  	public void setUp() throws Exception {
76  		this.resolver = new ExceptionHandlerExceptionResolver();
77  		this.request = new MockHttpServletRequest("GET", "/");
78  		this.response = new MockHttpServletResponse();
79  	}
80  
81  	@Test
82  	public void nullHandler() {
83  		Object handler = null;
84  		this.resolver.afterPropertiesSet();
85  		ModelAndView mav = this.resolver.resolveException(this.request, this.response, handler, null);
86  		assertNull("Exception can be resolved only if there is a HandlerMethod", mav);
87  	}
88  
89  	@Test
90  	public void setCustomArgumentResolvers() throws Exception {
91  		HandlerMethodArgumentResolver resolver = new ServletRequestMethodArgumentResolver();
92  		this.resolver.setCustomArgumentResolvers(Arrays.asList(resolver));
93  		this.resolver.afterPropertiesSet();
94  
95  		assertTrue(this.resolver.getArgumentResolvers().getResolvers().contains(resolver));
96  		assertMethodProcessorCount(RESOLVER_COUNT + 1, HANDLER_COUNT);
97  	}
98  
99  	@Test
100 	public void setArgumentResolvers() throws Exception {
101 		HandlerMethodArgumentResolver resolver = new ServletRequestMethodArgumentResolver();
102 		this.resolver.setArgumentResolvers(Arrays.asList(resolver));
103 		this.resolver.afterPropertiesSet();
104 
105 		assertMethodProcessorCount(1, HANDLER_COUNT);
106 	}
107 
108 	@Test
109 	public void setCustomReturnValueHandlers() {
110 		HandlerMethodReturnValueHandler handler = new ViewNameMethodReturnValueHandler();
111 		this.resolver.setCustomReturnValueHandlers(Arrays.asList(handler));
112 		this.resolver.afterPropertiesSet();
113 
114 		assertTrue(this.resolver.getReturnValueHandlers().getHandlers().contains(handler));
115 		assertMethodProcessorCount(RESOLVER_COUNT, HANDLER_COUNT + 1);
116 	}
117 
118 	@Test
119 	public void setReturnValueHandlers() {
120 		HandlerMethodReturnValueHandler handler = new ModelMethodProcessor();
121 		this.resolver.setReturnValueHandlers(Arrays.asList(handler));
122 		this.resolver.afterPropertiesSet();
123 
124 		assertMethodProcessorCount(RESOLVER_COUNT, 1);
125 	}
126 
127 	@Test
128 	public void resolveNoExceptionHandlerForException() throws NoSuchMethodException {
129 		Exception npe = new NullPointerException();
130 		HandlerMethod handlerMethod = new HandlerMethod(new IoExceptionController(), "handle");
131 		this.resolver.afterPropertiesSet();
132 		ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod, npe);
133 
134 		assertNull("NPE should not have been handled", mav);
135 	}
136 
137 	@Test
138 	public void resolveExceptionModelAndView() throws NoSuchMethodException {
139 		IllegalArgumentException ex = new IllegalArgumentException("Bad argument");
140 		HandlerMethod handlerMethod = new HandlerMethod(new ModelAndViewController(), "handle");
141 		this.resolver.afterPropertiesSet();
142 		ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod, ex);
143 
144 		assertNotNull(mav);
145 		assertFalse(mav.isEmpty());
146 		assertEquals("errorView", mav.getViewName());
147 		assertEquals("Bad argument", mav.getModel().get("detail"));
148 	}
149 
150 	@Test
151 	public void resolveExceptionResponseBody() throws UnsupportedEncodingException, NoSuchMethodException {
152 		IllegalArgumentException ex = new IllegalArgumentException();
153 		HandlerMethod handlerMethod = new HandlerMethod(new ResponseBodyController(), "handle");
154 		this.resolver.afterPropertiesSet();
155 		ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod, ex);
156 
157 		assertNotNull(mav);
158 		assertTrue(mav.isEmpty());
159 		assertEquals("IllegalArgumentException", this.response.getContentAsString());
160 	}
161 
162 	@Test
163 	public void resolveExceptionResponseWriter() throws Exception {
164 		IllegalArgumentException ex = new IllegalArgumentException();
165 		HandlerMethod handlerMethod = new HandlerMethod(new ResponseWriterController(), "handle");
166 		this.resolver.afterPropertiesSet();
167 		ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod, ex);
168 
169 		assertNotNull(mav);
170 		assertTrue(mav.isEmpty());
171 		assertEquals("IllegalArgumentException", this.response.getContentAsString());
172 	}
173 
174 	@Test
175 	public void resolveExceptionGlobalHandler() throws Exception {
176 		AnnotationConfigApplicationContext cxt = new AnnotationConfigApplicationContext(MyConfig.class);
177 		this.resolver.setApplicationContext(cxt);
178 		this.resolver.afterPropertiesSet();
179 
180 		IllegalAccessException ex = new IllegalAccessException();
181 		HandlerMethod handlerMethod = new HandlerMethod(new ResponseBodyController(), "handle");
182 		ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod, ex);
183 
184 		assertNotNull("Exception was not handled", mav);
185 		assertTrue(mav.isEmpty());
186 		assertEquals("AnotherTestExceptionResolver: IllegalAccessException", this.response.getContentAsString());
187 	}
188 
189 	@Test
190 	public void resolveExceptionGlobalHandlerOrdered() throws Exception {
191 		AnnotationConfigApplicationContext cxt = new AnnotationConfigApplicationContext(MyConfig.class);
192 		this.resolver.setApplicationContext(cxt);
193 		this.resolver.afterPropertiesSet();
194 
195 		IllegalStateException ex = new IllegalStateException();
196 		HandlerMethod handlerMethod = new HandlerMethod(new ResponseBodyController(), "handle");
197 		ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod, ex);
198 
199 		assertNotNull("Exception was not handled", mav);
200 		assertTrue(mav.isEmpty());
201 		assertEquals("TestExceptionResolver: IllegalStateException", this.response.getContentAsString());
202 	}
203 
204 	@Test
205 	public void resolveExceptionControllerAdviceHandler() throws Exception {
206 		AnnotationConfigApplicationContext cxt = new AnnotationConfigApplicationContext(MyControllerAdviceConfig.class);
207 		this.resolver.setApplicationContext(cxt);
208 		this.resolver.afterPropertiesSet();
209 
210 		IllegalStateException ex = new IllegalStateException();
211 		HandlerMethod handlerMethod = new HandlerMethod(new ResponseBodyController(), "handle");
212 		ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod, ex);
213 
214 		assertNotNull("Exception was not handled", mav);
215 		assertTrue(mav.isEmpty());
216 		assertEquals("BasePackageTestExceptionResolver: IllegalStateException", this.response.getContentAsString());
217 	}
218 
219 	@Test
220 	public void resolveExceptionControllerAdviceNoHandler() throws Exception {
221 		AnnotationConfigApplicationContext cxt = new AnnotationConfigApplicationContext(MyControllerAdviceConfig.class);
222 		this.resolver.setApplicationContext(cxt);
223 		this.resolver.afterPropertiesSet();
224 
225 		IllegalStateException ex = new IllegalStateException();
226 		ModelAndView mav = this.resolver.resolveException(this.request, this.response, null, ex);
227 
228 		assertNotNull("Exception was not handled", mav);
229 		assertTrue(mav.isEmpty());
230 		assertEquals("DefaultTestExceptionResolver: IllegalStateException", this.response.getContentAsString());
231 	}
232 
233 
234 	private void assertMethodProcessorCount(int resolverCount, int handlerCount) {
235 		assertEquals(resolverCount, this.resolver.getArgumentResolvers().getResolvers().size());
236 		assertEquals(handlerCount, this.resolver.getReturnValueHandlers().getHandlers().size());
237 	}
238 
239 	@Controller
240 	static class ModelAndViewController {
241 
242 		public void handle() {}
243 
244 		@ExceptionHandler
245 		public ModelAndView handle(Exception ex) throws IOException {
246 			return new ModelAndView("errorView", "detail", ex.getMessage());
247 		}
248 	}
249 
250 	@Controller
251 	static class ResponseWriterController {
252 
253 		public void handle() {}
254 
255 		@ExceptionHandler
256 		public void handleException(Exception ex, Writer writer) throws IOException {
257 			writer.write(ClassUtils.getShortName(ex.getClass()));
258 		}
259 	}
260 
261 	@Controller
262 	static class ResponseBodyController {
263 
264 		public void handle() {}
265 
266 		@ExceptionHandler
267 		@ResponseBody
268 		public String handleException(IllegalArgumentException ex) {
269 			return ClassUtils.getShortName(ex.getClass());
270 		}
271 	}
272 
273 	@Controller
274 	static class IoExceptionController {
275 
276 		public void handle() {}
277 
278 		@ExceptionHandler(value=IOException.class)
279 		public void handleException() {
280 		}
281 	}
282 
283 	@ControllerAdvice
284 	@Order(1)
285 	static class TestExceptionResolver {
286 
287 		@ExceptionHandler
288 		@ResponseBody
289 		public String handleException(IllegalStateException ex) {
290 			return "TestExceptionResolver: " + ClassUtils.getShortName(ex.getClass());
291 		}
292 	}
293 
294 	@ControllerAdvice
295 	@Order(2)
296 	static class AnotherTestExceptionResolver {
297 
298 		@ExceptionHandler({IllegalStateException.class, IllegalAccessException.class})
299 		@ResponseBody
300 		public String handleException(Exception ex) {
301 			return "AnotherTestExceptionResolver: " + ClassUtils.getShortName(ex.getClass());
302 		}
303 	}
304 
305 	@Configuration
306 	static class MyConfig {
307 
308 		@Bean public TestExceptionResolver testExceptionResolver() {
309 			return new TestExceptionResolver();
310 		}
311 
312 		@Bean public AnotherTestExceptionResolver anotherTestExceptionResolver() {
313 			return new AnotherTestExceptionResolver();
314 		}
315 	}
316 
317 	@ControllerAdvice("java.lang")
318 	@Order(1)
319 	static class NotCalledTestExceptionResolver {
320 
321 		@ExceptionHandler
322 		@ResponseBody
323 		public String handleException(IllegalStateException ex) {
324 			return "NotCalledTestExceptionResolver: " + ClassUtils.getShortName(ex.getClass());
325 		}
326 	}
327 
328 	@ControllerAdvice("org.springframework.web.servlet.mvc.method.annotation")
329 	@Order(2)
330 	static class BasePackageTestExceptionResolver {
331 
332 		@ExceptionHandler
333 		@ResponseBody
334 		public String handleException(IllegalStateException ex) {
335 			return "BasePackageTestExceptionResolver: " + ClassUtils.getShortName(ex.getClass());
336 		}
337 	}
338 
339 	@ControllerAdvice
340 	@Order(3)
341 	static class DefaultTestExceptionResolver {
342 
343 		@ExceptionHandler
344 		@ResponseBody
345 		public String handleException(IllegalStateException ex) {
346 			return "DefaultTestExceptionResolver: " + ClassUtils.getShortName(ex.getClass());
347 		}
348 	}
349 
350 	@Configuration
351 	static class MyControllerAdviceConfig {
352 		@Bean public NotCalledTestExceptionResolver notCalledTestExceptionResolver() {
353 			return new NotCalledTestExceptionResolver();
354 		}
355 
356 		@Bean public BasePackageTestExceptionResolver basePackageTestExceptionResolver() {
357 			return new BasePackageTestExceptionResolver();
358 		}
359 
360 		@Bean public DefaultTestExceptionResolver defaultTestExceptionResolver() {
361 			return new DefaultTestExceptionResolver();
362 		}
363 	}
364 }