Skip to content

Commit 7605707

Browse files
committed
backported @PathVariable matching fix (SPR-8543)
1 parent d48c3b5 commit 7605707

File tree

2 files changed

+66
-56
lines changed

2 files changed

+66
-56
lines changed

org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java

Lines changed: 43 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2010 the original author or authors.
2+
* Copyright 2002-2011 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -430,10 +430,19 @@ protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServl
430430
return mav;
431431
}
432432

433+
/**
434+
* This method always returns -1 since an annotated controller can have many methods,
435+
* each requiring separate lastModified calculations. Instead, an
436+
* @{@link RequestMapping}-annotated method can calculate the lastModified value, call
437+
* {@link org.springframework.web.context.request.WebRequest#checkNotModified(long)}
438+
* to check it, and return {@code null} if that returns {@code true}.
439+
* @see org.springframework.web.context.request.WebRequest#checkNotModified(long)
440+
*/
433441
public long getLastModified(HttpServletRequest request, Object handler) {
434442
return -1;
435443
}
436444

445+
437446
/**
438447
* Build a HandlerMethodResolver for the given handler type.
439448
*/
@@ -463,8 +472,7 @@ private ServletHandlerMethodResolver getMethodResolver(Object handler) {
463472
* @see ServletRequestDataBinder#bind(javax.servlet.ServletRequest)
464473
* @see ServletRequestDataBinder#convertIfNecessary(Object, Class, org.springframework.core.MethodParameter)
465474
*/
466-
protected ServletRequestDataBinder createBinder(HttpServletRequest request, Object target, String objectName)
467-
throws Exception {
475+
protected ServletRequestDataBinder createBinder(HttpServletRequest request, Object target, String objectName) throws Exception {
468476
return new ServletRequestDataBinder(target, objectName);
469477
}
470478

@@ -620,21 +628,20 @@ public Method resolveHandlerMethod(HttpServletRequest request) throws ServletExc
620628
}
621629
else {
622630
if (!allowedMethods.isEmpty()) {
623-
throw new HttpRequestMethodNotSupportedException(request.getMethod(),
624-
StringUtils.toStringArray(allowedMethods));
631+
throw new HttpRequestMethodNotSupportedException(request.getMethod(), StringUtils.toStringArray(allowedMethods));
625632
}
626-
throw new NoSuchRequestHandlingMethodException(lookupPath, request.getMethod(),
627-
request.getParameterMap());
633+
throw new NoSuchRequestHandlingMethodException(lookupPath, request.getMethod(), request.getParameterMap());
628634
}
629635
}
630636

631637
/**
632638
* Determines the combined pattern for the given methodLevelPattern and path.
633-
* <p>Uses the following algorithm: <ol>
639+
* <p>Uses the following algorithm:
640+
* <ol>
634641
* <li>If there is a type-level mapping with path information, it is {@linkplain
635642
* PathMatcher#combine(String, String) combined} with the method-level pattern.</li>
636-
* <li>If there is a {@linkplain HandlerMapping#BEST_MATCHING_PATTERN_ATTRIBUTE best matching pattern} in the
637-
* request, it is combined with the method-level pattern.</li>
643+
* <li>If there is a {@linkplain HandlerMapping#BEST_MATCHING_PATTERN_ATTRIBUTE best matching pattern}
644+
* in the request, it is combined with the method-level pattern.</li>
638645
* <li>Otherwise, the method-level pattern is returned.</li>
639646
* </ol>
640647
*/
@@ -646,53 +653,54 @@ private String getCombinedPattern(String methodLevelPattern, String lookupPath,
646653
typeLevelPattern = "/" + typeLevelPattern;
647654
}
648655
String combinedPattern = pathMatcher.combine(typeLevelPattern, methodLevelPattern);
649-
if (isPathMatchInternal(combinedPattern, lookupPath)) {
650-
return combinedPattern;
656+
String matchingPattern = getMatchingPattern(combinedPattern, lookupPath);
657+
if (matchingPattern != null) {
658+
return matchingPattern;
651659
}
652660
}
653661
return null;
654662
}
655663
String bestMatchingPattern = (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
656664
if (StringUtils.hasText(bestMatchingPattern) && bestMatchingPattern.endsWith("*")) {
657665
String combinedPattern = pathMatcher.combine(bestMatchingPattern, methodLevelPattern);
658-
if (!combinedPattern.equals(bestMatchingPattern) &&
659-
(isPathMatchInternal(combinedPattern, lookupPath))) {
660-
return combinedPattern;
666+
String matchingPattern = getMatchingPattern(combinedPattern, lookupPath);
667+
if (matchingPattern != null && !matchingPattern.equals(bestMatchingPattern)) {
668+
return matchingPattern;
661669
}
662670
}
663-
if (isPathMatchInternal(methodLevelPattern, lookupPath)) {
664-
return methodLevelPattern;
665-
}
666-
return null;
671+
return getMatchingPattern(methodLevelPattern, lookupPath);
667672
}
668673

669-
private boolean isPathMatchInternal(String pattern, String lookupPath) {
670-
if (pattern.equals(lookupPath) || pathMatcher.match(pattern, lookupPath)) {
671-
return true;
674+
private String getMatchingPattern(String pattern, String lookupPath) {
675+
if (pattern.equals(lookupPath)) {
676+
return pattern;
672677
}
673678
boolean hasSuffix = pattern.indexOf('.') != -1;
674-
if (!hasSuffix && pathMatcher.match(pattern + ".*", lookupPath)) {
675-
return true;
679+
if (!hasSuffix) {
680+
String patternWithSuffix = pattern + ".*";
681+
if (pathMatcher.match(patternWithSuffix, lookupPath)) {
682+
return patternWithSuffix;
683+
}
684+
}
685+
if (pathMatcher.match(pattern, lookupPath)) {
686+
return pattern;
676687
}
677688
boolean endsWithSlash = pattern.endsWith("/");
678-
if (!endsWithSlash && pathMatcher.match(pattern + "/", lookupPath)) {
679-
return true;
689+
if (!endsWithSlash) {
690+
String patternWithSlash = pattern + "/";
691+
if (pathMatcher.match(patternWithSlash, lookupPath)) {
692+
return patternWithSlash;
693+
}
680694
}
681-
return false;
695+
return null;
682696
}
683697

684698
@SuppressWarnings("unchecked")
685-
private void extractHandlerMethodUriTemplates(String mappedPattern,
686-
String lookupPath,
687-
HttpServletRequest request) {
688-
699+
private void extractHandlerMethodUriTemplates(String mappedPattern, String lookupPath, HttpServletRequest request) {
689700
Map<String, String> variables =
690701
(Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
691-
692702
int patternVariableCount = StringUtils.countOccurrencesOf(mappedPattern, "{");
693-
694-
if ( (variables == null || patternVariableCount != variables.size())
695-
&& pathMatcher.match(mappedPattern, lookupPath)) {
703+
if ((variables == null || patternVariableCount != variables.size()) && pathMatcher.match(mappedPattern, lookupPath)) {
696704
variables = pathMatcher.extractUriTemplateVariables(mappedPattern, lookupPath);
697705
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, variables);
698706
}

org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/UriTemplateServletAnnotationControllerTests.java

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2010 the original author or authors.
2+
* Copyright 2002-2011 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,7 +23,6 @@
2323
import javax.servlet.ServletException;
2424
import javax.servlet.http.HttpServletResponse;
2525

26-
import static org.junit.Assert.*;
2726
import org.junit.Test;
2827

2928
import org.springframework.beans.BeansException;
@@ -43,7 +42,12 @@
4342
import org.springframework.web.servlet.DispatcherServlet;
4443
import org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping;
4544

46-
/** @author Arjen Poutsma */
45+
import static org.junit.Assert.*;
46+
47+
/**
48+
* @author Arjen Poutsma
49+
* @author Rossen Stoyanchev
50+
*/
4751
public class UriTemplateServletAnnotationControllerTests {
4852

4953
private DispatcherServlet servlet;
@@ -309,9 +313,7 @@ public void customRegex() throws Exception {
309313
assertEquals("test-42", response.getContentAsString());
310314
}
311315

312-
/*
313-
* See SPR-6640
314-
*/
316+
// SPR-6640
315317
@Test
316318
public void menuTree() throws Exception {
317319
initServlet(MenuTreeController.class);
@@ -322,9 +324,7 @@ public void menuTree() throws Exception {
322324
assertEquals("M5", response.getContentAsString());
323325
}
324326

325-
/*
326-
* See SPR-6876
327-
*/
327+
// SPR-6876
328328
@Test
329329
public void variableNames() throws Exception {
330330
initServlet(VariableNamesController.class);
@@ -340,9 +340,18 @@ public void variableNames() throws Exception {
340340
assertEquals("bar-bar", response.getContentAsString());
341341
}
342342

343-
/*
344-
* See SPR-6906
345-
*/
343+
// SPR-8543
344+
@Test
345+
public void variableNamesWithUrlExtension() throws Exception {
346+
initServlet(VariableNamesController.class);
347+
348+
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test/foo.json");
349+
MockHttpServletResponse response = new MockHttpServletResponse();
350+
servlet.service(request, response);
351+
assertEquals("foo-foo", response.getContentAsString());
352+
}
353+
354+
// SPR-6906
346355
@Test
347356
public void controllerClassName() throws Exception {
348357
servlet = new DispatcherServlet() {
@@ -376,9 +385,7 @@ protected WebApplicationContext createWebApplicationContext(WebApplicationContex
376385
assertEquals("plain-bar", response.getContentAsString());
377386
}
378387

379-
/*
380-
* See SPR-6978
381-
*/
388+
// SPR-6978
382389
@Test
383390
public void doIt() throws Exception {
384391
initServlet(Spr6978Controller.class);
@@ -406,10 +413,7 @@ public void doIt() throws Exception {
406413
}
407414

408415

409-
410-
/*
411-
* Controllers
412-
*/
416+
// Controllers
413417

414418
@Controller
415419
public static class SimpleUriTemplateController {
@@ -548,7 +552,6 @@ public void testLatLong(@PathVariable Double latitude, @PathVariable Double long
548552

549553
}
550554

551-
552555
@Controller
553556
@RequestMapping("hotels")
554557
public static class CrudController {
@@ -676,5 +679,4 @@ public void publish(@PathVariable final String type, @PathVariable final long id
676679
}
677680
}
678681

679-
680682
}

0 commit comments

Comments
 (0)