From bb3c8e5c8774512ee72ab93e656c9b1b0d1c3c78 Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Mon, 11 Jan 2010 11:27:43 +0000 Subject: [PATCH] SPR-6640 - PathVariable does not work properly after updated to 3.0 GA --- .../DefaultAnnotationHandlerMapping.java | 112 +++++++++--------- ...plateServletAnnotationControllerTests.java | 23 ++++ 2 files changed, 81 insertions(+), 54 deletions(-) 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 797e1735f16..968113e8aaf 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 @@ -37,47 +37,37 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.handler.AbstractDetectingUrlHandlerMapping; /** - * Implementation of the {@link org.springframework.web.servlet.HandlerMapping} - * interface that maps handlers based on HTTP paths expressed through the - * {@link RequestMapping} annotation at the type or method level. + * Implementation of the {@link org.springframework.web.servlet.HandlerMapping} interface that maps handlers based on + * HTTP paths expressed through the {@link RequestMapping} annotation at the type or method level. * - *

Registered by default in {@link org.springframework.web.servlet.DispatcherServlet} - * on Java 5+. NOTE: If you define custom HandlerMapping beans in your - * DispatcherServlet context, you need to add a DefaultAnnotationHandlerMapping bean - * explicitly, since custom HandlerMapping beans replace the default mapping strategies. - * Defining a DefaultAnnotationHandlerMapping also allows for registering custom - * interceptors: + *

Registered by default in {@link org.springframework.web.servlet.DispatcherServlet} on Java 5+. NOTE: If you + * define custom HandlerMapping beans in your DispatcherServlet context, you need to add a + * DefaultAnnotationHandlerMapping bean explicitly, since custom HandlerMapping beans replace the default mapping + * strategies. Defining a DefaultAnnotationHandlerMapping also allows for registering custom interceptors: * - *

- * <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
- *   <property name="interceptors">
- *     ...
- *   </property>
- * </bean>
+ *
 <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
+ * <property name="interceptors"> ... </property> </bean>
* - * Annotated controllers are usually marked with the {@link Controller} stereotype - * at the type level. This is not strictly necessary when {@link RequestMapping} is - * applied at the type level (since such a handler usually implements the - * {@link org.springframework.web.servlet.mvc.Controller} interface). However, - * {@link Controller} is required for detecting {@link RequestMapping} annotations - * at the method level if {@link RequestMapping} is not present at the type level. + * Annotated controllers are usually marked with the {@link Controller} stereotype at the type level. This is not + * strictly necessary when {@link RequestMapping} is applied at the type level (since such a handler usually implements + * the {@link org.springframework.web.servlet.mvc.Controller} interface). However, {@link Controller} is required for + * detecting {@link RequestMapping} annotations at the method level if {@link RequestMapping} is not present at the type + * level. * - *

NOTE: Method-level mappings are only allowed to narrow the mapping - * expressed at the class level (if any). HTTP paths need to uniquely map onto - * specific handler beans, with any given HTTP path only allowed to be mapped - * onto one specific handler bean (not spread across multiple handler beans). - * It is strongly recommended to co-locate related handler methods into the same bean. + *

NOTE: Method-level mappings are only allowed to narrow the mapping expressed at the class level (if any). + * HTTP paths need to uniquely map onto specific handler beans, with any given HTTP path only allowed to be mapped onto + * one specific handler bean (not spread across multiple handler beans). It is strongly recommended to co-locate related + * handler methods into the same bean. * - *

The {@link AnnotationMethodHandlerAdapter} is responsible for processing - * annotated handler methods, as mapped by this HandlerMapping. For - * {@link RequestMapping} at the type level, specific HandlerAdapters such as - * {@link org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter} apply. + *

The {@link AnnotationMethodHandlerAdapter} is responsible for processing annotated handler methods, as mapped by + * this HandlerMapping. For {@link RequestMapping} at the type level, specific HandlerAdapters such as {@link + * org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter} apply. * * @author Juergen Hoeller * @author Arjen Poutsma - * @since 2.5 * @see RequestMapping * @see AnnotationMethodHandlerAdapter + * @since 2.5 */ public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandlerMapping { @@ -85,23 +75,19 @@ public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandler private final Map cachedMappings = new HashMap(); - /** - * Set whether to register paths using the default suffix pattern as well: - * i.e. whether "/users" should be registered as "/users.*" and "/users/" too. - *

Default is "true". Turn this convention off if you intend to interpret - * your @RequestMapping paths strictly. - *

Note that paths which include a ".xxx" suffix or end with "/" already will not be - * transformed using the default suffix pattern in any case. + * Set whether to register paths using the default suffix pattern as well: i.e. whether "/users" should be registered + * as "/users.*" and "/users/" too.

Default is "true". Turn this convention off if you intend to interpret your + * @RequestMapping paths strictly.

Note that paths which include a ".xxx" suffix or end with "/" + * already will not be transformed using the default suffix pattern in any case. */ public void setUseDefaultSuffixPattern(boolean useDefaultSuffixPattern) { this.useDefaultSuffixPattern = useDefaultSuffixPattern; } - /** - * Checks for presence of the {@link org.springframework.web.bind.annotation.RequestMapping} - * annotation on the handler class and on any of its methods. + * Checks for presence of the {@link org.springframework.web.bind.annotation.RequestMapping} annotation on the handler + * class and on any of its methods. */ @Override protected String[] determineUrlsForHandler(String beanName) { @@ -120,11 +106,19 @@ public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandler if (!typeLevelPattern.startsWith("/")) { typeLevelPattern = "/" + typeLevelPattern; } + boolean hasEmptyMethodLevelMappings = false; for (String methodLevelPattern : methodLevelPatterns) { - String combinedPattern = getPathMatcher().combine(typeLevelPattern, methodLevelPattern); - addUrlsForPath(urls, combinedPattern); + if (methodLevelPattern == null) { + hasEmptyMethodLevelMappings = true; + } else { + String combinedPattern = getPathMatcher().combine(typeLevelPattern, methodLevelPattern); + addUrlsForPath(urls, combinedPattern); + } + } + if (hasEmptyMethodLevelMappings || + org.springframework.web.servlet.mvc.Controller.class.isAssignableFrom(handlerType)) { + addUrlsForPath(urls, typeLevelPattern); } - addUrlsForPath(urls, typeLevelPattern); } return StringUtils.toStringArray(urls); } @@ -144,6 +138,9 @@ public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandler /** * Derive URL mappings from the handler's method-level mappings. + * + *

The returned array may contain {@code null}, indicating an empty {@link RequestMapping} value. + * * @param handlerType the handler type to introspect * @return the array of mapped URLs */ @@ -156,19 +153,25 @@ public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandler public void doWith(Method method) { RequestMapping mapping = AnnotationUtils.findAnnotation(method, RequestMapping.class); if (mapping != null) { - String[] mappedPaths = mapping.value(); - for (String mappedPath : mappedPaths) { - addUrlsForPath(urls, mappedPath); + String[] mappedPatterns = mapping.value(); + if (mappedPatterns.length > 0) { + for (String mappedPattern : mappedPatterns) { + addUrlsForPath(urls, mappedPattern); + } + } else { + // empty method-level RequestMapping + urls.add(null); } } } - }); + }, ReflectionUtils.NON_BRIDGED_METHODS); } return StringUtils.toStringArray(urls); } /** * Add URLs and/or URL patterns for the given path. + * * @param urls the Set of URLs for the current bean * @param path the currently introspected path */ @@ -180,9 +183,9 @@ public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandler } } - /** * Validate the given annotated handler against the current request. + * * @see #validateMapping */ @Override @@ -197,8 +200,9 @@ public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandler } /** - * Validate the given type-level mapping metadata against the current request, - * checking HTTP request method and parameter conditions. + * Validate the given type-level mapping metadata against the current request, checking HTTP request method and + * parameter conditions. + * * @param mapping the mapping metadata to validate * @param request current HTTP request * @throws Exception if validation failed @@ -220,9 +224,9 @@ public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandler String[] mappedHeaders = mapping.headers(); if (!ServletAnnotationMappingUtils.checkHeaders(mappedHeaders, request)) { - throw new ServletRequestBindingException("Header conditions \"" + - StringUtils.arrayToDelimitedString(mappedHeaders, ", ") + - "\" not met for actual request"); + throw new ServletRequestBindingException( + "Header conditions \"" + StringUtils.arrayToDelimitedString(mappedHeaders, ", ") + + "\" not met for actual request"); } } 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 6c079491c80..8b0ca88b607 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 @@ -308,6 +308,19 @@ public class UriTemplateServletAnnotationControllerTests { assertEquals("test-42", response.getContentAsString()); } + /* + * See SPR-6640 + */ + @Test + public void menuTree() throws Exception { + initServlet(MenuTreeController.class); + + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/book/menu/type/M5"); + MockHttpServletResponse response = new MockHttpServletResponse(); + servlet.service(request, response); + assertEquals("M5", response.getContentAsString()); + } + /* * Controllers @@ -533,4 +546,14 @@ public class UriTemplateServletAnnotationControllerTests { } + @RequestMapping("/*/menu/**") + public static class MenuTreeController { + + @RequestMapping("type/{var}") + public void getFirstLevelFunctionNodes(@PathVariable("var") String var, Writer writer) throws IOException { + writer.write(var); + } + } + + }