1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.springframework.web.servlet.mvc.method.annotation;
18
19 import java.lang.reflect.Method;
20 import java.text.SimpleDateFormat;
21 import java.util.Arrays;
22 import java.util.Collection;
23 import java.util.Date;
24
25 import org.junit.Test;
26 import org.junit.runner.RunWith;
27 import org.junit.runners.Parameterized;
28 import org.junit.runners.Parameterized.Parameters;
29
30 import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
31 import org.springframework.aop.interceptor.SimpleTraceInterceptor;
32 import org.springframework.aop.support.DefaultPointcutAdvisor;
33 import org.springframework.aop.support.StaticMethodMatcherPointcut;
34 import org.springframework.beans.factory.support.RootBeanDefinition;
35 import org.springframework.beans.propertyeditors.CustomDateEditor;
36 import org.springframework.core.annotation.AnnotationUtils;
37 import org.springframework.mock.web.test.MockHttpServletRequest;
38 import org.springframework.mock.web.test.MockHttpServletResponse;
39 import org.springframework.stereotype.Controller;
40 import org.springframework.ui.Model;
41 import org.springframework.web.bind.WebDataBinder;
42 import org.springframework.web.bind.annotation.ExceptionHandler;
43 import org.springframework.web.bind.annotation.InitBinder;
44 import org.springframework.web.bind.annotation.ModelAttribute;
45 import org.springframework.web.bind.annotation.RequestHeader;
46 import org.springframework.web.bind.annotation.RequestMapping;
47 import org.springframework.web.bind.annotation.RequestMethod;
48 import org.springframework.web.bind.annotation.RequestParam;
49 import org.springframework.web.bind.annotation.ResponseBody;
50 import org.springframework.web.context.support.GenericWebApplicationContext;
51 import org.springframework.web.servlet.HandlerExecutionChain;
52 import org.springframework.web.servlet.ModelAndView;
53
54 import static org.junit.Assert.*;
55
56
57
58
59
60
61
62
63 @RunWith(Parameterized.class)
64 public class HandlerMethodAnnotationDetectionTests {
65
66 @Parameters
67 public static Collection<Object[]> handlerTypes() {
68 Object[][] array = new Object[12][2];
69
70 array[0] = new Object[] { SimpleController.class, true};
71 array[1] = new Object[] { SimpleController.class, false};
72
73 array[2] = new Object[] { AbstractClassController.class, true };
74 array[3] = new Object[] { AbstractClassController.class, false };
75
76 array[4] = new Object[] { ParameterizedAbstractClassController.class, false};
77 array[5] = new Object[] { ParameterizedAbstractClassController.class, false};
78
79 array[6] = new Object[] { InterfaceController.class, true };
80 array[7] = new Object[] { InterfaceController.class, false };
81
82 array[8] = new Object[] { ParameterizedInterfaceController.class, false};
83 array[9] = new Object[] { ParameterizedInterfaceController.class, false};
84
85 array[10] = new Object[] { SupportClassController.class, true};
86 array[11] = new Object[] { SupportClassController.class, false};
87
88 return Arrays.asList(array);
89 }
90
91 private RequestMappingHandlerMapping handlerMapping = new RequestMappingHandlerMapping();
92
93 private RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter();
94
95 private ExceptionHandlerExceptionResolver exceptionResolver = new ExceptionHandlerExceptionResolver();
96
97 public HandlerMethodAnnotationDetectionTests(final Class<?> controllerType, boolean useAutoProxy) {
98 GenericWebApplicationContext context = new GenericWebApplicationContext();
99 context.registerBeanDefinition("controller", new RootBeanDefinition(controllerType));
100 context.registerBeanDefinition("handlerMapping", new RootBeanDefinition(RequestMappingHandlerMapping.class));
101 context.registerBeanDefinition("handlerAdapter", new RootBeanDefinition(RequestMappingHandlerAdapter.class));
102 context.registerBeanDefinition("exceptionResolver", new RootBeanDefinition(ExceptionHandlerExceptionResolver.class));
103 if (useAutoProxy) {
104 DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
105 autoProxyCreator.setBeanFactory(context.getBeanFactory());
106 context.getBeanFactory().addBeanPostProcessor(autoProxyCreator);
107 context.registerBeanDefinition("controllerAdvice", new RootBeanDefinition(ControllerAdvisor.class));
108 }
109 context.refresh();
110
111 this.handlerMapping = context.getBean(RequestMappingHandlerMapping.class);
112 this.handlerAdapter = context.getBean(RequestMappingHandlerAdapter.class);
113 this.exceptionResolver = context.getBean(ExceptionHandlerExceptionResolver.class);
114 }
115
116 class TestPointcut extends StaticMethodMatcherPointcut {
117 @Override
118 public boolean matches(Method method, Class<?> clazz) {
119 return method.getName().equals("hashCode");
120 }
121 }
122
123 @Test
124 public void testRequestMappingMethod() throws Exception {
125 String datePattern = "MM:dd:yyyy";
126 SimpleDateFormat dateFormat = new SimpleDateFormat(datePattern);
127 String dateA = "11:01:2011";
128 String dateB = "11:02:2011";
129
130 MockHttpServletRequest request = new MockHttpServletRequest("POST", "/path1/path2");
131 request.setParameter("datePattern", datePattern);
132 request.addHeader("header1", dateA);
133 request.addHeader("header2", dateB);
134
135 HandlerExecutionChain chain = handlerMapping.getHandler(request);
136 assertNotNull(chain);
137
138 ModelAndView mav = handlerAdapter.handle(request, new MockHttpServletResponse(), chain.getHandler());
139
140 assertEquals(mav.getModel().get("attr1"), dateFormat.parse(dateA));
141 assertEquals(mav.getModel().get("attr2"), dateFormat.parse(dateB));
142
143 MockHttpServletResponse response = new MockHttpServletResponse();
144 exceptionResolver.resolveException(request, response, chain.getHandler(), new Exception("failure"));
145 assertEquals("text/plain;charset=ISO-8859-1", response.getHeader("Content-Type"));
146 assertEquals("failure", response.getContentAsString());
147 }
148
149
150
151
152
153 @Controller
154 static class SimpleController {
155
156 @InitBinder
157 public void initBinder(WebDataBinder dataBinder, @RequestParam("datePattern") String pattern) {
158 CustomDateEditor dateEditor = new CustomDateEditor(new SimpleDateFormat(pattern), false);
159 dataBinder.registerCustomEditor(Date.class, dateEditor);
160 }
161
162 @ModelAttribute
163 public void initModel(@RequestHeader("header1") Date date, Model model) {
164 model.addAttribute("attr1", date);
165 }
166
167 @RequestMapping(value="/path1/path2", method=RequestMethod.POST)
168 @ModelAttribute("attr2")
169 public Date handle(@RequestHeader("header2") Date date) throws Exception {
170 return date;
171 }
172
173 @ExceptionHandler(Exception.class)
174 @ResponseBody
175 public String handleException(Exception exception) {
176 return exception.getMessage();
177 }
178 }
179
180
181 @Controller
182 static abstract class MappingAbstractClass {
183
184 @InitBinder
185 public abstract void initBinder(WebDataBinder dataBinder, String pattern);
186
187 @ModelAttribute
188 public abstract void initModel(Date date, Model model);
189
190 @RequestMapping(value="/path1/path2", method=RequestMethod.POST)
191 @ModelAttribute("attr2")
192 public abstract Date handle(Date date, Model model) throws Exception;
193
194 @ExceptionHandler(Exception.class)
195 @ResponseBody
196 public abstract String handleException(Exception exception);
197 }
198
199
200
201
202
203
204 static class AbstractClassController extends MappingAbstractClass {
205
206 @Override
207 public void initBinder(WebDataBinder dataBinder, @RequestParam("datePattern") String pattern) {
208 CustomDateEditor dateEditor = new CustomDateEditor(new SimpleDateFormat(pattern), false);
209 dataBinder.registerCustomEditor(Date.class, dateEditor);
210 }
211
212 @Override
213 public void initModel(@RequestHeader("header1") Date date, Model model) {
214 model.addAttribute("attr1", date);
215 }
216
217 @Override
218 public Date handle(@RequestHeader("header2") Date date, Model model) throws Exception {
219 return date;
220 }
221
222 @Override
223 public String handleException(Exception exception) {
224 return exception.getMessage();
225 }
226 }
227
228
229
230 @RequestMapping
231 static interface MappingInterface {
232
233 @InitBinder
234 void initBinder(WebDataBinder dataBinder, @RequestParam("datePattern") String thePattern);
235
236 @ModelAttribute
237 void initModel(@RequestHeader("header1") Date date, Model model);
238
239 @RequestMapping(value="/path1/path2", method=RequestMethod.POST)
240 @ModelAttribute("attr2")
241 Date handle(@RequestHeader("header2") Date date, Model model) throws Exception;
242
243 @ExceptionHandler(Exception.class)
244 @ResponseBody
245 String handleException(Exception exception);
246 }
247
248
249
250
251
252
253
254
255
256
257 static class InterfaceController implements MappingInterface {
258
259 @Override
260 public void initBinder(WebDataBinder dataBinder, @RequestParam("datePattern") String thePattern) {
261 CustomDateEditor dateEditor = new CustomDateEditor(new SimpleDateFormat(thePattern), false);
262 dataBinder.registerCustomEditor(Date.class, dateEditor);
263 }
264
265 @Override
266 public void initModel(@RequestHeader("header1") Date date, Model model) {
267 model.addAttribute("attr1", date);
268 }
269
270 @Override
271 public Date handle(@RequestHeader("header2") Date date, Model model) throws Exception {
272 return date;
273 }
274
275 @Override
276 public String handleException(Exception exception) {
277 return exception.getMessage();
278 }
279 }
280
281
282 @Controller
283 static abstract class MappingParameterizedAbstractClass<A, B, C> {
284
285 @InitBinder
286 public abstract void initBinder(WebDataBinder dataBinder, A thePattern);
287
288 @ModelAttribute
289 public abstract void initModel(B date, Model model);
290
291 @RequestMapping(value="/path1/path2", method=RequestMethod.POST)
292 @ModelAttribute("attr2")
293 public abstract Date handle(C date, Model model) throws Exception;
294
295 @ExceptionHandler(Exception.class)
296 @ResponseBody
297 public abstract String handleException(Exception exception);
298 }
299
300
301
302
303
304
305 static class ParameterizedAbstractClassController extends MappingParameterizedAbstractClass<String, Date, Date> {
306
307 @Override
308 public void initBinder(WebDataBinder dataBinder, @RequestParam("datePattern") String thePattern) {
309 CustomDateEditor dateEditor = new CustomDateEditor(new SimpleDateFormat(thePattern), false);
310 dataBinder.registerCustomEditor(Date.class, dateEditor);
311 }
312
313 @Override
314 public void initModel(@RequestHeader("header1") Date date, Model model) {
315 model.addAttribute("attr1", date);
316 }
317
318 @Override
319 public Date handle(@RequestHeader("header2") Date date, Model model) throws Exception {
320 return date;
321 }
322
323 @Override
324 public String handleException(Exception exception) {
325 return exception.getMessage();
326 }
327 }
328
329 @RequestMapping
330 static interface MappingParameterizedInterface<A, B, C> {
331
332 @InitBinder
333 void initBinder(WebDataBinder dataBinder, A thePattern);
334
335 @ModelAttribute
336 void initModel(B date, Model model);
337
338 @RequestMapping(value="/path1/path2", method=RequestMethod.POST)
339 @ModelAttribute("attr2")
340 Date handle(C date, Model model) throws Exception;
341
342 @ExceptionHandler(Exception.class)
343 @ResponseBody
344 String handleException(Exception exception);
345 }
346
347
348
349
350
351
352
353
354 static class ParameterizedInterfaceController implements MappingParameterizedInterface<String, Date, Date> {
355
356 @Override
357 @InitBinder
358 public void initBinder(WebDataBinder dataBinder, @RequestParam("datePattern") String thePattern) {
359 CustomDateEditor dateEditor = new CustomDateEditor(new SimpleDateFormat(thePattern), false);
360 dataBinder.registerCustomEditor(Date.class, dateEditor);
361 }
362
363 @Override
364 @ModelAttribute
365 public void initModel(@RequestHeader("header1") Date date, Model model) {
366 model.addAttribute("attr1", date);
367 }
368
369 @Override
370 @RequestMapping(value="/path1/path2", method=RequestMethod.POST)
371 @ModelAttribute("attr2")
372 public Date handle(@RequestHeader("header2") Date date, Model model) throws Exception {
373 return date;
374 }
375
376 @Override
377 @ExceptionHandler(Exception.class)
378 @ResponseBody
379 public String handleException(Exception exception) {
380 return exception.getMessage();
381 }
382 }
383
384
385
386
387
388
389
390 @Controller
391 static class MappingSupportClass {
392
393 @InitBinder
394 public void initBinder(WebDataBinder dataBinder, @RequestParam("datePattern") String thePattern) {
395 CustomDateEditor dateEditor = new CustomDateEditor(new SimpleDateFormat(thePattern), false);
396 dataBinder.registerCustomEditor(Date.class, dateEditor);
397 }
398
399 @ModelAttribute
400 public void initModel(@RequestHeader("header1") Date date, Model model) {
401 model.addAttribute("attr1", date);
402 }
403
404 @RequestMapping(value="/path2", method=RequestMethod.POST)
405 @ModelAttribute("attr2")
406 public Date handle(@RequestHeader("header2") Date date, Model model) throws Exception {
407 return date;
408 }
409
410 @ExceptionHandler(Exception.class)
411 @ResponseBody
412 public String handleException(Exception exception) {
413 return exception.getMessage();
414 }
415 }
416
417 @Controller
418 @RequestMapping("/path1")
419 static class SupportClassController extends MappingSupportClass {
420 }
421
422
423 @SuppressWarnings("serial")
424 static class ControllerAdvisor extends DefaultPointcutAdvisor {
425
426 public ControllerAdvisor() {
427 super(getControllerPointcut(), new SimpleTraceInterceptor());
428 }
429
430 private static StaticMethodMatcherPointcut getControllerPointcut() {
431 return new StaticMethodMatcherPointcut() {
432 @Override
433 public boolean matches(Method method, Class<?> targetClass) {
434 return ((AnnotationUtils.findAnnotation(targetClass, Controller.class) != null) ||
435 (AnnotationUtils.findAnnotation(targetClass, RequestMapping.class) != null));
436 }
437 };
438 }
439 }
440
441 }