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 ccbcd55311a..9c0de4787e1 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
@@ -266,6 +266,18 @@ import java.util.concurrent.Callable;
@Mapping
public @interface RequestMapping {
+
+ /**
+ * Assign a name to this mapping.
+ *
Supported at the method and also at type level!
+ * When used on both levels, a combined name is derived by
+ * concatenation with "#" as separator.
+ *
+ * @see org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder
+ * @see org.springframework.web.servlet.handler.HandlerMethodMappingNamingStrategy
+ */
+ String name() default "";
+
/**
* The primary mapping expressed by this annotation.
*
In a Servlet environment: the path mapping URIs (e.g. "/myPath.do").
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 619252bf7fc..f763d1288ca 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
@@ -256,34 +256,45 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod
}
@Override
- public void contributeMethodArgument(MethodParameter parameter, Object value,
+ public void contributeMethodArgument(MethodParameter param, Object value,
UriComponentsBuilder builder, Map uriVariables, ConversionService conversionService) {
- Class> paramType = parameter.getParameterType();
+ Class> paramType = param.getParameterType();
if (Map.class.isAssignableFrom(paramType) || MultipartFile.class.equals(paramType) ||
"javax.servlet.http.Part".equals(paramType.getName())) {
return;
}
- RequestParam annot = parameter.getParameterAnnotation(RequestParam.class);
- String name = StringUtils.isEmpty(annot.value()) ? parameter.getParameterName() : annot.value();
+ RequestParam annot = param.getParameterAnnotation(RequestParam.class);
+ String name = (annot == null || StringUtils.isEmpty(annot.value()) ? param.getParameterName() : annot.value());
if (value == null) {
builder.queryParam(name);
}
else if (value instanceof Collection) {
for (Object element : (Collection>) value) {
- element = formatUriValue(conversionService, TypeDescriptor.nested(parameter, 1), element);
+ element = formatUriValue(conversionService, TypeDescriptor.nested(param, 1), element);
builder.queryParam(name, element);
}
}
else {
- builder.queryParam(name, formatUriValue(conversionService, new TypeDescriptor(parameter), value));
+ builder.queryParam(name, formatUriValue(conversionService, new TypeDescriptor(param), value));
}
}
protected String formatUriValue(ConversionService cs, TypeDescriptor sourceType, Object value) {
- return (cs != null ? (String) cs.convert(value, sourceType, STRING_TYPE_DESCRIPTOR) : null);
+ if (value == null) {
+ return null;
+ }
+ else if (value instanceof String) {
+ return (String) value;
+ }
+ else if (cs != null) {
+ return (String) cs.convert(value, sourceType, STRING_TYPE_DESCRIPTOR);
+ }
+ else {
+ return value.toString();
+ }
}
diff --git a/spring-web/src/main/java/org/springframework/web/method/support/CompositeUriComponentsContributor.java b/spring-web/src/main/java/org/springframework/web/method/support/CompositeUriComponentsContributor.java
index 640172797f0..35957766ede 100644
--- a/spring-web/src/main/java/org/springframework/web/method/support/CompositeUriComponentsContributor.java
+++ b/spring-web/src/main/java/org/springframework/web/method/support/CompositeUriComponentsContributor.java
@@ -38,7 +38,7 @@ import org.springframework.web.util.UriComponentsBuilder;
*/
public class CompositeUriComponentsContributor implements UriComponentsContributor {
- private final List contributors = new LinkedList();
+ private final List contributors = new LinkedList();
private final ConversionService conversionService;
@@ -81,18 +81,13 @@ public class CompositeUriComponentsContributor implements UriComponentsContribut
* will be used by default.
* @param contributors a collection of {@link UriComponentsContributor}
* or {@link HandlerMethodArgumentResolver}s.
- * @param conversionService a ConversionService to use when method argument values
+ * @param cs a ConversionService to use when method argument values
* need to be formatted as Strings before being added to the URI
*/
- public CompositeUriComponentsContributor(Collection> contributors, ConversionService conversionService) {
+ public CompositeUriComponentsContributor(Collection> contributors, ConversionService cs) {
Assert.notNull(contributors, "'uriComponentsContributors' must not be null");
- for (Object contributor : contributors) {
- if (contributor instanceof UriComponentsContributor) {
- this.contributors.add((UriComponentsContributor) contributor);
- }
- }
- this.conversionService =
- (conversionService != null ? conversionService : new DefaultFormattingConversionService());
+ this.contributors.addAll(contributors);
+ this.conversionService = (cs != null ? cs : new DefaultFormattingConversionService());
}
@@ -102,9 +97,17 @@ public class CompositeUriComponentsContributor implements UriComponentsContribut
@Override
public boolean supportsParameter(MethodParameter parameter) {
- for (UriComponentsContributor contributor : this.contributors) {
- if (contributor.supportsParameter(parameter)) {
- return true;
+ for (Object c : this.contributors) {
+ if (c instanceof UriComponentsContributor) {
+ UriComponentsContributor contributor = (UriComponentsContributor) c;
+ if (contributor.supportsParameter(parameter)) {
+ return true;
+ }
+ }
+ else if (c instanceof HandlerMethodArgumentResolver) {
+ if (((HandlerMethodArgumentResolver) c).supportsParameter(parameter)) {
+ return false;
+ }
}
}
return false;
@@ -114,10 +117,18 @@ public class CompositeUriComponentsContributor implements UriComponentsContribut
public void contributeMethodArgument(MethodParameter parameter, Object value,
UriComponentsBuilder builder, Map uriVariables, ConversionService conversionService) {
- for (UriComponentsContributor contributor : this.contributors) {
- if (contributor.supportsParameter(parameter)) {
- contributor.contributeMethodArgument(parameter, value, builder, uriVariables, conversionService);
- break;
+ for (Object c : this.contributors) {
+ if (c instanceof UriComponentsContributor) {
+ UriComponentsContributor contributor = (UriComponentsContributor) c;
+ if (contributor.supportsParameter(parameter)) {
+ contributor.contributeMethodArgument(parameter, value, builder, uriVariables, conversionService);
+ break;
+ }
+ }
+ else if (c instanceof HandlerMethodArgumentResolver) {
+ if (((HandlerMethodArgumentResolver) c).supportsParameter(parameter)) {
+ break;
+ }
}
}
}
diff --git a/spring-web/src/test/java/org/springframework/web/method/support/CompositeUriComponentsContributorTests.java b/spring-web/src/test/java/org/springframework/web/method/support/CompositeUriComponentsContributorTests.java
new file mode 100644
index 00000000000..5a9fe5afc14
--- /dev/null
+++ b/spring-web/src/test/java/org/springframework/web/method/support/CompositeUriComponentsContributorTests.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2002-2014 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.method.support;
+
+import org.junit.Test;
+import org.springframework.core.MethodParameter;
+import org.springframework.util.ClassUtils;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver;
+import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Unit tests for
+ * {@link org.springframework.web.method.support.CompositeUriComponentsContributor}.
+ *
+ * @author Rossen Stoyanchev
+ */
+public class CompositeUriComponentsContributorTests {
+
+
+ @Test
+ public void supportsParameter() {
+
+ List resolvers = new ArrayList();
+ resolvers.add(new RequestParamMethodArgumentResolver(false));
+ resolvers.add(new RequestHeaderMethodArgumentResolver(null));
+ resolvers.add(new RequestParamMethodArgumentResolver(true));
+
+ Method method = ClassUtils.getMethod(this.getClass(), "handleRequest", String.class, String.class, String.class);
+
+ CompositeUriComponentsContributor contributor = new CompositeUriComponentsContributor(resolvers);
+ assertTrue(contributor.supportsParameter(new MethodParameter(method, 0)));
+ assertTrue(contributor.supportsParameter(new MethodParameter(method, 1)));
+ assertFalse(contributor.supportsParameter(new MethodParameter(method, 2)));
+ }
+
+
+ @SuppressWarnings("unused")
+ public void handleRequest(@RequestParam String p1, String p2, @RequestHeader String h) {
+ }
+
+}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java
index 3ed1dd67e2f..2e02f62cf11 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java
@@ -71,10 +71,15 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap
private boolean detectHandlerMethodsInAncestorContexts = false;
+ private HandlerMethodMappingNamingStrategy namingStrategy;
+
+
private final Map handlerMethods = new LinkedHashMap();
private final MultiValueMap urlMap = new LinkedMultiValueMap();
+ private final MultiValueMap nameMap = new LinkedMultiValueMap();
+
/**
* Whether to detect handler methods in beans in ancestor ApplicationContexts.
@@ -88,6 +93,16 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap
this.detectHandlerMethodsInAncestorContexts = detectHandlerMethodsInAncestorContexts;
}
+ /**
+ * Configure the naming strategy to use for assigning a default name to every
+ * mapped handler method.
+ *
+ * @param namingStrategy strategy to use.
+ */
+ public void setHandlerMethodMappingNamingStrategy(HandlerMethodMappingNamingStrategy namingStrategy) {
+ this.namingStrategy = namingStrategy;
+ }
+
/**
* Return a map with all handler methods and their mappings.
*/
@@ -95,6 +110,14 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap
return Collections.unmodifiableMap(this.handlerMethods);
}
+ /**
+ * Return the handler methods mapped to the mapping with the given name.
+ * @param mappingName the mapping name
+ */
+ public List getHandlerMethodsForMappingName(String mappingName) {
+ return this.nameMap.get(mappingName);
+ }
+
/**
* Detects handler methods at initialization.
*/
@@ -203,6 +226,34 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap
this.urlMap.add(pattern, mapping);
}
}
+
+ if (this.namingStrategy != null) {
+ String name = this.namingStrategy.getName(newHandlerMethod, mapping);
+ updateNameMap(name, newHandlerMethod);
+ }
+ }
+
+ private void updateNameMap(String name, HandlerMethod newHandlerMethod) {
+
+ List handlerMethods = this.nameMap.get(name);
+ if (handlerMethods != null) {
+ for (HandlerMethod handlerMethod : handlerMethods) {
+ if (handlerMethod.getMethod().equals(newHandlerMethod.getMethod())) {
+ logger.trace("Mapping name already registered. Multiple controller instances perhaps?");
+ return;
+ }
+ }
+ }
+
+ logger.trace("Mapping name=" + name);
+ this.nameMap.add(name, newHandlerMethod);
+
+ if (this.nameMap.get(name).size() > 1) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Mapping name clash for handlerMethods=" + this.nameMap.get(name) +
+ ". Consider assigning explicit names.");
+ }
+ }
}
/**
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMethodMappingNamingStrategy.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMethodMappingNamingStrategy.java
new file mode 100644
index 00000000000..50eaf016118
--- /dev/null
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/HandlerMethodMappingNamingStrategy.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2002-2014 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.handler;
+
+import org.springframework.web.method.HandlerMethod;
+
+import java.lang.reflect.Method;
+
+/**
+ * A strategy for assigning a name to a controller method mapping.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.1
+ */
+public interface HandlerMethodMappingNamingStrategy {
+
+ /**
+ * Determine the name for the given HandlerMethod and mapping.
+ *
+ * @param handlerMethod the handler method
+ * @param mapping the mapping
+ *
+ * @return the name
+ */
+ String getName(HandlerMethod handlerMethod, T mapping);
+
+}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfo.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfo.java
index d3fd61bd1b7..eba39776ed2 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfo.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfo.java
@@ -18,6 +18,7 @@ package org.springframework.web.servlet.mvc.method;
import javax.servlet.http.HttpServletRequest;
+import org.springframework.util.StringUtils;
import org.springframework.web.servlet.mvc.condition.ConsumesRequestCondition;
import org.springframework.web.servlet.mvc.condition.HeadersRequestCondition;
import org.springframework.web.servlet.mvc.condition.ParamsRequestCondition;
@@ -45,6 +46,8 @@ import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondit
*/
public final class RequestMappingInfo implements RequestCondition {
+ private final String name;
+
private final PatternsRequestCondition patternsCondition;
private final RequestMethodsRequestCondition methodsCondition;
@@ -60,13 +63,11 @@ public final class RequestMappingInfo implements RequestCondition custom) {
+ this.name = (StringUtils.hasText(name) ? name : null);
this.patternsCondition = (patterns != null ? patterns : new PatternsRequestCondition());
this.methodsCondition = (methods != null ? methods : new RequestMethodsRequestCondition());
this.paramsCondition = (params != null ? params : new ParamsRequestCondition());
@@ -76,15 +77,32 @@ public final class RequestMappingInfo implements RequestCondition custom) {
+
+ this(null, patterns, methods, params, headers, consumes, produces, custom);
+ }
+
/**
* Re-create a RequestMappingInfo with the given custom request condition.
*/
public RequestMappingInfo(RequestMappingInfo info, RequestCondition> customRequestCondition) {
- this(info.patternsCondition, info.methodsCondition, info.paramsCondition, info.headersCondition,
+ this(info.name, info.patternsCondition, info.methodsCondition, info.paramsCondition, info.headersCondition,
info.consumesCondition, info.producesCondition, customRequestCondition);
}
+ /**
+ * Return the name for this mapping, or {@code null}.
+ */
+ public String getName() {
+ return this.name;
+ }
+
/**
* Returns the URL patterns of this {@link RequestMappingInfo};
* or instance with 0 patterns, never {@code null}.
@@ -148,6 +166,7 @@ public final class RequestMappingInfo implements RequestCondition {
+
+ protected RequestMappingInfoHandlerMapping() {
+ setHandlerMethodMappingNamingStrategy(new RequestMappingInfoHandlerMethodMappingNamingStrategy());
+ }
+
/**
* Get the URL path patterns associated with this {@link RequestMappingInfo}.
*/
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMethodMappingNamingStrategy.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMethodMappingNamingStrategy.java
new file mode 100644
index 00000000000..b600b0f8d5c
--- /dev/null
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMethodMappingNamingStrategy.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2002-2014 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;
+
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.handler.HandlerMethodMappingNamingStrategy;
+
+import java.lang.reflect.Method;
+
+/**
+ * A {@link org.springframework.web.servlet.handler.HandlerMethodMappingNamingStrategy
+ * HandlerMethodMappingNamingStrategy} for {@code RequestMappingInfo}-based handler
+ * method mappings.
+ *
+ * If the {@code RequestMappingInfo} name attribute is set, its value is used.
+ * Otherwise the name is based on the capital letters of the class name,
+ * followed by "#" as a separator, and the method name. For example "TC#getFoo"
+ * for a class named TestController with method getFoo.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.1
+ */
+public class RequestMappingInfoHandlerMethodMappingNamingStrategy
+ implements HandlerMethodMappingNamingStrategy {
+
+ /** Separator between the type and method-level parts of a HandlerMethod mapping name */
+ public static final String SEPARATOR = "#";
+
+
+ @Override
+ public String getName(HandlerMethod handlerMethod, RequestMappingInfo mapping) {
+ if (mapping.getName() != null) {
+ return mapping.getName();
+ }
+ StringBuilder sb = new StringBuilder();
+ String simpleTypeName = handlerMethod.getBeanType().getSimpleName();
+ for (int i = 0 ; i < simpleTypeName.length(); i++) {
+ if (Character.isUpperCase(simpleTypeName.charAt(i))) {
+ sb.append(simpleTypeName.charAt(i));
+ }
+ }
+ sb.append(SEPARATOR).append(handlerMethod.getMethod().getName());
+ return sb.toString();
+ }
+
+}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java
index e9a0b9be6d9..d6a90398062 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java
@@ -19,6 +19,7 @@ package org.springframework.web.servlet.mvc.method.annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
@@ -29,6 +30,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.target.EmptyTargetSource;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.cglib.core.SpringNamingPolicy;
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.Enhancer;
@@ -50,9 +52,11 @@ import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
+import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
import org.springframework.web.method.support.CompositeUriComponentsContributor;
import org.springframework.web.servlet.DispatcherServlet;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
@@ -195,6 +199,43 @@ public class MvcUriComponentsBuilder extends UriComponentsBuilder {
return fromMethod(info.getControllerMethod(), info.getArgumentValues());
}
+ /**
+ * Create a {@link UriComponentsBuilder} from a request mapping identified
+ * by name. The configured
+ * {@link org.springframework.web.servlet.handler.HandlerMethodMappingNamingStrategy
+ * HandlerMethodMappingNamingStrategy} assigns a default name to every
+ * {@code @RequestMapping} method but an explicit name may also be assigned
+ * through the {@code @RequestMapping} name attribute.
+ *
+ * This is intended for use in EL expressions, typically in JSPs or other
+ * view templates, which can use the convenience method:
+ * {@link org.springframework.web.servlet.support.RequestContext#getMvcUrl(String, Object...)
+ * RequestContext.getMvcUrl(String, Object...)}).
+ *
+ *
The default naming convention for mappings is based on the capital
+ * letters of the class name, followed by "#" as a separator, and the method
+ * name. For example "TC#getFoo" for a class named TestController with method
+ * getFoo. Use explicit names where the naming convention does not produce
+ * unique results.
+ *
+ * @param name the mapping name
+ * @param argumentValues argument values for the controller method; those values
+ * are important for {@code @RequestParam} and {@code @PathVariable} arguments
+ * but may be passed as {@code null} otherwise.
+ *
+ * @return the UriComponentsBuilder
+ *
+ * @throws java.lang.IllegalStateException if the mapping name is not found
+ * or there is no unique match
+ */
+ public static UriComponentsBuilder fromMappingName(String name, Object... argumentValues) {
+ RequestMappingInfoHandlerMapping hm = getRequestMappingInfoHandlerMapping();
+ List handlerMethods = hm.getHandlerMethodsForMappingName(name);
+ Assert.state(handlerMethods != null, "Mapping name not found: " + name);
+ Assert.state(handlerMethods.size() == 1, "No unique match for mapping name " + name + ": " + handlerMethods);
+ return fromMethod(handlerMethods.get(0).getMethod(), argumentValues);
+ }
+
/**
* Create a {@link UriComponentsBuilder} from the mapping of a controller method
* and an array of method argument values. The array of values must match the
@@ -263,6 +304,37 @@ public class MvcUriComponentsBuilder extends UriComponentsBuilder {
}
protected static CompositeUriComponentsContributor getConfiguredUriComponentsContributor() {
+ WebApplicationContext wac = getWebApplicationContext();
+ if (wac == null) {
+ return null;
+ }
+ try {
+ return wac.getBean(MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME, CompositeUriComponentsContributor.class);
+ }
+ catch (NoSuchBeanDefinitionException ex) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("No CompositeUriComponentsContributor bean with name '" +
+ MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME + "'");
+ }
+ return null;
+ }
+ }
+
+ protected static RequestMappingInfoHandlerMapping getRequestMappingInfoHandlerMapping() {
+ WebApplicationContext wac = getWebApplicationContext();
+ Assert.notNull(wac, "Cannot lookup handler method mappings without WebApplicationContext");
+ try {
+ return wac.getBean(RequestMappingInfoHandlerMapping.class);
+ }
+ catch (NoUniqueBeanDefinitionException ex) {
+ throw new IllegalStateException("More than one RequestMappingInfoHandlerMapping beans found", ex);
+ }
+ catch (NoSuchBeanDefinitionException ex) {
+ throw new IllegalStateException("No RequestMappingInfoHandlerMapping bean", ex);
+ }
+ }
+
+ private static WebApplicationContext getWebApplicationContext() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes == null) {
logger.debug("No request bound to the current thread: is DispatcherSerlvet used?");
@@ -281,17 +353,7 @@ public class MvcUriComponentsBuilder extends UriComponentsBuilder {
logger.debug("No WebApplicationContext found: not in a DispatcherServlet request?");
return null;
}
-
- try {
- return wac.getBean(MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME, CompositeUriComponentsContributor.class);
- }
- catch (NoSuchBeanDefinitionException ex) {
- if (logger.isDebugEnabled()) {
- logger.debug("No CompositeUriComponentsContributor bean with name '" +
- MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME + "'");
- }
- return null;
- }
+ return wac;
}
/**
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 f6ed15f3c4e..0a47c4b83c6 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
@@ -123,21 +123,32 @@ public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethod
}
@Override
- public void contributeMethodArgument(MethodParameter parameter, Object value,
- UriComponentsBuilder builder, Map uriVariables, ConversionService conversionService) {
+ public void contributeMethodArgument(MethodParameter param, Object value,
+ UriComponentsBuilder builder, Map uriVariables, ConversionService cs) {
- if (Map.class.isAssignableFrom(parameter.getParameterType())) {
+ if (Map.class.isAssignableFrom(param.getParameterType())) {
return;
}
- PathVariable annot = parameter.getParameterAnnotation(PathVariable.class);
- String name = StringUtils.isEmpty(annot.value()) ? parameter.getParameterName() : annot.value();
+ PathVariable annot = param.getParameterAnnotation(PathVariable.class);
+ String name = (StringUtils.isEmpty(annot.value()) ? param.getParameterName() : annot.value());
+ value = formatUriValue(cs, new TypeDescriptor(param), value);
+ uriVariables.put(name, value);
+ }
- if (conversionService != null) {
- value = conversionService.convert(value, new TypeDescriptor(parameter), STRING_TYPE_DESCRIPTOR);
+ protected String formatUriValue(ConversionService cs, TypeDescriptor sourceType, Object value) {
+ if (value == null) {
+ return null;
+ }
+ else if (value instanceof String) {
+ return (String) value;
+ }
+ else if (cs != null) {
+ return (String) cs.convert(value, sourceType, STRING_TYPE_DESCRIPTOR);
+ }
+ else {
+ return value.toString();
}
-
- uriVariables.put(name, value);
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java
index 50934511c67..4d614e55d7d 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java
@@ -234,6 +234,7 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
protected RequestMappingInfo createRequestMappingInfo(RequestMapping annotation, RequestCondition> customCondition) {
String[] patterns = resolveEmbeddedValuesInPatterns(annotation.value());
return new RequestMappingInfo(
+ annotation.name(),
new PatternsRequestCondition(patterns, getUrlPathHelper(), getPathMatcher(),
this.useSuffixPatternMatch, this.useTrailingSlashMatch, this.fileExtensions),
new RequestMethodsRequestCondition(annotation.method()),
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContext.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContext.java
index 276907d8da8..4b30f3023a7 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContext.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContext.java
@@ -47,6 +47,7 @@ import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.LocaleContextResolver;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.ThemeResolver;
+import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import org.springframework.web.util.HtmlUtils;
import org.springframework.web.util.UriTemplate;
import org.springframework.web.util.UrlPathHelper;
@@ -596,6 +597,18 @@ public class RequestContext {
return this.urlPathHelper.getOriginatingQueryString(this.request);
}
+ /**
+ * Return a URL derived from a controller method's {@code @RequestMapping}.
+ * This method internally uses
+ * {@link org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder#fromMappingName(String, Object...)
+ * MvcUriComponentsBuilder#fromMappingName(String, Object...)}. See its
+ * Javadoc for more details.
+ */
+ public String getMvcUrl(String mappingName, Object... handlerMethodArguments) {
+ return MvcUriComponentsBuilder.fromMappingName(
+ mappingName, handlerMethodArguments).build().encode().toUriString();
+ }
+
/**
* Retrieve the message for the given code, using the "defaultHtmlEscape" setting.
* @param code code of the message
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMethodMappingNamingStrategyTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMethodMappingNamingStrategyTests.java
new file mode 100644
index 00000000000..4e1565faabe
--- /dev/null
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMethodMappingNamingStrategyTests.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2002-2014 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;
+
+import org.junit.Test;
+import org.springframework.util.ClassUtils;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.handler.HandlerMethodMappingNamingStrategy;
+
+import java.lang.reflect.Method;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Unit tests for
+ * {@link org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMethodMappingNamingStrategy}.
+ *
+ * @author Rossen Stoyanchev
+ */
+public class RequestMappingInfoHandlerMethodMappingNamingStrategyTests {
+
+
+ @Test
+ public void getNameExplicit() {
+
+ Method method = ClassUtils.getMethod(TestController.class, "handle");
+ HandlerMethod handlerMethod = new HandlerMethod(new TestController(), method);
+
+ RequestMappingInfo rmi = new RequestMappingInfo("foo", null, null, null, null, null, null, null);
+
+ HandlerMethodMappingNamingStrategy strategy = new RequestMappingInfoHandlerMethodMappingNamingStrategy();
+
+ assertEquals("foo", strategy.getName(handlerMethod, rmi));
+ }
+
+ @Test
+ public void getNameConvention() {
+
+ Method method = ClassUtils.getMethod(TestController.class, "handle");
+ HandlerMethod handlerMethod = new HandlerMethod(new TestController(), method);
+
+ RequestMappingInfo rmi = new RequestMappingInfo(null, null, null, null, null, null, null, null);
+
+ HandlerMethodMappingNamingStrategy strategy = new RequestMappingInfoHandlerMethodMappingNamingStrategy();
+
+ assertEquals("TC#handle", strategy.getName(handlerMethod, rmi));
+ }
+
+
+ private static class TestController {
+
+ @RequestMapping
+ public void handle() {
+ }
+ }
+
+}