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() { + } + } + +}