1 /*
2  * Copyright 2002-2010 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 static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertTrue;
21 
22 import java.io.IOException;
23 import java.io.Writer;
24 import java.text.SimpleDateFormat;
25 import java.util.Date;
26 import java.util.GregorianCalendar;
27 import java.util.HashMap;
28 import java.util.Locale;
29 import java.util.Map;
30 
31 import javax.servlet.http.HttpServletRequest;
32 import javax.servlet.http.HttpServletResponse;
33 
34 import org.junit.Test;
35 import org.springframework.beans.factory.support.RootBeanDefinition;
36 import org.springframework.beans.propertyeditors.CustomDateEditor;
37 import org.springframework.context.ApplicationContextInitializer;
38 import org.springframework.mock.web.MockHttpServletRequest;
39 import org.springframework.mock.web.MockHttpServletResponse;
40 import org.springframework.stereotype.Controller;
41 import org.springframework.web.bind.WebDataBinder;
42 import org.springframework.web.bind.annotation.InitBinder;
43 import org.springframework.web.bind.annotation.PathVariable;
44 import org.springframework.web.bind.annotation.RequestMapping;
45 import org.springframework.web.bind.annotation.RequestMethod;
46 import org.springframework.web.context.WebApplicationContext;
47 import org.springframework.web.context.support.GenericWebApplicationContext;
48 import org.springframework.web.method.HandlerMethod;
49 import org.springframework.web.servlet.View;
50 import org.springframework.web.servlet.ViewResolver;
51 import org.springframework.web.servlet.mvc.annotation.UriTemplateServletAnnotationControllerTests;
52 import org.springframework.web.servlet.view.AbstractView;
53 
54 /**
55  * The origin of this test class is {@link UriTemplateServletAnnotationControllerTests}.
56  *
57  * Tests in this class run against the {@link HandlerMethod} infrastructure:
58  * <ul>
59  * 	<li>RequestMappingHandlerMapping
60  * 	<li>RequestMappingHandlerAdapter
61  * 	<li>ExceptionHandlerExceptionResolver
62  * </ul>
63  *
64  * <p>Rather than against the existing infrastructure:
65  * <ul>
66  * 	<li>DefaultAnnotationHandlerMapping
67  * 	<li>AnnotationMethodHandlerAdapter
68  * 	<li>AnnotationMethodHandlerExceptionResolver
69  * </ul>
70  *
71  * @author Rossen Stoyanchev
72  * @since 3.1
73  */
74 public class UriTemplateServletAnnotationControllerHandlerMethodTests extends AbstractServletHandlerMethodTests {
75 
76 	@Test
simple()77 	public void simple() throws Exception {
78 		initServletWithControllers(SimpleUriTemplateController.class);
79 
80 		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/42");
81 		MockHttpServletResponse response = new MockHttpServletResponse();
82 		getServlet().service(request, response);
83 		assertEquals("test-42", response.getContentAsString());
84 	}
85 
86 	@Test
multiple()87 	public void multiple() throws Exception {
88 		initServletWithControllers(MultipleUriTemplateController.class);
89 
90 		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels/42/bookings/21-other");
91 		MockHttpServletResponse response = new MockHttpServletResponse();
92 		getServlet().service(request, response);
93 		assertEquals("test-42-21-other", response.getContentAsString());
94 	}
95 
96 	@Test
pathVarsInModel()97 	public void pathVarsInModel() throws Exception {
98 		final Map<String, Object> pathVars = new HashMap<String, Object>();
99 		pathVars.put("hotel", "42");
100 		pathVars.put("booking", 21);
101 		pathVars.put("other", "other");
102 
103 		WebApplicationContext wac =
104 			initServlet(new ApplicationContextInitializer<GenericWebApplicationContext>() {
105 				public void initialize(GenericWebApplicationContext context) {
106 					RootBeanDefinition beanDef = new RootBeanDefinition(ModelValidatingViewResolver.class);
107 					beanDef.getConstructorArgumentValues().addGenericArgumentValue(pathVars);
108 					context.registerBeanDefinition("viewResolver", beanDef);
109 				}
110 			}, ViewRenderingController.class);
111 
112 		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels/42/bookings/21-other");
113 		getServlet().service(request, new MockHttpServletResponse());
114 
115 		ModelValidatingViewResolver resolver = wac.getBean(ModelValidatingViewResolver.class);
116 		assertEquals(3, resolver.validatedAttrCount);
117 	}
118 
119 	@Test
binding()120 	public void binding() throws Exception {
121 		initServletWithControllers(BindingUriTemplateController.class);
122 
123 		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels/42/dates/2008-11-18");
124 		MockHttpServletResponse response = new MockHttpServletResponse();
125 		getServlet().service(request, response);
126 		assertEquals(200, response.getStatus());
127 
128 		request = new MockHttpServletRequest("GET", "/hotels/42/dates/2008-foo-bar");
129 		response = new MockHttpServletResponse();
130 		getServlet().service(request, response);
131 		assertEquals(400, response.getStatus());
132 
133 		initServletWithControllers(NonBindingUriTemplateController.class);
134 		request = new MockHttpServletRequest("GET", "/hotels/42/dates/2008-foo-bar");
135 		response = new MockHttpServletResponse();
136 		getServlet().service(request, response);
137 		assertEquals(500, response.getStatus());
138 	}
139 
140 	@Test
ambiguous()141 	public void ambiguous() throws Exception {
142 		initServletWithControllers(AmbiguousUriTemplateController.class);
143 
144 		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels/new");
145 		MockHttpServletResponse response = new MockHttpServletResponse();
146 		getServlet().service(request, response);
147 		assertEquals("specific", response.getContentAsString());
148 	}
149 
150 	@Test
relative()151 	public void relative() throws Exception {
152 		initServletWithControllers(RelativePathUriTemplateController.class);
153 
154 		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels/42/bookings/21");
155 		MockHttpServletResponse response = new MockHttpServletResponse();
156 		getServlet().service(request, response);
157 		assertEquals("test-42-21", response.getContentAsString());
158 
159 		request = new MockHttpServletRequest("GET", "/hotels/42/bookings/21.html");
160 		response = new MockHttpServletResponse();
161 		getServlet().service(request, response);
162 		assertEquals("test-42-21", response.getContentAsString());
163 	}
164 
165 	@Test
extension()166 	public void extension() throws Exception {
167 		initServletWithControllers(SimpleUriTemplateController.class);
168 
169 		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/42.xml");
170 		MockHttpServletResponse response = new MockHttpServletResponse();
171 		getServlet().service(request, response);
172 		assertEquals("test-42", response.getContentAsString());
173 
174 	}
175 
176 	@Test
typeConversionError()177 	public void typeConversionError() throws Exception {
178 		initServletWithControllers(SimpleUriTemplateController.class);
179 
180 		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo.xml");
181 		MockHttpServletResponse response = new MockHttpServletResponse();
182 		getServlet().service(request, response);
183 		assertEquals("Invalid response status code", HttpServletResponse.SC_BAD_REQUEST, response.getStatus());
184 	}
185 
186 	@Test
explicitSubPath()187 	public void explicitSubPath() throws Exception {
188 		initServletWithControllers(ExplicitSubPathController.class);
189 
190 		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels/42");
191 		MockHttpServletResponse response = new MockHttpServletResponse();
192 		getServlet().service(request, response);
193 		assertEquals("test-42", response.getContentAsString());
194 	}
195 
196 	@Test
implicitSubPath()197 	public void implicitSubPath() throws Exception {
198 		initServletWithControllers(ImplicitSubPathController.class);
199 
200 		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels/42");
201 		MockHttpServletResponse response = new MockHttpServletResponse();
202 		getServlet().service(request, response);
203 		assertEquals("test-42", response.getContentAsString());
204 	}
205 
206 	@Test
crud()207 	public void crud() throws Exception {
208 		initServletWithControllers(CrudController.class);
209 
210 		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels");
211 		MockHttpServletResponse response = new MockHttpServletResponse();
212 		getServlet().service(request, response);
213 		assertEquals("list", response.getContentAsString());
214 
215 		request = new MockHttpServletRequest("GET", "/hotels/");
216 		response = new MockHttpServletResponse();
217 		getServlet().service(request, response);
218 		assertEquals("list", response.getContentAsString());
219 
220 		request = new MockHttpServletRequest("POST", "/hotels");
221 		response = new MockHttpServletResponse();
222 		getServlet().service(request, response);
223 		assertEquals("create", response.getContentAsString());
224 
225 		request = new MockHttpServletRequest("GET", "/hotels/42");
226 		response = new MockHttpServletResponse();
227 		getServlet().service(request, response);
228 		assertEquals("show-42", response.getContentAsString());
229 
230 		request = new MockHttpServletRequest("GET", "/hotels/42/");
231 		response = new MockHttpServletResponse();
232 		getServlet().service(request, response);
233 		assertEquals("show-42", response.getContentAsString());
234 
235 		request = new MockHttpServletRequest("PUT", "/hotels/42");
236 		response = new MockHttpServletResponse();
237 		getServlet().service(request, response);
238 		assertEquals("createOrUpdate-42", response.getContentAsString());
239 
240 		request = new MockHttpServletRequest("DELETE", "/hotels/42");
241 		response = new MockHttpServletResponse();
242 		getServlet().service(request, response);
243 		assertEquals("remove-42", response.getContentAsString());
244 	}
245 
246 	@Test
methodNotSupported()247 	public void methodNotSupported() throws Exception {
248 		initServletWithControllers(MethodNotAllowedController.class);
249 
250 		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels/1");
251 		MockHttpServletResponse response = new MockHttpServletResponse();
252 		getServlet().service(request, response);
253 		assertEquals(200, response.getStatus());
254 
255 		request = new MockHttpServletRequest("POST", "/hotels/1");
256 		response = new MockHttpServletResponse();
257 		getServlet().service(request, response);
258 		assertEquals(405, response.getStatus());
259 
260 		request = new MockHttpServletRequest("GET", "/hotels");
261 		response = new MockHttpServletResponse();
262 		getServlet().service(request, response);
263 		assertEquals(200, response.getStatus());
264 
265 		request = new MockHttpServletRequest("POST", "/hotels");
266 		response = new MockHttpServletResponse();
267 		getServlet().service(request, response);
268 		assertEquals(405, response.getStatus());
269 	}
270 
271 	@Test
multiPaths()272 	public void multiPaths() throws Exception {
273 		initServletWithControllers(MultiPathController.class);
274 
275 		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/category/page/5");
276 		MockHttpServletResponse response = new MockHttpServletResponse();
277 		getServlet().service(request, response);
278 		assertEquals("handle4-page-5", response.getContentAsString());
279 
280 		request = new MockHttpServletRequest("GET", "/category/page/5.html");
281 		response = new MockHttpServletResponse();
282 		getServlet().service(request, response);
283 		assertEquals("handle4-page-5", response.getContentAsString());
284 	}
285 
286 	@Test
customRegex()287 	public void customRegex() throws Exception {
288 		initServletWithControllers(CustomRegexController.class);
289 
290 		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/42");
291 		MockHttpServletResponse response = new MockHttpServletResponse();
292 		getServlet().service(request, response);
293 		assertEquals("test-42", response.getContentAsString());
294 	}
295 
296 	/*
297 	 * See SPR-6640
298 	 */
299 	@Test
menuTree()300 	public void menuTree() throws Exception {
301 		initServletWithControllers(MenuTreeController.class);
302 
303 		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/book/menu/type/M5");
304 		MockHttpServletResponse response = new MockHttpServletResponse();
305 		getServlet().service(request, response);
306 		assertEquals("M5", response.getContentAsString());
307 	}
308 
309 	/*
310 	 * See SPR-6876
311 	 */
312 	@Test
variableNames()313 	public void variableNames() throws Exception {
314 		initServletWithControllers(VariableNamesController.class);
315 
316 		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test/foo");
317 		MockHttpServletResponse response = new MockHttpServletResponse();
318 		getServlet().service(request, response);
319 		assertEquals("foo-foo", response.getContentAsString());
320 
321 		request = new MockHttpServletRequest("DELETE", "/test/bar");
322 		response = new MockHttpServletResponse();
323 		getServlet().service(request, response);
324 		assertEquals("bar-bar", response.getContentAsString());
325 	}
326 
327 	/*
328 	 * See SPR-8543
329 	 */
330 	@Test
variableNamesWithUrlExtension()331 	public void variableNamesWithUrlExtension() throws Exception {
332 		initServletWithControllers(VariableNamesController.class);
333 
334 		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test/foo.json");
335 		MockHttpServletResponse response = new MockHttpServletResponse();
336 		getServlet().service(request, response);
337 		assertEquals("foo-foo", response.getContentAsString());
338 	}
339 
340 	/*
341 	 * See SPR-6978
342 	 */
343 	@Test
doIt()344 	public void doIt() throws Exception {
345 		initServletWithControllers(Spr6978Controller.class);
346 
347 		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo/100");
348 		MockHttpServletResponse response = new MockHttpServletResponse();
349 		getServlet().service(request, response);
350 		assertEquals("loadEntity:foo:100", response.getContentAsString());
351 
352 		request = new MockHttpServletRequest("POST", "/foo/100");
353 		response = new MockHttpServletResponse();
354 		getServlet().service(request, response);
355 		assertEquals("publish:foo:100", response.getContentAsString());
356 
357 		request = new MockHttpServletRequest("GET", "/module/100");
358 		response = new MockHttpServletResponse();
359 		getServlet().service(request, response);
360 		assertEquals("loadModule:100", response.getContentAsString());
361 
362 		request = new MockHttpServletRequest("POST", "/module/100");
363 		response = new MockHttpServletResponse();
364 		getServlet().service(request, response);
365 		assertEquals("publish:module:100", response.getContentAsString());
366 
367 	}
368 
369 
370 	/*
371 	 * Controllers
372 	 */
373 
374 	@Controller
375 	public static class SimpleUriTemplateController {
376 
377 		@RequestMapping("/{root}")
handle(@athVariableR) int root, Writer writer)378 		public void handle(@PathVariable("root") int root, Writer writer) throws IOException {
379 			assertEquals("Invalid path variable value", 42, root);
380 			writer.write("test-" + root);
381 		}
382 
383 	}
384 
385 	@Controller
386 	public static class MultipleUriTemplateController {
387 
388 		@RequestMapping("/hotels/{hotel}/bookings/{booking}-{other}")
handle(@athVariableR) String hotel, @PathVariable int booking, @PathVariable String other, Writer writer)389 		public void handle(@PathVariable("hotel") String hotel,
390 				@PathVariable int booking,
391 				@PathVariable String other,
392 				Writer writer) throws IOException {
393 			assertEquals("Invalid path variable value", "42", hotel);
394 			assertEquals("Invalid path variable value", 21, booking);
395 			writer.write("test-" + hotel + "-" + booking + "-" + other);
396 		}
397 
398 	}
399 
400 	@Controller
401 	public static class ViewRenderingController {
402 
403 		@RequestMapping("/hotels/{hotel}/bookings/{booking}-{other}")
handle(@athVariableR) String hotel, @PathVariable int booking, @PathVariable String other)404 		public void handle(@PathVariable("hotel") String hotel, @PathVariable int booking, @PathVariable String other) {
405 			assertEquals("Invalid path variable value", "42", hotel);
406 			assertEquals("Invalid path variable value", 21, booking);
407 		}
408 
409 	}
410 
411 	@Controller
412 	public static class BindingUriTemplateController {
413 
414 		@InitBinder
initBinder(WebDataBinder binder, @PathVariable(R) String hotel)415 		public void initBinder(WebDataBinder binder, @PathVariable("hotel") String hotel) {
416 			assertEquals("Invalid path variable value", "42", hotel);
417 			binder.initBeanPropertyAccess();
418 			SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
419 			dateFormat.setLenient(false);
420 			binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
421 		}
422 
423 		@RequestMapping("/hotels/{hotel}/dates/{date}")
handle(@athVariableR) String hotel, @PathVariable Date date, Writer writer)424 		public void handle(@PathVariable("hotel") String hotel, @PathVariable Date date, Writer writer)
425 				throws IOException {
426 			assertEquals("Invalid path variable value", "42", hotel);
427 			assertEquals("Invalid path variable value", new GregorianCalendar(2008, 10, 18).getTime(), date);
428 			writer.write("test-" + hotel);
429 		}
430 	}
431 
432 	@Controller
433 	public static class NonBindingUriTemplateController {
434 
435 		@RequestMapping("/hotels/{hotel}/dates/{date}")
handle(@athVariableR) String hotel, @PathVariable Date date, Writer writer)436 		public void handle(@PathVariable("hotel") String hotel, @PathVariable Date date, Writer writer)
437 				throws IOException {
438 		}
439 
440 	}
441 
442 	@Controller
443 	@RequestMapping("/hotels/{hotel}")
444 	public static class RelativePathUriTemplateController {
445 
446 		@RequestMapping("bookings/{booking}")
handle(@athVariableR) String hotel, @PathVariable int booking, Writer writer)447 		public void handle(@PathVariable("hotel") String hotel, @PathVariable int booking, Writer writer)
448 				throws IOException {
449 			assertEquals("Invalid path variable value", "42", hotel);
450 			assertEquals("Invalid path variable value", 21, booking);
451 			writer.write("test-" + hotel + "-" + booking);
452 		}
453 
454 	}
455 
456 	@Controller
457 	@RequestMapping("/hotels")
458 	public static class AmbiguousUriTemplateController {
459 
460 		@RequestMapping("/{hotel}")
handleVars(@athVariableR) String hotel, Writer writer)461 		public void handleVars(@PathVariable("hotel") String hotel, Writer writer) throws IOException {
462 			assertEquals("Invalid path variable value", "42", hotel);
463 			writer.write("variables");
464 		}
465 
466 		@RequestMapping("/new")
handleSpecific(Writer writer)467 		public void handleSpecific(Writer writer) throws IOException {
468 			writer.write("specific");
469 		}
470 
471 		@RequestMapping("/*")
handleWildCard(Writer writer)472 		public void handleWildCard(Writer writer) throws IOException {
473 			writer.write("wildcard");
474 		}
475 
476 	}
477 
478 	@Controller
479 	@RequestMapping("/hotels/*")
480 	public static class ExplicitSubPathController {
481 
482 		@RequestMapping("{hotel}")
handleHotel(@athVariable String hotel, Writer writer)483 		public void handleHotel(@PathVariable String hotel, Writer writer) throws IOException {
484 			writer.write("test-" + hotel);
485 		}
486 
487 	}
488 
489 	@Controller
490 	@RequestMapping("hotels")
491 	public static class ImplicitSubPathController {
492 
493 		@RequestMapping("{hotel}")
handleHotel(@athVariable String hotel, Writer writer)494 		public void handleHotel(@PathVariable String hotel, Writer writer) throws IOException {
495 			writer.write("test-" + hotel);
496 		}
497 	}
498 
499 	@Controller
500 	public static class CustomRegexController {
501 
502 		@RequestMapping("/{root:\\d+}")
handle(@athVariableR) int root, Writer writer)503 		public void handle(@PathVariable("root") int root, Writer writer) throws IOException {
504 			assertEquals("Invalid path variable value", 42, root);
505 			writer.write("test-" + root);
506 		}
507 
508 	}
509 
510 	@Controller
511 	public static class DoubleController {
512 
513 		@RequestMapping("/lat/{latitude}/long/{longitude}")
testLatLong(@athVariable Double latitude, @PathVariable Double longitude, Writer writer)514 		public void testLatLong(@PathVariable Double latitude, @PathVariable Double longitude, Writer writer)
515 			throws IOException {
516 			writer.write("latitude-" + latitude + "-longitude-" + longitude);
517 		}
518 
519 	}
520 
521 
522 	@Controller
523 	@RequestMapping("hotels")
524 	public static class CrudController {
525 
526 		@RequestMapping(method = RequestMethod.GET)
list(Writer writer)527 		public void list(Writer writer) throws IOException {
528 			writer.write("list");
529 		}
530 
531 		@RequestMapping(method = RequestMethod.POST)
create(Writer writer)532 		public void create(Writer writer) throws IOException {
533 			writer.write("create");
534 		}
535 
536 		@RequestMapping(value = "/{hotel}", method = RequestMethod.GET)
show(@athVariable String hotel, Writer writer)537 		public void show(@PathVariable String hotel, Writer writer) throws IOException {
538 			writer.write("show-" + hotel);
539 		}
540 
541 		@RequestMapping(value = "{hotel}", method = RequestMethod.PUT)
createOrUpdate(@athVariable String hotel, Writer writer)542 		public void createOrUpdate(@PathVariable String hotel, Writer writer) throws IOException {
543 			writer.write("createOrUpdate-" + hotel);
544 		}
545 
546 		@RequestMapping(value = "{hotel}", method = RequestMethod.DELETE)
remove(@athVariable String hotel, Writer writer)547 		public void remove(@PathVariable String hotel, Writer writer) throws IOException {
548 			writer.write("remove-" + hotel);
549 		}
550 
551 	}
552 
553 	@Controller
554 	@RequestMapping("/hotels")
555 	public static class MethodNotAllowedController {
556 
557 		@RequestMapping(method = RequestMethod.GET)
list(Writer writer)558 		public void list(Writer writer) {
559 		}
560 
561 		@RequestMapping(method = RequestMethod.GET, value = "{hotelId}")
show(@athVariable long hotelId, Writer writer)562 		public void show(@PathVariable long hotelId, Writer writer) {
563 		}
564 
565 		@RequestMapping(method = RequestMethod.PUT, value = "{hotelId}")
createOrUpdate(@athVariable long hotelId, Writer writer)566 		public void createOrUpdate(@PathVariable long hotelId, Writer writer) {
567 		}
568 
569 		@RequestMapping(method = RequestMethod.DELETE, value = "/{hotelId}")
remove(@athVariable long hotelId, Writer writer)570 		public void remove(@PathVariable long hotelId, Writer writer) {
571 		}
572 	}
573 
574 	@Controller
575 	@RequestMapping("/category")
576 	public static class MultiPathController {
577 
578 		@RequestMapping(value = {"/{category}/page/{page}", "/**/{category}/page/{page}"})
category(@athVariable String category, @PathVariable int page, Writer writer)579 		public void category(@PathVariable String category, @PathVariable int page, Writer writer) throws IOException {
580 			writer.write("handle1-");
581 			writer.write("category-" + category);
582 			writer.write("page-" + page);
583 		}
584 
585 		@RequestMapping(value = {"/{category}", "/**/{category}"})
category(@athVariable String category, Writer writer)586 		public void category(@PathVariable String category, Writer writer) throws IOException {
587 			writer.write("handle2-");
588 			writer.write("category-" + category);
589 		}
590 
591 		@RequestMapping(value = {""})
category(Writer writer)592 		public void category(Writer writer) throws IOException {
593 			writer.write("handle3");
594 		}
595 
596 		@RequestMapping(value = {"/page/{page}"})
category(@athVariable int page, Writer writer)597 		public void category(@PathVariable int page, Writer writer) throws IOException {
598 			writer.write("handle4-");
599 			writer.write("page-" + page);
600 		}
601 
602 	}
603 
604 	@Controller
605 	@RequestMapping("/*/menu/**")
606 	public static class MenuTreeController {
607 
608 		@RequestMapping("type/{var}")
getFirstLevelFunctionNodes(@athVariableR) String var, Writer writer)609 		public void getFirstLevelFunctionNodes(@PathVariable("var") String var, Writer writer) throws IOException {
610 			writer.write(var);
611 		}
612 	}
613 
614 	@Controller
615 	@RequestMapping("/test")
616 	public static class VariableNamesController {
617 
618 		@RequestMapping(value = "/{foo}", method=RequestMethod.GET)
foo(@athVariable String foo, Writer writer)619 		public void foo(@PathVariable String foo, Writer writer) throws IOException {
620 			writer.write("foo-" + foo);
621 		}
622 
623 		@RequestMapping(value = "/{bar}", method=RequestMethod.DELETE)
bar(@athVariable String bar, Writer writer)624 		public void bar(@PathVariable String bar, Writer writer) throws IOException {
625 			writer.write("bar-" + bar);
626 		}
627 	}
628 
629 	@Controller
630 	public static class Spr6978Controller {
631 
632 		@RequestMapping(value = "/{type}/{id}", method = RequestMethod.GET)
loadEntity(@athVariable final String type, @PathVariable final long id, Writer writer)633 		public void loadEntity(@PathVariable final String type, @PathVariable final long id, Writer writer)
634 				throws IOException {
635 			writer.write("loadEntity:" + type + ":" + id);
636 		}
637 
638 		@RequestMapping(value = "/module/{id}", method = RequestMethod.GET)
loadModule(@athVariable final long id, Writer writer)639 		public void loadModule(@PathVariable final long id, Writer writer) throws IOException {
640 			writer.write("loadModule:" + id);
641 		}
642 
643 		@RequestMapping(value = "/{type}/{id}", method = RequestMethod.POST)
publish(@athVariable final String type, @PathVariable final long id, Writer writer)644 		public void publish(@PathVariable final String type, @PathVariable final long id, Writer writer)
645 				throws IOException {
646 			writer.write("publish:" + type + ":" + id);
647 		}
648 	}
649 
650 	public static class ModelValidatingViewResolver implements ViewResolver {
651 
652 		private final Map<String, Object> attrsToValidate;
653 
654 		int validatedAttrCount;
655 
ModelValidatingViewResolver(Map<String, Object> attrsToValidate)656 		public ModelValidatingViewResolver(Map<String, Object> attrsToValidate) {
657 			this.attrsToValidate = attrsToValidate;
658 		}
659 
resolveViewName(final String viewName, Locale locale)660 		public View resolveViewName(final String viewName, Locale locale) throws Exception {
661 			return new AbstractView () {
662 				public String getContentType() {
663 					return null;
664 				}
665 				@Override
666 				protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
667 						HttpServletResponse response) throws Exception {
668 					for (String key : attrsToValidate.keySet()) {
669 						assertTrue("Model should contain attribute named " + key, model.containsKey(key));
670 						assertEquals(attrsToValidate.get(key), model.get(key));
671 						validatedAttrCount++;
672 					}
673 				}
674 			};
675 		}
676 	}
677 
678 // @Ignore("ControllerClassNameHandlerMapping")
679 //	public void controllerClassName() throws Exception {
680 
681 //	@Ignore("useDefaultSuffixPattern property not supported")
682 //	public void doubles() throws Exception {
683 
684 //	@Ignore("useDefaultSuffixPattern property not supported")
685 //	public void noDefaultSuffixPattern() throws Exception {
686 
687 }
688