diff --git a/org.springframework.core/src/main/java/org/springframework/util/AntPathMatcher.java b/org.springframework.core/src/main/java/org/springframework/util/AntPathMatcher.java index d28f38c6a5..ac9a524b8e 100644 --- a/org.springframework.core/src/main/java/org/springframework/util/AntPathMatcher.java +++ b/org.springframework.core/src/main/java/org/springframework/util/AntPathMatcher.java @@ -412,7 +412,7 @@ public class AntPathMatcher implements PathMatcher { else if (bracketCount2 < bracketCount1) { return 1; } - return 0; + return pattern2.length() - pattern1.length(); } } diff --git a/org.springframework.core/src/main/java/org/springframework/util/AntPathStringMatcher.java b/org.springframework.core/src/main/java/org/springframework/util/AntPathStringMatcher.java index 1f528ba77c..6f9bf25266 100644 --- a/org.springframework.core/src/main/java/org/springframework/util/AntPathStringMatcher.java +++ b/org.springframework.core/src/main/java/org/springframework/util/AntPathStringMatcher.java @@ -36,7 +36,7 @@ class AntPathStringMatcher { private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{([^/]+?)\\}"); - private static final String DEFAULT_VARIABLE_PATTERN = "(.*)"; + private static final String DEFAULT_VARIABLE_PATTERN = "([^\\.]*)"; private final Pattern pattern; diff --git a/org.springframework.core/src/test/java/org/springframework/util/AntPathMatcherTests.java b/org.springframework.core/src/test/java/org/springframework/util/AntPathMatcherTests.java index e2b5c036ed..f662306d28 100644 --- a/org.springframework.core/src/test/java/org/springframework/util/AntPathMatcherTests.java +++ b/org.springframework.core/src/test/java/org/springframework/util/AntPathMatcherTests.java @@ -395,6 +395,11 @@ public class AntPathMatcherTests { assertEquals(-1, comparator.compare("/hotels/*", "/hotels/*/**")); assertEquals(1, comparator.compare("/hotels/*/**", "/hotels/*")); + + assertEquals(-1, comparator.compare("/hotels/new", "/hotels/new.*")); + + // longer is better + assertEquals(1, comparator.compare("/hotels", "/hotels2")); } @Test @@ -467,6 +472,14 @@ public class AntPathMatcherTests { assertEquals("/hotels/{hotel}", paths.get(1)); assertEquals("/hotels/*", paths.get(2)); paths.clear(); + + paths.add("/hotels/ne*"); + paths.add("/hotels/n*"); + Collections.shuffle(paths); + Collections.sort(paths, comparator); + assertEquals("/hotels/ne*", paths.get(0)); + assertEquals("/hotels/n*", paths.get(1)); + paths.clear(); } } diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java index a82d1f826f..7aee2d76f0 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java @@ -19,6 +19,8 @@ package org.springframework.web.servlet.handler; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; +import java.util.List; +import java.util.ArrayList; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -214,12 +216,19 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping { return buildPathExposingHandler(handler, urlPath, null); } // Pattern match? - String bestPathMatch = null; + List matchingPaths = new ArrayList(); for (String registeredPath : this.handlerMap.keySet()) { - if (getPathMatcher().match(registeredPath, urlPath) && - (bestPathMatch == null || bestPathMatch.length() < registeredPath.length())) { - bestPathMatch = registeredPath; + if (getPathMatcher().match(registeredPath, urlPath)) { + matchingPaths.add(registeredPath); + } + } + String bestPathMatch = null; + if (!matchingPaths.isEmpty()) { + Collections.sort(matchingPaths, getPathMatcher().getPatternComparator(urlPath)); + if (logger.isDebugEnabled()) { + logger.debug("Matching path for request [" + urlPath + "] are " + matchingPaths); } + bestPathMatch = matchingPaths.get(0); } if (bestPathMatch != null) { handler = this.handlerMap.get(bestPathMatch); diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/UriTemplateServletAnnotationControllerTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/UriTemplateServletAnnotationControllerTests.java index 550681f714..6d2c265734 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/UriTemplateServletAnnotationControllerTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/UriTemplateServletAnnotationControllerTests.java @@ -221,6 +221,22 @@ public class UriTemplateServletAnnotationControllerTests { } + @Test + public void multiPaths() throws Exception { + initServlet(MultiPathController.class); + + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/category/page/5"); + MockHttpServletResponse response = new MockHttpServletResponse(); + servlet.service(request, response); + assertEquals("handle4-page-5", response.getContentAsString()); + + request = new MockHttpServletRequest("GET", "/category/page/5.html"); + response = new MockHttpServletResponse(); + servlet.service(request, response); + assertEquals("handle4-page-5", response.getContentAsString()); + } + + private void initServlet(final Class controllerclass) throws ServletException { servlet = new DispatcherServlet() { @Override @@ -258,6 +274,17 @@ public class UriTemplateServletAnnotationControllerTests { assertEquals("test-hotel.with.dot", response.getContentAsString()); } + @Test + public void customRegex() throws Exception { + initServlet(CustomRegexController.class); + + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/42"); + MockHttpServletResponse response = new MockHttpServletResponse(); + servlet.service(request, response); + assertEquals("test-42", response.getContentAsString()); + } + + /* * Controllers */ @@ -371,12 +398,24 @@ public class UriTemplateServletAnnotationControllerTests { @RequestMapping("hotels") public static class ImplicitSubPathController { - @RequestMapping("{hotel}") + @RequestMapping("{hotel:.*}") public void handleHotel(@PathVariable String hotel, Writer writer) throws IOException { writer.write("test-" + hotel); } } + @Controller + public static class CustomRegexController { + + @RequestMapping("/{root:\\d+}") + public void handle(@PathVariable("root") int root, Writer writer) throws IOException { + assertEquals("Invalid path variable value", 42, root); + writer.write("test-" + root); + } + + } + + @Controller @RequestMapping("hotels") public static class CrudController { @@ -429,4 +468,34 @@ public class UriTemplateServletAnnotationControllerTests { } } + @Controller + @RequestMapping("/category") + public static class MultiPathController { + + @RequestMapping(value = {"/{category}/page/{page}", "/**/{category}/page/{page}"}) + public void category(@PathVariable String category, @PathVariable int page, Writer writer) throws IOException { + writer.write("handle1-"); + writer.write("category-" + category); + writer.write("page-" + page); + } + + @RequestMapping(value = {"/{category}", "/**/{category}"}) + public void category(@PathVariable String category, Writer writer) throws IOException { + writer.write("handle2-"); + writer.write("category-" + category); + } + + @RequestMapping(value = {""}) + public void category(Writer writer) throws IOException { + writer.write("handle3"); + } + + @RequestMapping(value = {"/page/{page}"}) + public void category(@PathVariable int page, Writer writer) throws IOException { + writer.write("handle4-"); + writer.write("page-" + page); + } + + } + } diff --git a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java index 18862228ac..e63d72988f 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java +++ b/org.springframework.web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java @@ -73,7 +73,10 @@ import java.lang.annotation.Target; * exposed by the Servlet/Portlet API. *
  • {@link PathVariable @PathVariable} annotated parameters for access to * URI template values (i.e. /hotels/{hotel}). Variable values will be - * converted to the declared method argument type. + * converted to the declared method argument type. By default, the URI template + * will match against the regular expression {@code [^\.]*} (i.e. any character + * other than period), but this can be changed by specifying another regular + * expression, like so: /hotels/{hotel:\d+}. *
  • {@link RequestParam @RequestParam} annotated parameters for access to * specific Servlet/Portlet request parameters. Parameter values will be * converted to the declared method argument type. Additionally,