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;
18 
19 import static org.junit.Assert.assertArrayEquals;
20 import static org.junit.Assert.assertEquals;
21 import static org.junit.Assert.assertNotNull;
22 import static org.junit.Assert.assertNull;
23 import static org.junit.Assert.assertSame;
24 import static org.junit.Assert.fail;
25 
26 import java.lang.reflect.Method;
27 import java.util.Arrays;
28 import java.util.Collections;
29 import java.util.HashSet;
30 import java.util.Map;
31 import java.util.Set;
32 
33 import org.junit.Before;
34 import org.junit.Test;
35 import org.springframework.core.annotation.AnnotationUtils;
36 import org.springframework.http.MediaType;
37 import org.springframework.mock.web.MockHttpServletRequest;
38 import org.springframework.stereotype.Controller;
39 import org.springframework.web.HttpMediaTypeNotAcceptableException;
40 import org.springframework.web.HttpMediaTypeNotSupportedException;
41 import org.springframework.web.HttpRequestMethodNotSupportedException;
42 import org.springframework.web.bind.UnsatisfiedServletRequestParameterException;
43 import org.springframework.web.bind.annotation.RequestBody;
44 import org.springframework.web.bind.annotation.RequestMapping;
45 import org.springframework.web.bind.annotation.RequestMethod;
46 import org.springframework.web.context.support.StaticWebApplicationContext;
47 import org.springframework.web.method.HandlerMethod;
48 import org.springframework.web.servlet.HandlerExecutionChain;
49 import org.springframework.web.servlet.HandlerInterceptor;
50 import org.springframework.web.servlet.HandlerMapping;
51 import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
52 import org.springframework.web.servlet.handler.MappedInterceptor;
53 import org.springframework.web.servlet.mvc.condition.ConsumesRequestCondition;
54 import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition;
55 import org.springframework.web.servlet.mvc.condition.ParamsRequestCondition;
56 import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
57 import org.springframework.web.servlet.mvc.condition.ProducesRequestCondition;
58 import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
59 import org.springframework.web.util.UrlPathHelper;
60 
61 /**
62  * Test fixture with {@link RequestMappingInfoHandlerMapping}.
63  *
64  * @author Arjen Poutsma
65  * @author Rossen Stoyanchev
66  */
67 public class RequestMappingInfoHandlerMappingTests {
68 
69 	private TestRequestMappingInfoHandlerMapping mapping;
70 
71 	private Handler handler;
72 
73 	private HandlerMethod fooMethod;
74 
75 	private HandlerMethod fooParamMethod;
76 
77 	private HandlerMethod barMethod;
78 
79 	private HandlerMethod emptyMethod;
80 
81 	@Before
setUp()82 	public void setUp() throws Exception {
83 		this.handler = new Handler();
84 		this.fooMethod = new HandlerMethod(handler, "foo");
85 		this.fooParamMethod = new HandlerMethod(handler, "fooParam");
86 		this.barMethod = new HandlerMethod(handler, "bar");
87 		this.emptyMethod = new HandlerMethod(handler, "empty");
88 
89 		this.mapping = new TestRequestMappingInfoHandlerMapping();
90 		this.mapping.registerHandler(this.handler);
91 	}
92 
93 	@Test
getMappingPathPatterns()94 	public void getMappingPathPatterns() throws Exception {
95 		RequestMappingInfo info = new RequestMappingInfo(
96 				new PatternsRequestCondition("/foo/*", "/foo", "/bar/*", "/bar"), null, null, null, null, null, null);
97 		Set<String> paths = this.mapping.getMappingPathPatterns(info);
98 		HashSet<String> expected = new HashSet<String>(Arrays.asList("/foo/*", "/foo", "/bar/*", "/bar"));
99 
100 		assertEquals(expected, paths);
101 	}
102 
103 	@Test
directMatch()104 	public void directMatch() throws Exception {
105 		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
106 		HandlerMethod hm = (HandlerMethod) this.mapping.getHandler(request).getHandler();
107 		assertEquals(this.fooMethod.getMethod(), hm.getMethod());
108 	}
109 
110 	@Test
globMatch()111 	public void globMatch() throws Exception {
112 		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bar");
113 		HandlerMethod hm = (HandlerMethod) this.mapping.getHandler(request).getHandler();
114 		assertEquals(this.barMethod.getMethod(), hm.getMethod());
115 	}
116 
117 	@Test
emptyPathMatch()118 	public void emptyPathMatch() throws Exception {
119 		MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
120 		HandlerMethod hm = (HandlerMethod) this.mapping.getHandler(request).getHandler();
121 		assertEquals(this.emptyMethod.getMethod(), hm.getMethod());
122 
123 		request = new MockHttpServletRequest("GET", "/");
124 		hm = (HandlerMethod) this.mapping.getHandler(request).getHandler();
125 		assertEquals(this.emptyMethod.getMethod(), hm.getMethod());
126 	}
127 
128 	@Test
bestMatch()129 	public void bestMatch() throws Exception {
130 		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
131 		request.setParameter("p", "anything");
132 		HandlerMethod hm = (HandlerMethod) this.mapping.getHandler(request).getHandler();
133 		assertEquals(this.fooParamMethod.getMethod(), hm.getMethod());
134 	}
135 
136 	@Test
requestMethodNotAllowed()137 	public void requestMethodNotAllowed() throws Exception {
138 		try {
139 			MockHttpServletRequest request = new MockHttpServletRequest("POST", "/bar");
140 			this.mapping.getHandler(request);
141 			fail("HttpRequestMethodNotSupportedException expected");
142 		}
143 		catch (HttpRequestMethodNotSupportedException ex) {
144 			assertArrayEquals("Invalid supported methods", new String[]{"GET", "HEAD"}, ex.getSupportedMethods());
145 		}
146 	}
147 
148 	// SPR-9603
149 
150 	@Test(expected=HttpMediaTypeNotAcceptableException.class)
requestMethodMatchFalsePositive()151 	public void requestMethodMatchFalsePositive() throws Exception {
152 		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/users");
153 		request.addHeader("Accept", "application/xml");
154 
155 		this.mapping.registerHandler(new UserController());
156 		this.mapping.getHandler(request);
157 	}
158 
159 	@Test
mediaTypeNotSupported()160 	public void mediaTypeNotSupported() throws Exception {
161 		testMediaTypeNotSupported("/person/1");
162 		testMediaTypeNotSupported("/person/1/");	// SPR-8462
163 		testMediaTypeNotSupported("/person/1.json");
164 	}
165 
testMediaTypeNotSupported(String url)166 	private void testMediaTypeNotSupported(String url) throws Exception {
167 		try {
168 			MockHttpServletRequest request = new MockHttpServletRequest("PUT", url);
169 			request.setContentType("application/json");
170 			this.mapping.getHandler(request);
171 			fail("HttpMediaTypeNotSupportedException expected");
172 		}
173 		catch (HttpMediaTypeNotSupportedException ex) {
174 			assertEquals("Invalid supported consumable media types",
175 					Arrays.asList(new MediaType("application", "xml")), ex.getSupportedMediaTypes());
176 		}
177 	}
178 
179 	@Test
mediaTypeNotAccepted()180 	public void mediaTypeNotAccepted() throws Exception {
181 		testMediaTypeNotAccepted("/persons");
182 		testMediaTypeNotAccepted("/persons/");	// SPR-8462
183 		testMediaTypeNotAccepted("/persons.json");
184 	}
185 
testMediaTypeNotAccepted(String url)186 	private void testMediaTypeNotAccepted(String url) throws Exception {
187 		try {
188 			MockHttpServletRequest request = new MockHttpServletRequest("GET", url);
189 			request.addHeader("Accept", "application/json");
190 			this.mapping.getHandler(request);
191 			fail("HttpMediaTypeNotAcceptableException expected");
192 		}
193 		catch (HttpMediaTypeNotAcceptableException ex) {
194 			assertEquals("Invalid supported producible media types",
195 					Arrays.asList(new MediaType("application", "xml")), ex.getSupportedMediaTypes());
196 		}
197 	}
198 
199 	@Test
testUnsatisfiedServletRequestParameterException()200 	public void testUnsatisfiedServletRequestParameterException() throws Exception {
201 		try {
202 			MockHttpServletRequest request = new MockHttpServletRequest("GET", "/params");
203 			this.mapping.getHandler(request);
204 			fail("UnsatisfiedServletRequestParameterException expected");
205 		}
206 		catch (UnsatisfiedServletRequestParameterException ex) {
207 			assertArrayEquals("Invalid request parameter conditions",
208 					new String[] { "foo=bar" }, ex.getParamConditions());
209 		}
210 	}
211 
212 	@Test
uriTemplateVariables()213 	public void uriTemplateVariables() {
214 		PatternsRequestCondition patterns = new PatternsRequestCondition("/{path1}/{path2}");
215 		RequestMappingInfo key = new RequestMappingInfo(patterns, null, null, null, null, null, null);
216 		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/1/2");
217 		String lookupPath = new UrlPathHelper().getLookupPathForRequest(request);
218 		this.mapping.handleMatch(key, lookupPath, request);
219 
220 		@SuppressWarnings("unchecked")
221 		Map<String, String> uriVariables =
222 			(Map<String, String>) request.getAttribute(
223 					HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
224 
225 		assertNotNull(uriVariables);
226 		assertEquals("1", uriVariables.get("path1"));
227 		assertEquals("2", uriVariables.get("path2"));
228 	}
229 
230 	@Test
bestMatchingPatternAttribute()231 	public void bestMatchingPatternAttribute() {
232 		PatternsRequestCondition patterns = new PatternsRequestCondition("/{path1}/2", "/**");
233 		RequestMappingInfo key = new RequestMappingInfo(patterns, null, null, null, null, null, null);
234 		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/1/2");
235 
236 		this.mapping.handleMatch(key, "/1/2", request);
237 
238 		assertEquals("/{path1}/2", request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE));
239 	}
240 
241 	@Test
bestMatchingPatternAttributeNoPatternsDefined()242 	public void bestMatchingPatternAttributeNoPatternsDefined() {
243 		PatternsRequestCondition patterns = new PatternsRequestCondition();
244 		RequestMappingInfo key = new RequestMappingInfo(patterns, null, null, null, null, null, null);
245 		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/1/2");
246 
247 		this.mapping.handleMatch(key, "/1/2", request);
248 
249 		assertEquals("/1/2", request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE));
250 	}
251 
252 	@Test
producibleMediaTypesAttribute()253 	public void producibleMediaTypesAttribute() throws Exception {
254 		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/content");
255 		request.addHeader("Accept", "application/xml");
256 		this.mapping.getHandler(request);
257 
258 		assertEquals(Collections.singleton(MediaType.APPLICATION_XML),
259 				request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE));
260 
261 		request = new MockHttpServletRequest("GET", "/content");
262 		request.addHeader("Accept", "application/json");
263 		this.mapping.getHandler(request);
264 
265 		assertNull("Negated expression should not be listed as a producible type",
266 				request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE));
267 	}
268 
269 	@Test
mappedInterceptors()270 	public void mappedInterceptors() throws Exception {
271 		String path = "/foo";
272 		HandlerInterceptor interceptor = new HandlerInterceptorAdapter() {};
273 		MappedInterceptor mappedInterceptor = new MappedInterceptor(new String[] {path}, interceptor);
274 
275 		TestRequestMappingInfoHandlerMapping hm = new TestRequestMappingInfoHandlerMapping();
276 		hm.registerHandler(this.handler);
277 		hm.setInterceptors(new Object[] { mappedInterceptor });
278 		hm.setApplicationContext(new StaticWebApplicationContext());
279 
280 		HandlerExecutionChain chain = hm.getHandler(new MockHttpServletRequest("GET", path));
281 		assertNotNull(chain);
282 		assertNotNull(chain.getInterceptors());
283 		assertSame(interceptor, chain.getInterceptors()[0]);
284 
285 		chain = hm.getHandler(new MockHttpServletRequest("GET", "/invalid"));
286 		assertNull(chain);
287 	}
288 
289 	@Controller
290 	private static class Handler {
291 
292 		@RequestMapping(value = "/foo", method = RequestMethod.GET)
foo()293 		public void foo() {
294 		}
295 
296 		@RequestMapping(value = "/foo", method = RequestMethod.GET, params="p")
fooParam()297 		public void fooParam() {
298 		}
299 
300 		@RequestMapping(value = "/ba*", method = { RequestMethod.GET, RequestMethod.HEAD })
bar()301 		public void bar() {
302 		}
303 
304 		@RequestMapping(value = "")
empty()305 		public void empty() {
306 		}
307 
308 		@RequestMapping(value = "/person/{id}", method = RequestMethod.PUT, consumes="application/xml")
consumes(@equestBody String text)309 		public void consumes(@RequestBody String text) {
310 		}
311 
312 		@RequestMapping(value = "/persons", produces="application/xml")
produces()313 		public String produces() {
314 			return "";
315 		}
316 
317 		@RequestMapping(value = "/params", params="foo=bar")
param()318 		public String param() {
319 			return "";
320 		}
321 
322 		@RequestMapping(value = "/content", produces="application/xml")
xmlContent()323 		public String xmlContent() {
324 			return "";
325 		}
326 
327 		@RequestMapping(value = "/content", produces="!application/xml")
nonXmlContent()328 		public String nonXmlContent() {
329 			return "";
330 		}
331 	}
332 
333 	@Controller
334 	private static class UserController {
335 
336 		@RequestMapping(value = "/users", method = RequestMethod.GET, produces = "application/json")
getUser()337 		public void getUser() {
338 		}
339 
340 		@RequestMapping(value = "/users", method = RequestMethod.PUT)
saveUser()341 		public void saveUser() {
342 		}
343 	}
344 
345 	private static class TestRequestMappingInfoHandlerMapping extends RequestMappingInfoHandlerMapping {
346 
registerHandler(Object handler)347 		public void registerHandler(Object handler) {
348 			super.detectHandlerMethods(handler);
349 		}
350 
351 		@Override
isHandler(Class<?> beanType)352 		protected boolean isHandler(Class<?> beanType) {
353 			return AnnotationUtils.findAnnotation(beanType, RequestMapping.class) != null;
354 		}
355 
356 		@Override
getMappingForMethod(Method method, Class<?> handlerType)357 		protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
358 			RequestMapping annotation = AnnotationUtils.findAnnotation(method, RequestMapping.class);
359 			if (annotation != null) {
360 				return new RequestMappingInfo(
361 					new PatternsRequestCondition(annotation.value(), getUrlPathHelper(), getPathMatcher(), true, true),
362 					new RequestMethodsRequestCondition(annotation.method()),
363 					new ParamsRequestCondition(annotation.params()),
364 					new HeadersRequestCondition(annotation.headers()),
365 					new ConsumesRequestCondition(annotation.consumes(), annotation.headers()),
366 					new ProducesRequestCondition(annotation.produces(), annotation.headers()), null);
367 			}
368 			else {
369 				return null;
370 			}
371 		}
372 	}
373 
374 }