From bcad0df06357d44494b8c17202260a12140b1e0f Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Mon, 8 Nov 2010 14:56:35 +0000 Subject: [PATCH] SPR-7707 - Unexpected behavior with class-level @RequestMappings git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@3793 50f2f4bb-b051-0410-bef5-90022cba6387 --- .../web/servlet/HandlerMapping.java | 10 +++++- .../handler/AbstractUrlHandlerMapping.java | 10 +++++- .../AnnotationMethodHandlerAdapter.java | 33 +++++++++++++++++-- .../DefaultAnnotationHandlerMapping.java | 4 +++ .../ServletAnnotationControllerTests.java | 29 +++++++++++++++- 5 files changed, 81 insertions(+), 5 deletions(-) diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/HandlerMapping.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/HandlerMapping.java index 76e37463089..c95e6ede0f9 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/HandlerMapping.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/HandlerMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2007 the original author or authors. + * Copyright 2002-2010 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -74,6 +74,14 @@ public interface HandlerMapping { */ String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern"; + /** + * Name of the boolean {@link HttpServletRequest} attribute that indicates + * whether type-level mappings should be inspected. + *

Note: This attribute is not required to be supported by all + * HandlerMapping implementations. + */ + String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping"; + /** * Name of the {@link HttpServletRequest} attribute that contains the URI * templates map, mapping variable names to values. 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 8d80909d379..f9623c76d47 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 @@ -429,6 +429,13 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping { return Collections.unmodifiableMap(this.handlerMap); } + /** + * Indicates whether this handler mapping support type-level mappings. Default to {@code false}. + */ + protected boolean supportsTypeLevelMappings() { + return false; + } + /** * Special interceptor for exposing the @@ -449,10 +456,11 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { exposePathWithinMapping(this.bestMatchingPattern, this.pathWithinMapping, request); + request.setAttribute(HandlerMapping.INTROSPECT_TYPE_LEVEL_MAPPING, supportsTypeLevelMappings()); return true; } - } + } /** * Special interceptor for exposing the diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java index 328288a1b1c..a1156798b08 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java @@ -34,7 +34,6 @@ import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; - import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; @@ -45,6 +44,7 @@ import javax.servlet.http.HttpSession; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; @@ -571,6 +571,27 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator } mappingInfo.sortMatchedPatterns(pathComparator); } + else if (useTypeLevelMapping(request)) { + String[] typeLevelPatterns = getTypeLevelMapping().value(); + for (String typeLevelPattern : typeLevelPatterns) { + if (!typeLevelPattern.startsWith("/")) { + typeLevelPattern = "/" + typeLevelPattern; + } + if (isPathMatchInternal(typeLevelPattern, lookupPath)) { + if (mappingInfo.matches(request)) { + match = true; + mappingInfo.addMatchedPattern(typeLevelPattern); + } + else { + if (!mappingInfo.matchesRequestMethod(request)) { + allowedMethods.addAll(mappingInfo.methodNames()); + } + break; + } + } + } + mappingInfo.sortMatchedPatterns(pathComparator); + } else { // No paths specified: parameter match sufficient. match = mappingInfo.matches(request); @@ -638,6 +659,14 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator } } + private boolean useTypeLevelMapping(HttpServletRequest request) { + if (!hasTypeLevelMapping() || ObjectUtils.isEmpty(getTypeLevelMapping().value())) { + return false; + } + return (Boolean) request.getAttribute( + HandlerMapping.INTROSPECT_TYPE_LEVEL_MAPPING); + } + /** * Determines the combined pattern for the given methodLevelPattern and path. *

Uses the following algorithm:

    @@ -649,7 +678,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator *
*/ private String getCombinedPattern(String methodLevelPattern, String lookupPath, HttpServletRequest request) { - if (hasTypeLevelMapping() && (!ObjectUtils.isEmpty(getTypeLevelMapping().value()))) { + if (useTypeLevelMapping(request)) { String[] typeLevelPatterns = getTypeLevelMapping().value(); for (String typeLevelPattern : typeLevelPatterns) { if (!typeLevelPattern.startsWith("/")) { diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/DefaultAnnotationHandlerMapping.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/DefaultAnnotationHandlerMapping.java index b67f84fec9c..c4c3eb263fd 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/DefaultAnnotationHandlerMapping.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/DefaultAnnotationHandlerMapping.java @@ -261,4 +261,8 @@ public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandler } } + @Override + protected boolean supportsTypeLevelMappings() { + return true; + } } diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java index 3d361e66c8a..6cfa55ea77b 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java @@ -50,7 +50,6 @@ import javax.validation.Valid; import javax.validation.constraints.NotNull; import javax.xml.bind.annotation.XmlRootElement; -import static org.junit.Assert.*; import org.junit.Test; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; @@ -134,6 +133,8 @@ import org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMap import org.springframework.web.servlet.view.InternalResourceViewResolver; import org.springframework.web.util.NestedServletException; +import static org.junit.Assert.*; + /** * @author Juergen Hoeller * @author Sam Brannen @@ -1842,6 +1843,16 @@ public class ServletAnnotationControllerTests { assertEquals("1-2", response.getContentAsString()); } + @Test + public void testMatchWithoutMethodLevelPath() throws Exception { + initServlet(NoPathGetAndM2PostController.class); + + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/t1/m2"); + MockHttpServletResponse response = new MockHttpServletResponse(); + servlet.service(request, response); + assertEquals(405, response.getStatus()); + } + /* * Controllers @@ -3104,4 +3115,20 @@ public class ServletAnnotationControllerTests { } } + @Controller + @RequestMapping("/t1") + protected static class NoPathGetAndM2PostController { + @RequestMapping(method = RequestMethod.GET) + public void handle1(Writer writer) throws IOException { + writer.write("handle1"); + } + + @RequestMapping(value = "/m2", method = RequestMethod.POST) + public void handle2(Writer writer) throws IOException { + writer.write("handle2"); + } + } + + + }