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 }