();
+
+ for (int i = 0; i < patternParts.length && i < pathParts.length; i++) {
+ String patternPart = patternParts[i];
+ String pathPart = pathParts[i];
+ int patternEnd = patternPart.length() -1 ;
+ if (patternEnd > 1 && patternPart.charAt(0) == '{' && patternPart.charAt(patternEnd) == '}') {
+ String varName = patternPart.substring(1, patternEnd);
+ variables.put(varName, pathPart);
}
- buffer.append(pathParts[i]);
}
- return buffer.toString();
+ return variables;
}
}
diff --git a/org.springframework.core/src/main/java/org/springframework/util/PathMatcher.java b/org.springframework.core/src/main/java/org/springframework/util/PathMatcher.java
index b7671d2f872..4ebbf32176b 100644
--- a/org.springframework.core/src/main/java/org/springframework/util/PathMatcher.java
+++ b/org.springframework.core/src/main/java/org/springframework/util/PathMatcher.java
@@ -16,6 +16,8 @@
package org.springframework.util;
+import java.util.Map;
+
/**
* Strategy interface for String-based path matching.
*
@@ -88,4 +90,16 @@ public interface PathMatcher {
*/
String extractPathWithinPattern(String pattern, String path);
+ /**
+ * Given a pattern and a full path, extract the URI template variables. URI template
+ * variables are expressed through curly brackets ('{' and '}').
+ *
+ * For example: For pattern "/hotels/{hotel}" and path "/hotels/1", this method will
+ * return a map containing "hotel"->"1".
+ *
+ * @param pattern the path pattern, possibly containing URI templates
+ * @param path the full path to extract template variables from
+ * @return a map, containing variable names as keys; variables values as values
+ */
+ Map extractUriTemplateVariables(String pattern, String path);
}
diff --git a/org.springframework.core/src/test/java/org/springframework/util/PathMatcherTests.java b/org.springframework.core/src/test/java/org/springframework/util/AntPathMatcherTests.java
similarity index 93%
rename from org.springframework.core/src/test/java/org/springframework/util/PathMatcherTests.java
rename to org.springframework.core/src/test/java/org/springframework/util/AntPathMatcherTests.java
index 8ac4e00801c..c855c66e1eb 100644
--- a/org.springframework.core/src/test/java/org/springframework/util/PathMatcherTests.java
+++ b/org.springframework.core/src/test/java/org/springframework/util/AntPathMatcherTests.java
@@ -16,18 +16,31 @@
package org.springframework.util;
-import junit.framework.TestCase;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
/**
* @author Alef Arendsen
* @author Seth Ladd
* @author Juergen Hoeller
+ * @author Arjen Poutsma
*/
-public class PathMatcherTests extends TestCase {
+public class AntPathMatcherTests {
+
+ private AntPathMatcher pathMatcher;
- public void testAntPathMatcher() {
- PathMatcher pathMatcher = new AntPathMatcher();
+ @Before
+ public void createMatcher() {
+ pathMatcher = new AntPathMatcher();
+ }
+ @Test
+ public void standard() {
// test exact matching
assertTrue(pathMatcher.match("test", "test"));
assertTrue(pathMatcher.match("/test", "/test"));
@@ -109,9 +122,8 @@ public class PathMatcherTests extends TestCase {
assertTrue(pathMatcher.match("", ""));
}
- public void testAntPathMatcherWithMatchStart() {
- PathMatcher pathMatcher = new AntPathMatcher();
-
+ @Test
+ public void withMatchStart() {
// test exact matching
assertTrue(pathMatcher.matchStart("test", "test"));
assertTrue(pathMatcher.matchStart("/test", "/test"));
@@ -197,8 +209,8 @@ public class PathMatcherTests extends TestCase {
assertTrue(pathMatcher.matchStart("", ""));
}
- public void testAntPathMatcherWithUniqueDeliminator() {
- AntPathMatcher pathMatcher = new AntPathMatcher();
+ @Test
+ public void uniqueDeliminator() {
pathMatcher.setPathSeparator(".");
// test exact matching
@@ -259,9 +271,8 @@ public class PathMatcherTests extends TestCase {
assertFalse(pathMatcher.match(".*bla.test", "XXXbl.test"));
}
- public void testAntPathMatcherExtractPathWithinPattern() throws Exception {
- PathMatcher pathMatcher = new AntPathMatcher();
-
+ @Test
+ public void extractPathWithinPattern() throws Exception {
assertEquals("", pathMatcher.extractPathWithinPattern("/docs/commit.html", "/docs/commit.html"));
assertEquals("cvs/commit", pathMatcher.extractPathWithinPattern("/docs/*", "/docs/cvs/commit"));
@@ -282,4 +293,17 @@ public class PathMatcherTests extends TestCase {
assertEquals("docs/cvs/commit.html", pathMatcher.extractPathWithinPattern("/d?cs/**/*.html", "/docs/cvs/commit.html"));
}
+ @Test
+ public void extractUriTemplateVariables() throws Exception {
+ Map result = pathMatcher.extractUriTemplateVariables("/hotels/{hotel}", "/hotels/1");
+ assertEquals(Collections.singletonMap("hotel", "1"), result);
+
+ result = pathMatcher.extractUriTemplateVariables("/hotels/{hotel}/bookings/{booking}", "/hotels/1/bookings/2");
+ Map expected = new LinkedHashMap();
+ expected.put("hotel", "1");
+ expected.put("booking", "2");
+ assertEquals(expected, result);
+ }
+
+
}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/PathVariable.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/PathVariable.java
new file mode 100644
index 00000000000..9fff5eff2e2
--- /dev/null
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/PathVariable.java
@@ -0,0 +1,26 @@
+package org.springframework.web.bind.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation which indicates that a method parameter should be bound to a URI template variable. Supported for {@link
+ * RequestMapping} annotated handler methods in Servlet environments.
+ *
+ * @author Arjen Poutsma
+ * @see RequestMapping
+ * @see org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
+ * @since 3.0
+ */
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface PathVariable {
+
+ /** The URI template variable to bind to. */
+ String value() default "";
+
+}
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java
index 75ed5560eeb..cd80754cf62 100644
--- a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java
@@ -72,6 +72,9 @@ import java.lang.annotation.Target;
* {@link RequestParam @RequestParam} annotated parameters for access to
* specific Servlet/Portlet request parameters. Parameter values will be
* converted to the declared method argument type.
+ * {@link PathVariable @PathVariable} annotated parameters for acces to
+ * URI template values (i.e. /hotels/{hotel}). Variable values will be
+ * converted to the declared method argument type.
* {@link java.util.Map} / {@link org.springframework.ui.Model} /
* {@link org.springframework.ui.ModelMap} for enriching the implicit model
* that will be exposed to the web view.
diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java
index 8e50de38d2e..45a781c2a9f 100644
--- a/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java
+++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java
@@ -44,6 +44,7 @@ import org.springframework.validation.Errors;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.support.DefaultSessionAttributeStore;
import org.springframework.web.bind.support.SessionAttributeStore;
@@ -158,11 +159,11 @@ public class HandlerMethodInvoker {
String paramName = null;
boolean paramRequired = false;
String paramDefaultValue = null;
+ String pathVarName = null;
String attrName = null;
Object[] paramAnns = methodParam.getParameterAnnotations();
- for (int j = 0; j < paramAnns.length; j++) {
- Object paramAnn = paramAnns[j];
+ for (Object paramAnn : paramAnns) {
if (RequestParam.class.isInstance(paramAnn)) {
RequestParam requestParam = (RequestParam) paramAnn;
paramName = requestParam.value();
@@ -173,21 +174,24 @@ public class HandlerMethodInvoker {
else if (ModelAttribute.class.isInstance(paramAnn)) {
ModelAttribute attr = (ModelAttribute) paramAnn;
attrName = attr.value();
+ } else if (PathVariable.class.isInstance(paramAnn)) {
+ PathVariable pathVar = (PathVariable) paramAnn;
+ pathVarName = pathVar.value();
}
}
- if (paramName != null && attrName != null) {
- throw new IllegalStateException("@RequestParam and @ModelAttribute are an exclusive choice -" +
- "do not specify both on the same parameter: " + handlerMethod);
+ if ((paramName != null && attrName != null) || (paramName != null && pathVarName != null) ||
+ (pathVarName != null && attrName != null)) {
+ throw new IllegalStateException("@RequestParam, @PathVariable and @ModelAttribute are exclusive " +
+ "choices - do not specify both on the same parameter: " + handlerMethod);
}
- Class paramType = methodParam.getParameterType();
-
- if (paramName == null && attrName == null) {
+ if (paramName == null && attrName == null && pathVarName == null) {
Object argValue = resolveCommonArgument(methodParam, webRequest);
if (argValue != WebArgumentResolver.UNRESOLVED) {
args[i] = argValue;
}
else {
+ Class paramType = methodParam.getParameterType();
if (Model.class.isAssignableFrom(paramType) || Map.class.isAssignableFrom(paramType)) {
args[i] = implicitModel;
}
@@ -223,13 +227,15 @@ public class HandlerMethodInvoker {
i++;
}
implicitModel.putAll(binder.getBindingResult().getModel());
+ } else if (pathVarName != null) {
+ args[i] = resolvePathVariable(pathVarName, methodParam, webRequest, handler);
}
}
return args;
}
- private void initBinder(Object handler, String attrName, WebDataBinder binder, NativeWebRequest webRequest)
+ protected void initBinder(Object handler, String attrName, WebDataBinder binder, NativeWebRequest webRequest)
throws Exception {
if (this.bindingInitializer != null) {
@@ -276,8 +282,7 @@ public class HandlerMethodInvoker {
String paramDefaultValue = null;
Object[] paramAnns = methodParam.getParameterAnnotations();
- for (int j = 0; j < paramAnns.length; j++) {
- Object paramAnn = paramAnns[j];
+ for (Object paramAnn : paramAnns) {
if (RequestParam.class.isInstance(paramAnn)) {
RequestParam requestParam = (RequestParam) paramAnn;
paramName = requestParam.value();
@@ -328,7 +333,7 @@ public class HandlerMethodInvoker {
Object handlerForInitBinderCall) throws Exception {
Class paramType = methodParam.getParameterType();
- if ("".equals(paramName)) {
+ if (paramName.length() == 0) {
paramName = methodParam.getParameterName();
if (paramName == null) {
throw new IllegalStateException("No parameter specified for @RequestParam argument of type [" +
@@ -393,6 +398,18 @@ public class HandlerMethodInvoker {
return binder;
}
+ /**
+ * Resolves the given {@link org.springframework.web.bind.annotation.PathVariable @PathVariable} variable. Overriden in
+ * {@link org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.ServletHandlerMethodInvoker},
+ * throws an UnsupportedOperationException by default.
+ */
+ protected Object resolvePathVariable(String pathVarName,
+ MethodParameter methodParam,
+ NativeWebRequest webRequest,
+ Object handlerForInitBinderCall) throws Exception {
+ throw new UnsupportedOperationException("@PathVariable not supported");
+ }
+
@SuppressWarnings("unchecked")
public final void updateModelAttributes(Object handler,
Map mavModel,
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 6458beea6d0..a447cd8b2f2 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
@@ -64,6 +64,16 @@ public interface HandlerMapping {
*/
String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";
+ /**
+ * Name of the {@link HttpServletRequest} attribute that contains the URI
+ * templates map, mapping variable names to values.
+ * Note: This attribute is not required to be supported by all
+ * HandlerMapping implementations. URL-based HandlerMappings will
+ * typically support it, but handlers should not necessarily expect
+ * this request attribute to be present in all scenarios.
+ */
+ String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";
+
/**
* Return a handler and any interceptors for this request. The choice may be made
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 b594b856f61..d3e32e27d45 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
@@ -17,7 +17,6 @@
package org.springframework.web.servlet.handler;
import java.util.Collections;
-import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
@@ -26,6 +25,7 @@ import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.BeansException;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
import org.springframework.util.PathMatcher;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerMapping;
@@ -47,6 +47,7 @@ import org.springframework.web.util.UrlPathHelper;
* path pattern that matches the current request path.
*
* @author Juergen Hoeller
+ * @author Arjen Poutsma
* @since 16.04.2003
* @see #setAlwaysUseFullPath
* @see #setUrlDecode
@@ -62,7 +63,7 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
private boolean lazyInitHandlers = false;
- private final Map handlerMap = new LinkedHashMap();
+ private final Map handlerMap = new LinkedHashMap();
/**
@@ -170,7 +171,7 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
}
if (rawHandler != null) {
validateHandler(rawHandler, request);
- handler = buildPathExposingHandler(rawHandler, lookupPath);
+ handler = buildPathExposingHandler(rawHandler, lookupPath, null);
}
}
if (handler != null && logger.isDebugEnabled()) {
@@ -200,12 +201,11 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
Object handler = this.handlerMap.get(urlPath);
if (handler != null) {
validateHandler(handler, request);
- return buildPathExposingHandler(handler, urlPath);
+ return buildPathExposingHandler(handler, urlPath, null);
}
// Pattern match?
String bestPathMatch = null;
- for (Iterator it = this.handlerMap.keySet().iterator(); it.hasNext();) {
- String registeredPath = (String) it.next();
+ for (String registeredPath : this.handlerMap.keySet()) {
if (getPathMatcher().match(registeredPath, urlPath) &&
(bestPathMatch == null || bestPathMatch.length() < registeredPath.length())) {
bestPathMatch = registeredPath;
@@ -215,7 +215,9 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
handler = this.handlerMap.get(bestPathMatch);
validateHandler(handler, request);
String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestPathMatch, urlPath);
- return buildPathExposingHandler(handler, pathWithinMapping);
+ Map uriTemplateVariables =
+ getPathMatcher().extractUriTemplateVariables(bestPathMatch, urlPath);
+ return buildPathExposingHandler(handler, pathWithinMapping, uriTemplateVariables);
}
// No handler found...
return null;
@@ -234,15 +236,18 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
/**
* Build a handler object for the given raw handler, exposing the actual
- * handler as well as the {@link #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE}
- * before executing the handler.
+ * handler, the {@link #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE}, as well as
+ * the {@link #URI_TEMPLATE_VARIABLES_ATTRIBUTE} before executing the handler.
* The default implementation builds a {@link HandlerExecutionChain}
- * with a special interceptor that exposes the path attribute.
+ * with a special interceptor that exposes the path attribute and uri template variables
* @param rawHandler the raw handler to expose
* @param pathWithinMapping the path to expose before executing the handler
+ * @param uriTemplateVariables the URI template variables, can be null if no variables found
* @return the final handler object
*/
- protected Object buildPathExposingHandler(Object rawHandler, String pathWithinMapping) {
+ protected Object buildPathExposingHandler(Object rawHandler,
+ String pathWithinMapping,
+ Map uriTemplateVariables) {
// Bean name or resolved handler?
if (rawHandler instanceof String) {
String handlerName = (String) rawHandler;
@@ -250,6 +255,9 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
}
HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler);
chain.addInterceptor(new PathExposingHandlerInterceptor(pathWithinMapping));
+ if (!CollectionUtils.isEmpty(uriTemplateVariables)) {
+ chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables));
+ }
return chain;
}
@@ -263,6 +271,15 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, pathWithinMapping);
}
+ /**
+ * Expose the URI templates variables as request attribute.
+ * @param uriTemplateVariables the URI template variables
+ * @param request the request to expose the path to
+ * @see #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE
+ */
+ protected void exposeUriTemplateVariables(Map uriTemplateVariables, HttpServletRequest request) {
+ request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);
+ }
/**
* Register the specified handler for the given URL paths.
@@ -273,8 +290,8 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
*/
protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException {
Assert.notNull(urlPaths, "URL path array must not be null");
- for (int j = 0; j < urlPaths.length; j++) {
- registerHandler(urlPaths[j], beanName);
+ for (String urlPath : urlPaths) {
+ registerHandler(urlPath, beanName);
}
}
@@ -350,7 +367,7 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
private final String pathWithinMapping;
- public PathExposingHandlerInterceptor(String pathWithinMapping) {
+ private PathExposingHandlerInterceptor(String pathWithinMapping) {
this.pathWithinMapping = pathWithinMapping;
}
@@ -361,4 +378,24 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
}
}
+ /**
+ * Special interceptor for exposing the
+ * {@link AbstractUrlHandlerMapping#URI_TEMPLATE_VARIABLES_ATTRIBUTE} attribute.
+ * @link AbstractUrlHandlerMapping#exposePathWithinMapping
+ */
+ private class UriTemplateVariablesHandlerInterceptor extends HandlerInterceptorAdapter {
+
+ private final Map uriTemplateVariables;
+
+ private UriTemplateVariablesHandlerInterceptor(Map uriTemplateVariables) {
+ this.uriTemplateVariables = uriTemplateVariables;
+ }
+
+ @Override
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
+ exposeUriTemplateVariables(this.uriTemplateVariables, request);
+ return true;
+ }
+ }
+
}
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 37670bea4f3..d7dcd327471 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
@@ -74,6 +74,7 @@ import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.servlet.HandlerAdapter;
+import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.mvc.multiaction.InternalPathMethodNameResolver;
@@ -420,9 +421,12 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
+ /**
+ * Servlet-specific subclass of {@link HandlerMethodResolver}.
+ */
private class ServletHandlerMethodResolver extends HandlerMethodResolver {
- public ServletHandlerMethodResolver(Class> handlerType) {
+ private ServletHandlerMethodResolver(Class> handlerType) {
super(handlerType);
}
@@ -569,6 +573,9 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
+ /**
+ * Servlet-specific subclass of {@link HandlerMethodInvoker}.
+ */
private class ServletHandlerMethodInvoker extends HandlerMethodInvoker {
private boolean responseArgumentUsed = false;
@@ -607,6 +614,33 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
}
+ @Override
+ @SuppressWarnings({"unchecked"})
+ protected Object resolvePathVariable(String pathVarName,
+ MethodParameter methodParam,
+ NativeWebRequest webRequest,
+ Object handlerForInitBinderCall) throws Exception {
+ Class paramType = methodParam.getParameterType();
+ if (pathVarName.length() == 0) {
+ pathVarName = methodParam.getParameterName();
+ if (pathVarName == null) {
+ throw new IllegalStateException("No variable name specified for @PathVariable argument of type [" +
+ paramType.getName() + "], and no parameter name information found in class file either.");
+ }
+ }
+ HttpServletRequest servletRequest = (HttpServletRequest) webRequest.getNativeRequest();
+ Map uriTemplateVariables =
+ (Map) servletRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
+ if (uriTemplateVariables == null || !uriTemplateVariables.containsKey(pathVarName)) {
+ throw new IllegalStateException("Could not find @PathVariable [" + pathVarName + "] in @RequestMapping");
+ }
+ String pathVarValue = uriTemplateVariables.get(pathVarName);
+
+ WebDataBinder binder = createBinder(webRequest, null, pathVarName);
+ initBinder(handlerForInitBinderCall, pathVarName, binder, webRequest);
+ return binder.convertIfNecessary(pathVarValue, paramType, methodParam);
+ }
+
@Override
protected Object resolveStandardArgument(Class parameterType, NativeWebRequest webRequest)
throws Exception {
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 3dbaf590cb5..64c77b456ed 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
@@ -21,7 +21,6 @@ import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
-
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@@ -152,8 +151,8 @@ public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandler
RequestMapping mapping = method.getAnnotation(RequestMapping.class);
if (mapping != null) {
String[] mappedPaths = mapping.value();
- for (int i = 0; i < mappedPaths.length; i++) {
- addUrlsForPath(urls, mappedPaths[i]);
+ for (String mappedPath : mappedPaths) {
+ addUrlsForPath(urls, mappedPath);
}
}
}
@@ -214,4 +213,6 @@ public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandler
}
}
+
+
}
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 2dae75fb3d7..bcf535702fd 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
@@ -40,6 +40,7 @@ import org.junit.Test;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.aop.interceptor.SimpleTraceInterceptor;
import org.springframework.aop.support.DefaultPointcutAdvisor;
+import org.springframework.beans.BeansException;
import org.springframework.beans.DerivedTestBean;
import org.springframework.beans.ITestBean;
import org.springframework.beans.TestBean;
@@ -63,6 +64,7 @@ import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
@@ -796,6 +798,29 @@ public class ServletAnnotationControllerTests {
}
}
+ @Test
+ public void uriTemplates() throws Exception {
+ DispatcherServlet servlet = new DispatcherServlet() {
+ @Override
+ protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) throws BeansException {
+ GenericWebApplicationContext wac = new GenericWebApplicationContext();
+ wac.registerBeanDefinition("controller", new RootBeanDefinition(UriTemplateController.class));
+ wac.refresh();
+ return wac;
+ }
+ };
+ servlet.init(new MockServletConfig());
+
+ MockHttpServletRequest request = new MockHttpServletRequest("GET", "/hotels/42/bookings/21");
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ servlet.service(request, response);
+ assertEquals("test-42-21", response.getContentAsString());
+ }
+
+ /*
+ * Controllers
+ */
+
@RequestMapping("/myPath.do")
private static class MyController extends AbstractController {
@@ -1285,7 +1310,6 @@ public class ServletAnnotationControllerTests {
@Controller
public static class MethodNotAllowedController {
-
@RequestMapping(value="/myPath.do", method = RequestMethod.DELETE)
public void delete() {
}
@@ -1314,4 +1338,15 @@ public class ServletAnnotationControllerTests {
}
}
+ @Controller
+ public static class UriTemplateController {
+
+ @RequestMapping("/hotels/{hotel}/bookings/{booking}")
+ public void handle(@PathVariable("hotel") int hotel, @PathVariable int booking, HttpServletResponse response)
+ throws IOException {
+ response.getWriter().write("test-" + hotel + "-" + booking);
+ }
+
+ }
+
}