diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java index fe93c5b159a..4ea7e7e6ab2 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java +++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java @@ -79,6 +79,9 @@ import java.util.concurrent.Callable; * 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+}. + * Additionally, {@code @PathVariable} can be used on a + * {@link java.util.Map Map<String, String>} to gain access to all + * URI template variables. *
  • {@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, diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolver.java b/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolver.java index 0fb76c9bfb4..3f351595ecd 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolver.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -44,24 +44,25 @@ import org.springframework.web.multipart.MultipartResolver; import org.springframework.web.util.WebUtils; /** - * Resolves method arguments annotated with @{@link RequestParam}, arguments of - * type {@link MultipartFile} in conjunction with Spring's {@link MultipartResolver} - * abstraction, and arguments of type {@code javax.servlet.http.Part} in conjunction - * with Servlet 3.0 multipart requests. This resolver can also be created in default - * resolution mode in which simple types (int, long, etc.) not annotated - * with @{@link RequestParam} are also treated as request parameters with the + * Resolves method arguments annotated with @{@link RequestParam}, arguments of + * type {@link MultipartFile} in conjunction with Spring's {@link MultipartResolver} + * abstraction, and arguments of type {@code javax.servlet.http.Part} in conjunction + * with Servlet 3.0 multipart requests. This resolver can also be created in default + * resolution mode in which simple types (int, long, etc.) not annotated + * with @{@link RequestParam} are also treated as request parameters with the * parameter name derived from the argument name. - * - *

    If the method parameter type is {@link Map}, the request parameter name is used to - * resolve the request parameter String value. The value is then converted to a {@link Map} - * via type conversion assuming a suitable {@link Converter} or {@link PropertyEditor} has - * been registered. If a request parameter name is not specified with a {@link Map} method - * parameter type, the {@link RequestParamMapMethodArgumentResolver} is used instead - * providing access to all request parameters in the form of a map. - * - *

    A {@link WebDataBinder} is invoked to apply type conversion to resolved request + * + *

    If the method parameter type is {@link Map}, the name specified in the + * annotation is used to resolve the request parameter String value. The value is + * then converted to a {@link Map} via type conversion assuming a suitable + * {@link Converter} or {@link PropertyEditor} has been registered. + * Or if a request parameter name is not specified the + * {@link RequestParamMapMethodArgumentResolver} is used instead to provide + * access to all request parameters in the form of a map. + * + *

    A {@link WebDataBinder} is invoked to apply type conversion to resolved request * header values that don't yet match the method parameter type. - * + * * @author Arjen Poutsma * @author Rossen Stoyanchev * @since 3.1 @@ -72,15 +73,15 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod private final boolean useDefaultResolution; /** - * @param beanFactory a bean factory used for resolving ${...} placeholder - * and #{...} SpEL expressions in default values, or {@code null} if default + * @param beanFactory a bean factory used for resolving ${...} placeholder + * and #{...} SpEL expressions in default values, or {@code null} if default * values are not expected to contain expressions - * @param useDefaultResolution in default resolution mode a method argument - * that is a simple type, as defined in {@link BeanUtils#isSimpleProperty}, - * is treated as a request parameter even if it itsn't annotated, the + * @param useDefaultResolution in default resolution mode a method argument + * that is a simple type, as defined in {@link BeanUtils#isSimpleProperty}, + * is treated as a request parameter even if it itsn't annotated, the * request parameter name is derived from the method parameter name. */ - public RequestParamMethodArgumentResolver(ConfigurableBeanFactory beanFactory, + public RequestParamMethodArgumentResolver(ConfigurableBeanFactory beanFactory, boolean useDefaultResolution) { super(beanFactory); this.useDefaultResolution = useDefaultResolution; @@ -89,15 +90,15 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod /** * Supports the following: *

    */ @@ -131,8 +132,8 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod @Override protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) { RequestParam annotation = parameter.getParameterAnnotation(RequestParam.class); - return (annotation != null) ? - new RequestParamNamedValueInfo(annotation) : + return (annotation != null) ? + new RequestParamNamedValueInfo(annotation) : new RequestParamNamedValueInfo(); } @@ -140,9 +141,9 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest webRequest) throws Exception { Object arg; - + HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class); - MultipartHttpServletRequest multipartRequest = + MultipartHttpServletRequest multipartRequest = WebUtils.getNativeRequest(servletRequest, MultipartHttpServletRequest.class); if (MultipartFile.class.equals(parameter.getParameterType())) { @@ -174,7 +175,7 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod } } } - + return arg; } @@ -184,7 +185,7 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod throw new MultipartException("The current request is not a multipart request"); } } - + private boolean isMultipartFileCollection(MethodParameter parameter) { Class paramType = parameter.getParameterType(); if (Collection.class.equals(paramType) || List.class.isAssignableFrom(paramType)){ @@ -206,7 +207,7 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod private RequestParamNamedValueInfo() { super("", false, ValueConstants.DEFAULT_NONE); } - + private RequestParamNamedValueInfo(RequestParam annotation) { super(annotation.value(), annotation.required(), annotation.defaultValue()); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/PathVariableMapMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/PathVariableMapMethodArgumentResolver.java new file mode 100644 index 00000000000..b0a24ced357 --- /dev/null +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/PathVariableMapMethodArgumentResolver.java @@ -0,0 +1,72 @@ +/* + * Copyright 2002-2012 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.servlet.mvc.method.annotation; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.core.MethodParameter; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.ServletRequestBindingException; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; +import org.springframework.web.servlet.HandlerMapping; + +/** + * Resolves {@link Map} method arguments annotated with an @{@link PathVariable} + * where the annotation does not specify a path variable name. The created + * {@link Map} contains all URI template name/value pairs. + * + * @author Rossen Stoyanchev + * @since 3.2 + * @see PathVariableMethodArgumentResolver + */ +public class PathVariableMapMethodArgumentResolver implements HandlerMethodArgumentResolver { + + public boolean supportsParameter(MethodParameter parameter) { + PathVariable annot = parameter.getParameterAnnotation(PathVariable.class); + return ((annot != null) && (Map.class.isAssignableFrom(parameter.getParameterType())) + && (!StringUtils.hasText(annot.value()))); + } + + /** + * Return a Map with all URI template variables. + * @throws ServletRequestBindingException if no URI vars are found in the + * request attribute {@link HandlerMapping#URI_TEMPLATE_VARIABLES_ATTRIBUTE} + */ + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + + @SuppressWarnings("unchecked") + Map uriTemplateVars = + (Map) webRequest.getAttribute( + HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST); + + if (CollectionUtils.isEmpty(uriTemplateVars)) { + throw new ServletRequestBindingException( + "No URI template variables for method parameter type [" + parameter.getParameterType() + "]"); + } + + return new LinkedHashMap(uriTemplateVars); + } + +} \ No newline at end of file diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/PathVariableMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/PathVariableMethodArgumentResolver.java index b74b6309e5a..9e5a88524ac 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/PathVariableMethodArgumentResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/PathVariableMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2012 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. @@ -16,10 +16,13 @@ package org.springframework.web.servlet.mvc.method.annotation; +import java.beans.PropertyEditor; import java.util.HashMap; import java.util.Map; import org.springframework.core.MethodParameter; +import org.springframework.core.convert.converter.Converter; +import org.springframework.util.StringUtils; import org.springframework.web.bind.ServletRequestBindingException; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.PathVariable; @@ -27,6 +30,7 @@ import org.springframework.web.bind.annotation.ValueConstants; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver; +import org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.View; @@ -34,13 +38,23 @@ import org.springframework.web.servlet.View; /** * Resolves method arguments annotated with an @{@link PathVariable}. * - *

    An @{@link PathVariable} is a named value that gets resolved from a URI template variable. It is always - * required and does not have a default value to fall back on. See the base class - * {@link org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver} for more information on how named values are processed. - * - *

    A {@link WebDataBinder} is invoked to apply type conversion to resolved path variable values that + *

    An @{@link PathVariable} is a named value that gets resolved from a URI + * template variable. It is always required and does not have a default value + * to fall back on. See the base class + * {@link org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver} + * for more information on how named values are processed. + * + *

    If the method parameter type is {@link Map}, the name specified in the + * annotation is used to resolve the URI variable String value. The value is + * then converted to a {@link Map} via type conversion assuming a suitable + * {@link Converter} or {@link PropertyEditor} has been registered. + * Or if the annotation does not specify name the + * {@link RequestParamMapMethodArgumentResolver} is used instead to provide + * access to all URI variables in a map. + * + *

    A {@link WebDataBinder} is invoked to apply type conversion to resolved path variable values that * don't yet match the method parameter type. - * + * * @author Rossen Stoyanchev * @author Arjen Poutsma * @since 3.1 @@ -52,7 +66,14 @@ public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethod } public boolean supportsParameter(MethodParameter parameter) { - return parameter.hasParameterAnnotation(PathVariable.class); + if (!parameter.hasParameterAnnotation(PathVariable.class)) { + return false; + } + if (Map.class.isAssignableFrom(parameter.getParameterType())) { + String paramName = parameter.getParameterAnnotation(PathVariable.class).value(); + return StringUtils.hasText(paramName); + } + return true; } @Override @@ -64,7 +85,7 @@ public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethod @Override @SuppressWarnings("unchecked") protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception { - Map uriTemplateVars = + Map uriTemplateVars = (Map) request.getAttribute( HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST); return (uriTemplateVars != null) ? uriTemplateVars.get(name) : null; @@ -79,10 +100,10 @@ public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethod @Override @SuppressWarnings("unchecked") - protected void handleResolvedValue(Object arg, - String name, + protected void handleResolvedValue(Object arg, + String name, MethodParameter parameter, - ModelAndViewContainer mavContainer, + ModelAndViewContainer mavContainer, NativeWebRequest request) { String key = View.PATH_VARIABLES; int scope = RequestAttributes.SCOPE_REQUEST; diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java index 7f852cf60ea..683f0304810 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java @@ -454,6 +454,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false)); resolvers.add(new RequestParamMapMethodArgumentResolver()); resolvers.add(new PathVariableMethodArgumentResolver()); + resolvers.add(new PathVariableMapMethodArgumentResolver()); resolvers.add(new ServletModelAttributeMethodProcessor(false)); resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters())); resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters())); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/PathVariableMapMethodArgumentResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/PathVariableMapMethodArgumentResolverTests.java new file mode 100644 index 00000000000..415be1c80ba --- /dev/null +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/PathVariableMapMethodArgumentResolverTests.java @@ -0,0 +1,104 @@ +/* + * Copyright 2002-2012 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.servlet.mvc.method.annotation; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.core.MethodParameter; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.web.bind.ServletRequestBindingException; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.context.request.ServletWebRequest; +import org.springframework.web.method.support.ModelAndViewContainer; +import org.springframework.web.servlet.HandlerMapping; + +/** + * Test fixture with {@link PathVariableMapMethodArgumentResolver}. + * + * @author Rossen Stoyanchev + */ +public class PathVariableMapMethodArgumentResolverTests { + + private PathVariableMapMethodArgumentResolver resolver; + + private MethodParameter paramMap; + + private MethodParameter paramNamedMap; + + private MethodParameter paramMapNoAnnot; + + private ModelAndViewContainer mavContainer; + + private ServletWebRequest webRequest; + + private MockHttpServletRequest request; + + @Before + public void setUp() throws Exception { + resolver = new PathVariableMapMethodArgumentResolver(); + + Method method = getClass().getMethod("handle", Map.class, Map.class, Map.class); + paramMap = new MethodParameter(method, 0); + paramNamedMap = new MethodParameter(method, 1); + paramMapNoAnnot = new MethodParameter(method, 2); + + mavContainer = new ModelAndViewContainer(); + request = new MockHttpServletRequest(); + webRequest = new ServletWebRequest(request, new MockHttpServletResponse()); + } + + @Test + public void supportsParameter() { + assertTrue(resolver.supportsParameter(paramMap)); + assertFalse(resolver.supportsParameter(paramNamedMap)); + assertFalse(resolver.supportsParameter(paramMapNoAnnot)); + } + + @Test + public void resolveArgument() throws Exception { + Map uriTemplateVars = new HashMap(); + uriTemplateVars.put("name1", "value1"); + uriTemplateVars.put("name2", "value2"); + request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVars); + + Object result = resolver.resolveArgument(paramMap, mavContainer, webRequest, null); + + assertEquals(uriTemplateVars, result); + } + + @Test(expected=ServletRequestBindingException.class) + public void resolveArgumentNoUriVars() throws Exception { + resolver.resolveArgument(paramMap, mavContainer, webRequest, null); + } + + + public void handle( + @PathVariable Map map, + @PathVariable(value = "name") Map namedMap, + Map mapWithoutAnnotat) { + } + +} \ No newline at end of file diff --git a/src/dist/changelog.txt b/src/dist/changelog.txt index aa107215522..f2940ec853d 100644 --- a/src/dist/changelog.txt +++ b/src/dist/changelog.txt @@ -22,6 +22,7 @@ Changes in version 3.2 M1 * fix content negotiation issue when sorting selected media types by quality value * Prevent further writing to the response when @ResponseStatus contains a reason * Deprecate HttpStatus codes 419, 420, 421 +* support access to all URI vars via @PathVariable Map Changes in version 3.1.1 (2012-02-16) ------------------------------------- diff --git a/src/reference/docbook/mvc.xml b/src/reference/docbook/mvc.xml index 80b8ae89b39..330ec31873e 100644 --- a/src/reference/docbook/mvc.xml +++ b/src/reference/docbook/mvc.xml @@ -992,6 +992,11 @@ public String findPet(@PathVariable String owne return "displayPet"; } + When a @PathVariable annotation is + used on a Map<String, String> argument, the + map is populated with all URI template variables. + + A URI template can be assembled from type and path level @RequestMapping annotations. As a result the findPet() method can be invoked with a URL