From cf5db8362b3b95e2555e4e1600e3c6919e952f01 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 25 Oct 2013 22:25:06 -0400 Subject: [PATCH] Replace MvcUrls with MvcUriComponentsBuilder Issue: SPR-8826 --- .../CompositeUriComponentsContributor.java | 125 +++++ .../web/util/UriComponentsBuilder.java | 11 + .../AnnotationDrivenBeanDefinitionParser.java | 65 ++- .../config/DefaultMvcUrlsFactoryBean.java | 89 ---- .../WebMvcConfigurationSupport.java | 12 +- .../annotation/MvcUriComponentsBuilder.java | 430 ++++++++++++++++++ .../servlet/mvc/support/DefaultMvcUrls.java | 172 ------- .../web/servlet/mvc/support/MvcUrlUtils.java | 203 --------- .../web/servlet/mvc/support/MvcUrls.java | 111 ----- .../web/servlet/config/MvcNamespaceTests.java | 28 +- .../WebMvcConfigurationSupportTests.java | 26 +- .../MvcUriComponentsContributorTests.java} | 147 +++--- .../servlet/mvc/support/MvcUrlUtilsTests.java | 122 ----- 13 files changed, 722 insertions(+), 819 deletions(-) create mode 100644 spring-web/src/main/java/org/springframework/web/method/support/CompositeUriComponentsContributor.java delete mode 100644 spring-webmvc/src/main/java/org/springframework/web/servlet/config/DefaultMvcUrlsFactoryBean.java create mode 100644 spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java delete mode 100644 spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultMvcUrls.java delete mode 100644 spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/MvcUrlUtils.java delete mode 100644 spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/MvcUrls.java rename spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/{support/DefaultMvcUrlsTests.java => method/annotation/MvcUriComponentsContributorTests.java} (60%) delete mode 100644 spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/support/MvcUrlUtilsTests.java 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 new file mode 100644 index 00000000000..dfccebd7d26 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/web/method/support/CompositeUriComponentsContributor.java @@ -0,0 +1,125 @@ +/* + * Copyright 2002-2013 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.springframework.core.MethodParameter; +import org.springframework.core.convert.ConversionService; +import org.springframework.format.support.DefaultFormattingConversionService; +import org.springframework.util.Assert; +import org.springframework.web.util.UriComponentsBuilder; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * A {@link UriComponentsContributor} containing a list of other contributors + * to delegate and also encapsulating a specific {@link ConversionService} to + * use for formatting method argument values to Strings. + * + * @author Rossen Stoyanchev + * @since 4.0 + */ +public class CompositeUriComponentsContributor implements UriComponentsContributor { + + private final List contributors = new ArrayList(); + + private final ConversionService conversionService; + + + /** + * Create an instance from a collection of {@link UriComponentsContributor}s or + * {@link HandlerMethodArgumentResolver}s. Since both of these tend to be implemented + * by the same class, the most convenient option is to obtain the configured + * {@code HandlerMethodArgumentResolvers} in {@code RequestMappingHandlerAdapter} and + * provide that to this constructor. + * + * @param contributors a collection of {@link UriComponentsContributor} + * or {@link HandlerMethodArgumentResolver}s. + */ + public CompositeUriComponentsContributor(Collection contributors) { + this(contributors, null); + } + + /** + * Create an instance from a collection of {@link UriComponentsContributor}s or + * {@link HandlerMethodArgumentResolver}s. Since both of these tend to be implemented + * by the same class, the most convenient option is to obtain the configured + * {@code HandlerMethodArgumentResolvers} in the {@code RequestMappingHandlerAdapter} + * and provide that to this constructor. + *

+ * If the {@link ConversionService} argument is {@code null}, + * {@link org.springframework.format.support.DefaultFormattingConversionService} + * 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 + * need to be formatted as Strings before being added to the URI + */ + public CompositeUriComponentsContributor(Collection contributors, ConversionService conversionService) { + + 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(); + } + + + public boolean hasContributors() { + return this.contributors.isEmpty(); + } + + @Override + public boolean supportsParameter(MethodParameter parameter) { + for (UriComponentsContributor c : this.contributors) { + if (c.supportsParameter(parameter)) { + return true; + } + } + return false; + } + + @Override + public void contributeMethodArgument(MethodParameter parameter, Object value, + UriComponentsBuilder builder, Map uriVariables, ConversionService conversionService) { + + for (UriComponentsContributor c : this.contributors) { + if (c.supportsParameter(parameter)) { + c.contributeMethodArgument(parameter, value, builder, uriVariables, conversionService); + break; + } + } + } + + /** + * An overloaded method that uses the ConversionService created at construction. + */ + public void contributeMethodArgument(MethodParameter parameter, Object value, UriComponentsBuilder builder, + Map uriVariables) { + + this.contributeMethodArgument(parameter, value, builder, uriVariables, this.conversionService); + } + +} diff --git a/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java b/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java index 05fce62b090..a5ca916d6c8 100644 --- a/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java +++ b/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java @@ -555,6 +555,17 @@ public class UriComponentsBuilder { return this; } + /** + * Adds the given query parameters. + * @param params the params + * @return this UriComponentsBuilder + */ + public UriComponentsBuilder queryParams(MultiValueMap params) { + Assert.notNull(params, "'params' must not be null"); + this.queryParams.putAll(params); + return this; + } + /** * Sets the query parameter values overriding all existing query values for * the same parameter. If no values are given, the query parameter is diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java index dcf8afee92f..b8c9cb85233 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java @@ -19,6 +19,8 @@ package org.springframework.web.servlet.config; import java.util.List; import java.util.Properties; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; import org.springframework.beans.factory.config.RuntimeBeanReference; @@ -28,6 +30,7 @@ import org.springframework.beans.factory.support.ManagedList; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.core.convert.ConversionService; import org.springframework.format.support.DefaultFormattingConversionService; import org.springframework.format.support.FormattingConversionServiceFactoryBean; import org.springframework.http.MediaType; @@ -52,6 +55,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; import org.springframework.web.bind.support.WebArgumentResolver; +import org.springframework.web.method.support.CompositeUriComponentsContributor; import org.springframework.web.servlet.HandlerAdapter; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.HandlerMapping; @@ -67,6 +71,7 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import org.springframework.web.servlet.mvc.method.annotation.ServletWebArgumentResolverAdapter; import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; +import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; import org.w3c.dom.Element; /** @@ -207,12 +212,12 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { handlerAdapterDef.getPropertyValues().add("deferredResultInterceptors", deferredResultInterceptors); String handlerAdapterName = parserContext.getReaderContext().registerWithGeneratedName(handlerAdapterDef); - String mvcUrlsName = "mvcUrls"; - RootBeanDefinition mvcUrlsDef = new RootBeanDefinition(DefaultMvcUrlsFactoryBean.class); - mvcUrlsDef.setSource(source); - mvcUrlsDef.getPropertyValues().addPropertyValue("handlerAdapter", handlerAdapterDef); - mvcUrlsDef.getPropertyValues().addPropertyValue("conversionService", conversionService); - parserContext.getReaderContext().getRegistry().registerBeanDefinition(mvcUrlsName, mvcUrlsDef); + String uriCompContribName = MvcUriComponentsBuilder.MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME; + RootBeanDefinition uriCompContribDef = new RootBeanDefinition(CompositeUriComponentsContributorFactoryBean.class); + uriCompContribDef.setSource(source); + uriCompContribDef.getPropertyValues().addPropertyValue("handlerAdapter", handlerAdapterDef); + uriCompContribDef.getPropertyValues().addPropertyValue("conversionService", conversionService); + parserContext.getReaderContext().getRegistry().registerBeanDefinition(uriCompContribName, uriCompContribDef); RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class); csInterceptorDef.setSource(source); @@ -249,7 +254,7 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, methodMappingName)); parserContext.registerComponent(new BeanComponentDefinition(handlerAdapterDef, handlerAdapterName)); - parserContext.registerComponent(new BeanComponentDefinition(mvcUrlsDef, mvcUrlsName)); + parserContext.registerComponent(new BeanComponentDefinition(uriCompContribDef, uriCompContribName)); parserContext.registerComponent(new BeanComponentDefinition(exceptionHandlerExceptionResolver, methodExceptionResolverName)); parserContext.registerComponent(new BeanComponentDefinition(responseStatusExceptionResolver, responseStatusExceptionResolverName)); parserContext.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExceptionResolverName)); @@ -484,4 +489,50 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { return result; } + + /** + * A FactoryBean for a CompositeUriComponentsContributor that obtains the + * HandlerMethodArgumentResolver's configured in RequestMappingHandlerAdapter + * after it is fully initialized. + */ + private static class CompositeUriComponentsContributorFactoryBean + implements InitializingBean, FactoryBean { + + private RequestMappingHandlerAdapter handlerAdapter; + + private ConversionService conversionService; + + private CompositeUriComponentsContributor uriComponentsContributor; + + + public void setHandlerAdapter(RequestMappingHandlerAdapter handlerAdapter) { + this.handlerAdapter = handlerAdapter; + } + + public void setConversionService(ConversionService conversionService) { + this.conversionService = conversionService; + } + + @Override + public void afterPropertiesSet() throws Exception { + this.uriComponentsContributor = new CompositeUriComponentsContributor( + this.handlerAdapter.getArgumentResolvers(), this.conversionService); + } + + @Override + public CompositeUriComponentsContributor getObject() throws Exception { + return this.uriComponentsContributor; + } + + @Override + public Class getObjectType() { + return CompositeUriComponentsContributor.class; + } + + @Override + public boolean isSingleton() { + return true; + } + } + } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/DefaultMvcUrlsFactoryBean.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/DefaultMvcUrlsFactoryBean.java deleted file mode 100644 index 34c207e9bc5..00000000000 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/DefaultMvcUrlsFactoryBean.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2002-2013 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.config; - -import org.springframework.beans.factory.FactoryBean; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.core.convert.ConversionService; -import org.springframework.web.method.support.HandlerMethodArgumentResolver; -import org.springframework.web.method.support.UriComponentsContributor; -import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; -import org.springframework.web.servlet.mvc.support.DefaultMvcUrls; -import org.springframework.web.servlet.mvc.support.MvcUrls; - - -/** - * A factory bean for creating an instance of {@link MvcUrls} that discovers - * {@link UriComponentsContributor}s by obtaining the - * {@link HandlerMethodArgumentResolver}s configured in a - * {@link RequestMappingHandlerAdapter}. - *

- * This is mainly provided as a convenience in XML configuration. Otherwise call the - * constructors of {@link DefaultMvcUrls} directly. Also note the MVC Java config and - * XML namespace already create an instance of {@link MvcUrls} and when using either - * of them this {@code FactoryBean} is not needed. - * - * @author Rossen Stoyanchev - * @since 4.0 - */ -public class DefaultMvcUrlsFactoryBean implements InitializingBean, FactoryBean { - - private RequestMappingHandlerAdapter handlerAdapter; - - private ConversionService conversionService; - - private MvcUrls mvcUrls; - - - /** - * Provide a {@link RequestMappingHandlerAdapter} from which to obtain - * the list of configured {@link HandlerMethodArgumentResolver}s. This - * is provided for ease of configuration in XML. - */ - public void setHandlerAdapter(RequestMappingHandlerAdapter handlerAdapter) { - this.handlerAdapter = handlerAdapter; - } - - /** - * Configure the {@link ConversionService} instance that {@link MvcUrls} should - * use to format Object values being added to a URI. - */ - public void setConversionService(ConversionService conversionService) { - this.conversionService = conversionService; - } - - @Override - public void afterPropertiesSet() throws Exception { - this.mvcUrls = new DefaultMvcUrls(this.handlerAdapter.getArgumentResolvers(), this.conversionService); - } - - @Override - public MvcUrls getObject() throws Exception { - return this.mvcUrls; - } - - @Override - public Class getObjectType() { - return MvcUrls.class; - } - - @Override - public boolean isSingleton() { - return true; - } - -} diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java index c1f3cab17c0..6dbfa527b19 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java @@ -61,6 +61,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; import org.springframework.web.context.ServletContextAware; +import org.springframework.web.method.support.CompositeUriComponentsContributor; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.servlet.HandlerAdapter; @@ -78,8 +79,6 @@ import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExc import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; -import org.springframework.web.servlet.mvc.support.DefaultMvcUrls; -import org.springframework.web.servlet.mvc.support.MvcUrls; /** * This is the main class providing the configuration behind the MVC Java config. @@ -566,12 +565,13 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv } /** - * Return an instance of {@link MvcUrls} for injection into controllers to create - * URLs by referencing controllers and controller methods. + * Return an instance of {@link CompositeUriComponentsContributor} for use with + * {@link org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder}. */ @Bean - public MvcUrls mvcUrls() { - return new DefaultMvcUrls(requestMappingHandlerAdapter().getArgumentResolvers(), mvcConversionService()); + public CompositeUriComponentsContributor mvcUriComponentsContributor() { + return new CompositeUriComponentsContributor( + requestMappingHandlerAdapter().getArgumentResolvers(), mvcConversionService()); } /** 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 new file mode 100644 index 00000000000..359db0711ea --- /dev/null +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java @@ -0,0 +1,430 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.servlet.mvc.method.annotation; + +import org.aopalliance.intercept.MethodInterceptor; +import org.apache.commons.logging.Log; +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.cglib.proxy.Callback; +import org.springframework.cglib.proxy.Enhancer; +import org.springframework.cglib.proxy.Factory; +import org.springframework.cglib.proxy.MethodProxy; +import org.springframework.core.DefaultParameterNameDiscoverer; +import org.springframework.core.MethodParameter; +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.objenesis.ObjenesisStd; +import org.springframework.util.*; +import org.springframework.web.bind.annotation.RequestMapping; +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.context.support.WebApplicationContextUtils; +import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver; +import org.springframework.web.method.support.CompositeUriComponentsContributor; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import java.lang.reflect.Method; +import java.util.*; + +/** + * A UriComponentsBuilder that helps to build URIs to Spring MVC controllers and methods from their + * request mappings. + * + * @author Oliver Gierke + * @author Rossen Stoyanchev + * + * @since 4.0 + */ + +public class MvcUriComponentsBuilder extends UriComponentsBuilder { + + /** + * Well-known name for the {@link CompositeUriComponentsContributor} object in the bean factory. + */ + public static final String MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME = "mvcUriComponentsContributor"; + + + private static final CompositeUriComponentsContributor defaultUriComponentsContributor; + + private static final PathMatcher pathMatcher = new AntPathMatcher(); + + private static final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); + + private final static ObjenesisStd objenesis = new ObjenesisStd(true); + + private static Log logger = LogFactory.getLog(MvcUriComponentsBuilder.class); + + + static { + defaultUriComponentsContributor = new CompositeUriComponentsContributor( + Arrays.asList( + new PathVariableMethodArgumentResolver(), + new RequestParamMethodArgumentResolver(null, false))); + } + + + /** + * Create a {@link UriComponentsBuilder} by pointing to a controller class. The + * resulting builder contains the current request information up to and including + * the Servlet mapping plus any type-level request mapping. If the controller + * contains multiple mappings, the first one is used. + * + * @param controllerType the controller to create a URI for + * + * @return a UriComponentsBuilder instance + */ + public static UriComponentsBuilder fromController(Class controllerType) { + String mapping = getTypeRequestMapping(controllerType); + return ServletUriComponentsBuilder.fromCurrentServletMapping().path(mapping); + } + + /** + * Create a {@link UriComponentsBuilder} by pointing to a controller method and + * providing method argument values. The method is matched based on the provided + * method name and the number of argument values. If that results in a clash + * (i.e. overloaded methods with the same number of parameters), use + * {@link #fromMethod(java.lang.reflect.Method, Object...)} instead. + *

+ * The argument values are used to prepare the URI for example expanding + * path variables, or adding query parameters. Any other arguments not + * relevant to the URI can be provided as {@literal null} and will be ignored. + *

+ * Additional (custom) argument types can be supported through an implementation + * of {@link org.springframework.web.method.support.UriComponentsContributor}. + * + * @param controllerType the target controller type + * @param methodName the target method name + * @param argumentValues argument values matching to method parameters + * + * @return a UriComponentsBuilder instance + */ + public static UriComponentsBuilder fromMethodName(Class controllerType, + String methodName, Object... argumentValues) { + + Method match = null; + for (Method method : controllerType.getDeclaredMethods()) { + if ((method.getParameterCount() == argumentValues.length) && method.getName().equals(methodName)) { + if (match != null) { + throw new IllegalStateException("Found two methods named '" + methodName + + "' having " + argumentValues + " arguments, controller " + + controllerType.getName()); + } + match = method; + } + } + if (match == null) { + throw new IllegalArgumentException("No method '" + methodName + "' with " + + argumentValues.length + " parameters found in " + controllerType.getName()); + } + return fromMethod(match, argumentValues); + } + + /** + * Create a {@link UriComponentsBuilder} by pointing to a controller method and + * providing method argument values. The method argument values are used to + * prepare the URI for example expanding path variables, or adding request + * parameters. Any other arguments not relevant to the URL can be provided as + * {@literal null} and will be ignored. + *

+ * Additional (custom) argument types can be supported through an implementation + * of {@link org.springframework.web.method.support.UriComponentsContributor}. + * + * @param method the target controller method + * @param argumentValues argument values matching to method parameters + * + * @return a UriComponentsBuilder instance + */ + public static UriComponentsBuilder fromMethod(Method method, Object... argumentValues) { + + UriComponentsBuilder builder = ServletUriComponentsBuilder.newInstance().path(getMethodRequestMapping(method)); + UriComponents uriComponents = applyContributors(builder, method, argumentValues); + + String typePath = getTypeRequestMapping(method.getDeclaringClass()); + String methodPath = uriComponents.getPath(); + String path = pathMatcher.combine(typePath, methodPath); + + return ServletUriComponentsBuilder.fromCurrentServletMapping().path( + path).queryParams(uriComponents.getQueryParams()); + } + + /** + * Create a {@link UriComponents} by invoking a method on a "mock" controller, similar + * to how test frameworks provide mock objects and record method invocations. + *

+ * For example given this controller: + * + *

+	 * @RequestMapping("/people/{id}/addresses")
+	 * class AddressController {
+	 *
+	 *   @RequestMapping("/{country}")
+	 *   public HttpEntity getAddressesForCountry(@PathVariable String country) { … }
+	 *
+	 *   @RequestMapping(value="/", method=RequestMethod.POST)
+	 *   public void addAddress(Address address) { … }
+	 * }
+	 * 
+ * + * A "mock" controller can be used as follows: + * + *
+	 *
+	 * // Inline style with static import of MvcUriComponentsBuilder.mock
+	 *
+	 * MvcUriComponentsBuilder.fromLastCall(
+	 * 		mock(CustomerController.class).showAddresses("US")).buildAndExpand(1);
+	 *
+	 * // Longer style for preparing multiple URIs and for void controller methods
+	 *
+	 * CustomerController controller = MvcUriComponentsBuilder.mock(CustomController.class);
+	 * controller.addAddress(null);
+	 *
+	 * MvcUriComponentsBuilder.fromLastCall(controller);
+	 *
+	 * 
+ * + * The above supports {@codce @PathVariable} and {@code @RequestParam} method parameters. + * Any other arguments can be provided as {@literal null} and will be ignored. + *

+ * Additional (custom) argument types can be supported through an implementation + * of {@link org.springframework.web.method.support.UriComponentsContributor}. + * + * @param methodInvocationInfo either the value returned from a "mock" controller + * invocation or the "mock" controller itself after an invocation + * + * @return a UriComponents instance + */ + public static UriComponentsBuilder fromLastCall(Object methodInvocationInfo) { + + Assert.isInstanceOf(MethodInvocationInfo.class, methodInvocationInfo); + MethodInvocationInfo info = (MethodInvocationInfo) methodInvocationInfo; + + Method method = info.getControllerMethod(); + Object[] argumentValues = info.getArgumentValues(); + + UriComponentsBuilder builder = ServletUriComponentsBuilder.newInstance().path(getMethodRequestMapping(method)); + UriComponents uriComponents = applyContributors(builder, method, argumentValues); + + String typeMapping = getTypeRequestMapping(method.getDeclaringClass()); + String methodMapping = uriComponents.getPath(); + String path = pathMatcher.combine(typeMapping, methodMapping); + + return ServletUriComponentsBuilder.fromCurrentServletMapping().path( + path).queryParams(uriComponents.getQueryParams()); + } + + + private static String getTypeRequestMapping(Class controllerType) { + Assert.notNull(controllerType, "'controllerType' must not be null"); + RequestMapping annot = AnnotationUtils.findAnnotation(controllerType, RequestMapping.class); + if ((annot == null) || ObjectUtils.isEmpty(annot.value()) || StringUtils.isEmpty(annot.value()[0])) { + return "/"; + } + if (annot.value().length > 1) { + logger.warn("Multiple mappings on controller " + controllerType.getName() + ", using the first one"); + } + return annot.value()[0]; + } + + private static String getMethodRequestMapping(Method method) { + RequestMapping annot = AnnotationUtils.findAnnotation(method, RequestMapping.class); + Assert.notNull(annot, "No @RequestMapping on: " + method.toGenericString()); + if (ObjectUtils.isEmpty(annot.value()) || StringUtils.isEmpty(annot.value()[0])) { + return "/"; + } + if (annot.value().length > 1) { + logger.debug("Multiple mappings on method " + method.toGenericString() + ", using first one"); + } + return annot.value()[0]; + } + + private static UriComponents applyContributors(UriComponentsBuilder builder, Method method, Object[] args) { + + CompositeUriComponentsContributor contributor = getConfiguredUriComponentsContributor(); + if (contributor == null) { + logger.debug("Using default CompositeUriComponentsContributor"); + contributor = defaultUriComponentsContributor; + } + + int paramCount = method.getParameterCount(); + int argCount = args.length; + + Assert.isTrue(paramCount == argCount, "Number of method parameters " + paramCount + + " does not match number of argument values " + argCount); + + Map uriVars = new HashMap(); + + for (int i=0; i < paramCount; i++) { + MethodParameter param = new MethodParameter(method, i); + param.initParameterNameDiscovery(parameterNameDiscoverer); + contributor.contributeMethodArgument(param, args[i], builder, uriVars); + } + + return builder.buildAndExpand(uriVars); + } + + protected static CompositeUriComponentsContributor getConfiguredUriComponentsContributor() { + RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); + if (requestAttributes == null) { + logger.debug("No request bound to the current thread: is DispatcherSerlvet used?"); + return null; + } + HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest(); + if (request == null) { + logger.debug("Request bound to current thread is not an HttpServletRequest"); + return null; + } + ServletContext servletContext = request.getServletContext(); + WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(servletContext); + if (wac == null) { + logger.debug("No WebApplicationContext found: no ContextLoaderListener registered?"); + return null; + } + try { + String beanName = MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME; + return wac.getBean(beanName, CompositeUriComponentsContributor.class); + } + catch (NoSuchBeanDefinitionException ex) { + if (logger.isDebugEnabled()) { + logger.debug("No CompositeUriComponentsContributor bean with name '" + + MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME + "'"); + } + return null; + } + } + + /** + * Return a "mock" controller instance. When a method on the mock is invoked, the + * supplied argument values are remembered and the result can then be used to + * prepare a UriComponents through the factory method {@link #fromLastCall(Object)}. + *

+ * The controller can be invoked more than once. However, only the last invocation + * is remembered. This means the same mock controller can be used to create + * multiple links in succession, for example: + * + *

+	 * CustomerController controller = MvcUriComponentsBuilder.mock(CustomController.class);
+	 *
+	 * MvcUriComponentsBuilder.fromLastCall(controller.getFoo(1)).build();
+	 * MvcUriComponentsBuilder.fromLastCall(controller.getFoo(2)).build();
+	 *
+	 * MvcUriComponentsBuilder.fromLastCall(controller.getBar(1)).build();
+	 * MvcUriComponentsBuilder.fromLastCall(controller.getBar(2)).build();
+	 * 
+ * + * If a controller method returns void, use the following style: + * + *
+	 * CustomerController controller = MvcUriComponentsBuilder.mock(CustomController.class);
+	 *
+	 * controller.handleFoo(1);
+	 * MvcUriComponentsBuilder.fromLastCall(controller).build();
+	 *
+	 * controller.handleFoo(2)
+	 * MvcUriComponentsBuilder.fromLastCall(controller).build();
+	 * 
+ * + * @param controllerType the type of controller to mock + */ + public static T mock(Class controllerType) { + Assert.notNull(controllerType, "'controllerType' must not be null"); + return initProxy(controllerType, new ControllerMethodInvocationInterceptor()); + } + + @SuppressWarnings("unchecked") + private static T initProxy(Class type, ControllerMethodInvocationInterceptor interceptor) { + + if (type.isInterface()) { + ProxyFactory factory = new ProxyFactory(EmptyTargetSource.INSTANCE); + factory.addInterface(type); + factory.addInterface(MethodInvocationInfo.class); + factory.addAdvice(interceptor); + return (T) factory.getProxy(); + } + else { + Enhancer enhancer = new Enhancer(); + enhancer.setSuperclass(type); + enhancer.setInterfaces(new Class[]{MethodInvocationInfo.class}); + enhancer.setCallbackType(org.springframework.cglib.proxy.MethodInterceptor.class); + + Factory factory = (Factory) objenesis.newInstance(enhancer.createClass()); + factory.setCallbacks(new Callback[] { interceptor }); + return (T) factory; + } + } + + + private static class ControllerMethodInvocationInterceptor + implements org.springframework.cglib.proxy.MethodInterceptor, MethodInterceptor { + + private static final Method getControllerMethod = + ReflectionUtils.findMethod(MethodInvocationInfo.class, "getControllerMethod"); + + private static final Method getArgumentValues = + ReflectionUtils.findMethod(MethodInvocationInfo.class, "getArgumentValues"); + + + private Method controllerMethod; + + private Object[] argumentValues; + + + @Override + public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) { + + if (getControllerMethod.equals(method)) { + return this.controllerMethod; + } + else if (getArgumentValues.equals(method)) { + return this.argumentValues; + } + else if (ReflectionUtils.isObjectMethod(method)) { + return ReflectionUtils.invokeMethod(method, obj, args); + } + else { + this.controllerMethod = method; + this.argumentValues = args; + + Class returnType = method.getReturnType(); + return void.class.equals(returnType) ? null : returnType.cast(initProxy(returnType, this)); + } + } + + @Override + public Object invoke(org.aopalliance.intercept.MethodInvocation inv) throws Throwable { + return intercept(inv.getThis(), inv.getMethod(), inv.getArguments(), null); + } + } + + public interface MethodInvocationInfo { + + Method getControllerMethod(); + + Object[] getArgumentValues(); + + } + +} \ No newline at end of file diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultMvcUrls.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultMvcUrls.java deleted file mode 100644 index 1b4784d2639..00000000000 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultMvcUrls.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright 2002-2013 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.support; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.springframework.core.LocalVariableTableParameterNameDiscoverer; -import org.springframework.core.MethodParameter; -import org.springframework.core.ParameterNameDiscoverer; -import org.springframework.core.convert.ConversionService; -import org.springframework.format.support.DefaultFormattingConversionService; -import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; -import org.springframework.web.method.support.HandlerMethodArgumentResolver; -import org.springframework.web.method.support.UriComponentsContributor; -import org.springframework.web.servlet.mvc.support.MvcUrlUtils.ControllerMethodValues; -import org.springframework.web.servlet.support.ServletUriComponentsBuilder; -import org.springframework.web.util.UriComponents; -import org.springframework.web.util.UriComponentsBuilder; -import org.springframework.web.util.UriTemplate; - -/** - * A default {@link MvcUrls} implementation. - * - * @author Oliver Gierke - * @author Rossen Stoyanchev - * - * @since 4.0 - */ -public class DefaultMvcUrls implements MvcUrls { - - private static final ParameterNameDiscoverer parameterNameDiscoverer = - new LocalVariableTableParameterNameDiscoverer(); - - - private final List contributors = new ArrayList(); - - private final ConversionService conversionService; - - - /** - * Create an instance providing a collection of {@link UriComponentsContributor}s or - * {@link HandlerMethodArgumentResolver}s. Since both of these tend to be implemented - * by the same class, the most convenient option is to obtain the configured - * {@code HandlerMethodArgumentResolvers} in the {@code RequestMappingHandlerAdapter} - * and provide that to this constructor. - * @param uriComponentsContributors a collection of {@link UriComponentsContributor} - * or {@link HandlerMethodArgumentResolver}s. - */ - public DefaultMvcUrls(Collection uriComponentsContributors) { - this(uriComponentsContributors, null); - } - - /** - * Create an instance providing a collection of {@link UriComponentsContributor}s or - * {@link HandlerMethodArgumentResolver}s. Since both of these tend to be implemented - * by the same class, the most convenient option is to obtain the configured - * {@code HandlerMethodArgumentResolvers} in the {@code RequestMappingHandlerAdapter} - * and provide that to this constructor. - * - *

If the {@link ConversionService} argument is {@code null}, - * {@link DefaultFormattingConversionService} will be used by default. - * @param uriComponentsContributors a collection of {@link UriComponentsContributor} - * or {@link HandlerMethodArgumentResolver}s. - * @param conversionService a ConversionService to use when method argument values - * need to be formatted as Strings before being added to the URI - */ - public DefaultMvcUrls(Collection uriComponentsContributors, ConversionService conversionService) { - Assert.notNull(uriComponentsContributors, "'uriComponentsContributors' must not be null"); - for (Object contributor : uriComponentsContributors) { - if (contributor instanceof UriComponentsContributor) { - this.contributors.add((UriComponentsContributor) contributor); - } - } - this.conversionService = (conversionService != null) ? - conversionService : new DefaultFormattingConversionService(); - } - - - @Override - public UriComponentsBuilder linkToController(Class controllerClass) { - String mapping = MvcUrlUtils.getTypeLevelMapping(controllerClass); - return ServletUriComponentsBuilder.fromCurrentServletMapping().path(mapping); - } - - @Override - public UriComponents linkToMethod(Method method, Object... argumentValues) { - String mapping = MvcUrlUtils.getMethodMapping(method); - UriComponentsBuilder builder = ServletUriComponentsBuilder.fromCurrentServletMapping().path(mapping); - Map uriVars = new HashMap(); - return applyContributers(builder, method, argumentValues, uriVars); - } - - private UriComponents applyContributers(UriComponentsBuilder builder, Method method, - Object[] argumentValues, Map uriVars) { - if (this.contributors.isEmpty()) { - return builder.buildAndExpand(uriVars); - } - - int paramCount = method.getParameters().length; - int argCount = argumentValues.length; - Assert.isTrue(paramCount == argCount, "Number of method parameters " + paramCount + - " does not match number of argument values " + argCount); - - for (int i=0; i < paramCount; i++) { - MethodParameter param = new MethodParameter(method, i); - param.initParameterNameDiscovery(parameterNameDiscoverer); - for (UriComponentsContributor c : this.contributors) { - if (c.supportsParameter(param)) { - c.contributeMethodArgument(param, argumentValues[i], builder, uriVars, this.conversionService); - break; - } - } - } - - return builder.buildAndExpand(uriVars); - } - - @Override - public UriComponents linkToMethodOn(Object mockController) { - Assert.isInstanceOf(ControllerMethodValues.class, mockController); - ControllerMethodValues controllerMethodValues = (ControllerMethodValues) mockController; - - Method method = controllerMethodValues.getControllerMethod(); - Object[] argumentValues = controllerMethodValues.getArgumentValues(); - - Map uriVars = new HashMap(); - addTypeLevelUriVaris(controllerMethodValues, uriVars); - - String mapping = MvcUrlUtils.getMethodMapping(method); - UriComponentsBuilder builder = ServletUriComponentsBuilder.fromCurrentServletMapping().path(mapping); - - return applyContributers(builder, method, argumentValues, uriVars); - } - - private void addTypeLevelUriVaris(ControllerMethodValues info, Map uriVariables) { - Object[] values = info.getTypeLevelUriVariables(); - if (!ObjectUtils.isEmpty(values)) { - - String mapping = MvcUrlUtils.getTypeLevelMapping(info.getControllerMethod().getDeclaringClass()); - - List names = new UriTemplate(mapping).getVariableNames(); - Assert.isTrue(names.size() == values.length, "The provided type-level URI template variables " + - Arrays.toString(values) + " do not match the template " + mapping); - - for (int i=0; i < names.size(); i++) { - uriVariables.put(names.get(i), values[i]); - } - } - } - -} diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/MvcUrlUtils.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/MvcUrlUtils.java deleted file mode 100644 index e1dda18a58d..00000000000 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/MvcUrlUtils.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright 2012-2013 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.support; - -import java.lang.reflect.Method; -import java.util.Set; - -import org.aopalliance.intercept.MethodInterceptor; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.aop.framework.ProxyFactory; -import org.springframework.aop.target.EmptyTargetSource; -import org.springframework.cglib.proxy.Callback; -import org.springframework.cglib.proxy.Enhancer; -import org.springframework.cglib.proxy.Factory; -import org.springframework.cglib.proxy.MethodProxy; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.objenesis.ObjenesisStd; -import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; -import org.springframework.util.ReflectionUtils; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition; -import org.springframework.web.util.UriComponents; - -/** - * Utility methods to support the creation URLs to Spring MVC controllers and controller - * methods. - * - * @author Oliver Gierke - * @author Rossen Stoyanchev - * @since 4.0 - */ -public class MvcUrlUtils { - - private static Log logger = LogFactory.getLog(MvcUrlUtils.class); - - private final static ObjenesisStd OBJENESIS = new ObjenesisStd(true); - - - /** - * Extract the type-level URL mapping or return an empty String. If multiple mappings - * are found, the first one is used. - */ - public static String getTypeLevelMapping(Class controllerType) { - Assert.notNull(controllerType, "'controllerType' must not be null"); - RequestMapping annot = AnnotationUtils.findAnnotation(controllerType, RequestMapping.class); - if ((annot == null) || ObjectUtils.isEmpty(annot.value())) { - return "/"; - } - if (annot.value().length > 1) { - logger.warn("Multiple class level mappings on " + controllerType.getName() + ", using the first one"); - } - return annot.value()[0]; - } - - /** - * Extract the mapping from the given controller method, including both type and - * method-level mappings. If multiple mappings are found, the first one is used. - */ - public static String getMethodMapping(Method method) { - RequestMapping methodAnnot = AnnotationUtils.findAnnotation(method, RequestMapping.class); - Assert.notNull(methodAnnot, "No mappings on " + method.toGenericString()); - PatternsRequestCondition condition = new PatternsRequestCondition(methodAnnot.value()); - - RequestMapping typeAnnot = AnnotationUtils.findAnnotation(method.getDeclaringClass(), RequestMapping.class); - if (typeAnnot != null) { - condition = new PatternsRequestCondition(typeAnnot.value()).combine(condition); - } - - Set patterns = condition.getPatterns(); - if (patterns.size() > 1) { - logger.warn("Multiple mappings on " + method.toGenericString() + ", using the first one"); - } - - return (patterns.size() == 0) ? "/" : patterns.iterator().next(); - } - - /** - * Return a "mock" controller instance. When a controller method is invoked, the - * invoked method and argument values are remembered, and a "mock" value is returned - * so it can be used to help prepare a {@link UriComponents} through - * {@link MvcUrls#linkToMethodOn(Object)}. - * @param controllerType the type of controller to mock, must not be {@literal null}. - * @param typeLevelUriVariables URI variables to expand into the type-level mapping - * @return the created controller instance - */ - public static T controller(Class controllerType, Object... typeLevelUriVariables) { - Assert.notNull(controllerType, "'type' must not be null"); - return initProxy(controllerType, new ControllerMethodInvocationInterceptor(typeLevelUriVariables)); - } - - @SuppressWarnings("unchecked") - private static T initProxy(Class type, ControllerMethodInvocationInterceptor interceptor) { - if (type.isInterface()) { - ProxyFactory factory = new ProxyFactory(EmptyTargetSource.INSTANCE); - factory.addInterface(type); - factory.addInterface(ControllerMethodValues.class); - factory.addAdvice(interceptor); - return (T) factory.getProxy(); - } - else { - Enhancer enhancer = new Enhancer(); - enhancer.setSuperclass(type); - enhancer.setInterfaces(new Class[] { ControllerMethodValues.class }); - enhancer.setCallbackType(org.springframework.cglib.proxy.MethodInterceptor.class); - - Factory factory = (Factory) OBJENESIS.newInstance(enhancer.createClass()); - factory.setCallbacks(new Callback[] { interceptor }); - return (T) factory; - } - } - - - private static class ControllerMethodInvocationInterceptor - implements org.springframework.cglib.proxy.MethodInterceptor, MethodInterceptor { - - private static final Method getTypeLevelUriVariables = - ReflectionUtils.findMethod(ControllerMethodValues.class, "getTypeLevelUriVariables"); - - private static final Method getControllerMethod = - ReflectionUtils.findMethod(ControllerMethodValues.class, "getControllerMethod"); - - private static final Method getArgumentValues = - ReflectionUtils.findMethod(ControllerMethodValues.class, "getArgumentValues"); - - - private final Object[] typeLevelUriVariables; - - private Method controllerMethod; - - private Object[] argumentValues; - - - public ControllerMethodInvocationInterceptor(Object... typeLevelUriVariables) { - this.typeLevelUriVariables = typeLevelUriVariables.clone(); - } - - - @Override - public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) { - - if (getTypeLevelUriVariables.equals(method)) { - return this.typeLevelUriVariables; - } - else if (getControllerMethod.equals(method)) { - return this.controllerMethod; - } - else if (getArgumentValues.equals(method)) { - return this.argumentValues; - } - else if (ReflectionUtils.isObjectMethod(method)) { - return ReflectionUtils.invokeMethod(method, obj, args); - } - else { - this.controllerMethod = method; - this.argumentValues = args; - - Class returnType = method.getReturnType(); - return void.class.equals(returnType) ? null : returnType.cast(initProxy(returnType, this)); - } - } - - @Override - public Object invoke(org.aopalliance.intercept.MethodInvocation inv) throws Throwable { - return intercept(inv.getThis(), inv.getMethod(), inv.getArguments(), null); - } - } - - /** - * Provides information about a controller method that can be used to prepare a URL - * including type-level URI template variables, a method reference, and argument - * values collected through the invocation of a "mock" controller. - *

- * Instances of this interface are returned from - * {@link MvcUrlUtils#controller(Class, Object...) controller(Class, Object...)} and - * are needed for {@link MvcUrls#linkToMethodOn(Object)}. - */ - public interface ControllerMethodValues { - - Object[] getTypeLevelUriVariables(); - - Method getControllerMethod(); - - Object[] getArgumentValues(); - - } - -} diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/MvcUrls.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/MvcUrls.java deleted file mode 100644 index 3f42ed9ff35..00000000000 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/MvcUrls.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2002-2013 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.support; - -import java.lang.reflect.Method; - -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.method.support.UriComponentsContributor; -import org.springframework.web.servlet.config.DefaultMvcUrlsFactoryBean; -import org.springframework.web.util.UriComponents; -import org.springframework.web.util.UriComponentsBuilder; - -/** - * A contract for creating URLs by referencing Spring MVC controllers and methods. - * - *

The MVC Java config and the MVC namespace automatically create an instance of this - * contract for use in controllers and anywhere else during the processing of a request. - * The best way for access it is to have it autowired, or otherwise injected either by - * type or also qualified by name ("mvcUrls") if necessary. - * - *

If not using either option, with explicit configuration it's easy to create an - * instance of {@link DefaultMvcUrls} in Java config or in XML configuration, use - * {@link DefaultMvcUrlsFactoryBean}. - * - * @author Oliver Gierke - * @author Rossen Stoyanchev - * @since 4.0 - */ -public interface MvcUrls { - - /** - * Creates a new {@link UriComponentsBuilder} by pointing to a controller class. The - * resulting builder contains all the current request information up to and including - * the Servlet mapping as well as the portion of the path matching to the controller - * level request mapping. If the controller contains multiple mappings, the - * {@link DefaultMvcUrls} will use the first one. - * @param controllerType the controller type to create a URL to - * @return a builder that can be used to further build the {@link UriComponents}. - */ - UriComponentsBuilder linkToController(Class controllerType); - - /** - * Create a {@link UriComponents} by pointing to a controller method along with method - * argument values. - * - *

Type and method-level mappings of the controller method are extracted and the - * resulting {@link UriComponents} is further enriched with method argument values from - * {@link PathVariable} and {@link RequestParam} parameters. Any other arguments not - * relevant to the building of the URL can be provided as {@literal null} and will be - * ignored. Support for additional custom arguments can be added through a - * {@link UriComponentsContributor}. - * @param method the target controller method - * @param argumentValues argument values matching to method parameters - * @return UriComponents instance, never {@literal null} - */ - UriComponents linkToMethod(Method method, Object... argumentValues); - - /** - * Create a {@link UriComponents} by invoking a method on a "mock" controller similar - * to how test frameworks provide mock objects and record method invocations. The - * static method {@link MvcUrlUtils#controller(Class, Object...)} can be used to - * create a "mock" controller: - * - *

-	 * @RequestMapping("/people/{id}/addresses")
-	 * class AddressController {
-	 *
-	 *   @RequestMapping("/{country}")
-	 *   public HttpEntity<Void> getAddressesForCountry(@PathVariable String country) { ... }
-	 *
-	 *   @RequestMapping(value="/", method=RequestMethod.POST)
-	 *   public void addAddress(Address address) { ... }
-	 * }
-	 *
-	 * // short-hand style with static import of MvcUrlUtils.controller
-	 *
-	 * mvcUrls.linkToMethodOn(controller(CustomerController.class, 1).showAddresses("US"));
-	 *
-	 * // longer style, required for void controller methods
-	 *
-	 * CustomerController controller = MvcUrlUtils.controller(CustomController.class, 1);
-	 * controller.addAddress(null);
-	 *
-	 * mvcUrls.linkToMethodOn(controller);
-	 * 
- * - * The above mechanism supports {@link PathVariable} and {@link RequestParam} method - * arguments. Any other arguments can be provided as {@literal null} and will be - * ignored. Additional custom arguments can be added through an implementation of - * {@link UriComponentsContributor}. - * @param mockController created via {@link MvcUrlUtils#controller(Class, Object...)} - * @return UriComponents instance, never {@literal null} - */ - UriComponents linkToMethodOn(Object mockController); - -} diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java index 5629d14e2ae..1576b56904c 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java @@ -19,8 +19,6 @@ package org.springframework.web.servlet.config; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Method; -import java.text.DateFormat; -import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; import java.util.List; @@ -58,8 +56,6 @@ import org.springframework.web.accept.ContentNegotiationManager; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.context.request.NativeWebRequest; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.context.request.async.CallableProcessingInterceptor; import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter; @@ -67,6 +63,7 @@ import org.springframework.web.context.request.async.DeferredResultProcessingInt import org.springframework.web.context.request.async.DeferredResultProcessingInterceptorAdapter; import org.springframework.web.context.support.GenericWebApplicationContext; import org.springframework.web.method.HandlerMethod; +import org.springframework.web.method.support.CompositeUriComponentsContributor; import org.springframework.web.method.support.InvocableHandlerMethod; import org.springframework.web.servlet.HandlerExecutionChain; import org.springframework.web.servlet.HandlerInterceptor; @@ -80,14 +77,12 @@ import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter; import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; -import org.springframework.web.servlet.mvc.support.MvcUrls; +import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; import org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler; import org.springframework.web.servlet.resource.ResourceHttpRequestHandler; import org.springframework.web.servlet.theme.ThemeChangeInterceptor; -import org.springframework.web.util.UriComponents; import static org.junit.Assert.*; -import static org.springframework.web.servlet.mvc.support.MvcUrlUtils.*; /** * @author Keith Donald @@ -155,20 +150,11 @@ public class MvcNamespaceTests { adapter.handle(request, response, handlerMethod); assertTrue(handler.recordedValidationError); - // MvcUrls - RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(new MockHttpServletRequest())); - try { - Date now = new Date(); - TestController testController = controller(TestController.class); - testController.testBind(now, null, null); - MvcUrls mvcUrls = this.appContext.getBean(MvcUrls.class); - UriComponents uriComponents = mvcUrls.linkToMethodOn(testController); - DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); - assertEquals("http://localhost/?date=" + dateFormat.format(now), uriComponents.toUriString()); - } - finally { - RequestContextHolder.resetRequestAttributes(); - } + CompositeUriComponentsContributor uriComponentsContributor = this.appContext.getBean( + MvcUriComponentsBuilder.MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME, + CompositeUriComponentsContributor.class); + + assertNotNull(uriComponentsContributor); } @Test(expected=TypeMismatchException.class) diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java index 8d5f086e5a8..57c6866f406 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java @@ -21,7 +21,6 @@ import java.util.List; import javax.servlet.http.HttpServletRequest; import org.joda.time.DateTime; -import org.joda.time.format.ISODateTimeFormat; import org.junit.Before; import org.junit.Test; import org.springframework.context.annotation.Bean; @@ -40,9 +39,8 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; +import org.springframework.web.method.support.CompositeUriComponentsContributor; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.HandlerExecutionChain; import org.springframework.web.servlet.handler.AbstractHandlerMapping; @@ -54,11 +52,9 @@ import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExc import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; -import org.springframework.web.servlet.mvc.support.MvcUrls; -import org.springframework.web.util.UriComponents; +import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; import static org.junit.Assert.*; -import static org.springframework.web.servlet.mvc.support.MvcUrlUtils.*; /** * A test fixture with an {@link WebMvcConfigurationSupport} instance. @@ -159,19 +155,13 @@ public class WebMvcConfigurationSupportTests { } @Test - public void mvcUrls() throws Exception { - RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(new MockHttpServletRequest())); - try { - DateTime now = DateTime.now(); - MvcUrls mvcUrls = this.wac.getBean(MvcUrls.class); - UriComponents uriComponents = mvcUrls.linkToMethodOn(controller( - TestController.class).methodWithTwoPathVariables(1, now)); + public void uriComponentsContributor() throws Exception { - assertEquals("/foo/1/bar/" + ISODateTimeFormat.date().print(now), uriComponents.getPath()); - } - finally { - RequestContextHolder.resetRequestAttributes(); - } + CompositeUriComponentsContributor uriComponentsContributor = this.wac.getBean( + MvcUriComponentsBuilder.MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME, + CompositeUriComponentsContributor.class); + + assertNotNull(uriComponentsContributor); } @Test diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/support/DefaultMvcUrlsTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsContributorTests.java similarity index 60% rename from spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/support/DefaultMvcUrlsTests.java rename to spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsContributorTests.java index c5954a71c32..6a953a8eff2 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/support/DefaultMvcUrlsTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsContributorTests.java @@ -14,12 +14,7 @@ * limitations under the License. */ -package org.springframework.web.servlet.mvc.support; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +package org.springframework.web.servlet.mvc.method.annotation; import org.hamcrest.Matchers; import org.joda.time.DateTime; @@ -38,142 +33,149 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; -import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver; -import org.springframework.web.method.support.HandlerMethodArgumentResolver; -import org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver; +import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; import org.springframework.web.util.UriComponents; +import java.util.Arrays; +import java.util.List; + import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; -import static org.springframework.web.servlet.mvc.support.MvcUrlUtils.*; +import static org.junit.Assert.assertThat; +import static org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.mock; /** - * Unit tests for {@link DefaultMvcUrls}. + * Unit tests for {@link org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder}. * * @author Oliver Gierke * @author Dietrich Schulten * @author Rossen Stoyanchev */ -public class DefaultMvcUrlsTests { +public class MvcUriComponentsContributorTests { private MockHttpServletRequest request; - private MvcUrls mvcUrls; + private MvcUriComponentsBuilder builder; @Before public void setUp() { this.request = new MockHttpServletRequest(); - ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request); - RequestContextHolder.setRequestAttributes(requestAttributes); - - List resolvers = new ArrayList<>(); - resolvers.add(new PathVariableMethodArgumentResolver()); - resolvers.add(new RequestParamMethodArgumentResolver(null, false)); - - this.mvcUrls = new DefaultMvcUrls(resolvers, null); + RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(this.request)); } @After - public void teardown() { + public void tearDown() { RequestContextHolder.resetRequestAttributes(); } @Test - public void linkToControllerRoot() { - UriComponents uriComponents = this.mvcUrls.linkToController(PersonControllerImpl.class).build(); + public void fromController() { + UriComponents uriComponents = this.builder.fromController(PersonControllerImpl.class).build(); assertThat(uriComponents.toUriString(), Matchers.endsWith("/people")); } @Test - public void linkToParameterizedControllerRoot() { - UriComponents uriComponents = this.mvcUrls.linkToController( - PersonsAddressesController.class).buildAndExpand(15); - + public void fromControllerUriTemplate() { + UriComponents uriComponents = this.builder.fromController(PersonsAddressesController.class).buildAndExpand(15); assertThat(uriComponents.toUriString(), endsWith("/people/15/addresses")); } @Test - public void linkToMethodOnParameterizedControllerRoot() { - UriComponents uriComponents = this.mvcUrls.linkToMethodOn( - controller(PersonsAddressesController.class, 15).getAddressesForCountry("DE")); - - assertThat(uriComponents.toUriString(), endsWith("/people/15/addresses/DE")); - } - - @Test - public void linkToSubResource() { + public void fromControllerSubResource() { UriComponents uriComponents = - this.mvcUrls.linkToController(PersonControllerImpl.class).pathSegment("something").build(); + this.builder.fromController(PersonControllerImpl.class).pathSegment("something").build(); assertThat(uriComponents.toUriString(), endsWith("/people/something")); } @Test - public void linkToControllerWithMultipleMappings() { - UriComponents uriComponents = this.mvcUrls.linkToController(InvalidController.class).build(); + public void fromControllerTwoTypeLevelMappings() { + UriComponents uriComponents = this.builder.fromController(InvalidController.class).build(); assertThat(uriComponents.toUriString(), is("http://localhost/persons")); } @Test - public void linkToControllerNotMapped() { - UriComponents uriComponents = this.mvcUrls.linkToController(UnmappedController.class).build(); + public void fromControllerNotMapped() { + UriComponents uriComponents = this.builder.fromController(UnmappedController.class).build(); assertThat(uriComponents.toUriString(), is("http://localhost/")); } @Test - public void linkToMethodRefWithPathVar() throws Exception { - Method method = ControllerWithMethods.class.getDeclaredMethod("methodWithPathVariable", String.class); - UriComponents uriComponents = this.mvcUrls.linkToMethod(method, new Object[] { "1" }); + public void fromMethodPathVariable() throws Exception { + UriComponents uriComponents = this.builder.fromMethodName( + ControllerWithMethods.class, "methodWithPathVariable", new Object[]{"1"}).build(); assertThat(uriComponents.toUriString(), is("http://localhost/something/1/foo")); } @Test - public void linkToMethodRefWithTwoPathVars() throws Exception { + public void fromMethodTypeLevelPathVariable() throws Exception { + this.request.setContextPath("/myapp"); + UriComponents uriComponents = this.builder.fromMethodName( + PersonsAddressesController.class, "getAddressesForCountry", "DE").buildAndExpand("1"); + + assertThat(uriComponents.toUriString(), is("http://localhost/myapp/people/1/addresses/DE")); + } + + @Test + public void fromMethodTwoPathVariables() throws Exception { DateTime now = DateTime.now(); - Method method = ControllerWithMethods.class.getDeclaredMethod( - "methodWithTwoPathVariables", Integer.class, DateTime.class); - UriComponents uriComponents = this.mvcUrls.linkToMethod(method, new Object[] { 1, now }); + UriComponents uriComponents = this.builder.fromMethodName( + ControllerWithMethods.class, "methodWithTwoPathVariables", 1, now).build(); assertThat(uriComponents.getPath(), is("/something/1/foo/" + ISODateTimeFormat.date().print(now))); } @Test - public void linkToMethodRefWithPathVarAndRequestParam() throws Exception { - Method method = ControllerWithMethods.class.getDeclaredMethod("methodForNextPage", String.class, Integer.class, Integer.class); - UriComponents uriComponents = this.mvcUrls.linkToMethod(method, new Object[] {"1", 10, 5}); + public void fromMethodWithPathVarAndRequestParam() throws Exception { + UriComponents uriComponents = this.builder.fromMethodName( + ControllerWithMethods.class, "methodForNextPage", "1", 10, 5).build(); assertThat(uriComponents.getPath(), is("/something/1/foo")); - MultiValueMap queryParams = uriComponents.getQueryParams(); assertThat(queryParams.get("limit"), contains("5")); assertThat(queryParams.get("offset"), contains("10")); } @Test - public void linkToMethod() { - UriComponents uriComponents = this.mvcUrls.linkToMethodOn( - controller(ControllerWithMethods.class).myMethod(null)); + public void fromMethodNotMapped() throws Exception { + UriComponents uriComponents = this.builder.fromMethodName(UnmappedController.class, "unmappedMethod").build(); + + assertThat(uriComponents.toUriString(), is("http://localhost/")); + } + + @Test + public void fromLastCall() { + UriComponents uriComponents = this.builder.fromLastCall( + mock(ControllerWithMethods.class).myMethod(null)).build(); assertThat(uriComponents.toUriString(), startsWith("http://localhost")); assertThat(uriComponents.toUriString(), endsWith("/something/else")); } @Test - public void linkToMethodWithPathVar() { - UriComponents uriComponents = this.mvcUrls.linkToMethodOn( - controller(ControllerWithMethods.class).methodWithPathVariable("1")); + public void fromLastCallWithTypeLevelUriVars() { + UriComponents uriComponents = this.builder.fromLastCall( + mock(PersonsAddressesController.class).getAddressesForCountry("DE")).buildAndExpand(15); + + assertThat(uriComponents.toUriString(), endsWith("/people/15/addresses/DE")); + } + + + @Test + public void fromLastCallWithPathVar() { + UriComponents uriComponents = this.builder.fromLastCall( + mock(ControllerWithMethods.class).methodWithPathVariable("1")).build(); assertThat(uriComponents.toUriString(), startsWith("http://localhost")); assertThat(uriComponents.toUriString(), endsWith("/something/1/foo")); } @Test - public void linkToMethodWithPathVarAndRequestParams() { - UriComponents uriComponents = this.mvcUrls.linkToMethodOn( - controller(ControllerWithMethods.class).methodForNextPage("1", 10, 5)); + public void fromLastCallWithPathVarAndRequestParams() { + UriComponents uriComponents = this.builder.fromLastCall( + mock(ControllerWithMethods.class).methodForNextPage("1", 10, 5)).build(); assertThat(uriComponents.getPath(), is("/something/1/foo")); @@ -183,10 +185,10 @@ public class DefaultMvcUrlsTests { } @Test - public void linkToMethodWithPathVarAndMultiValueRequestParams() { - UriComponents uriComponents = this.mvcUrls.linkToMethodOn( - controller(ControllerWithMethods.class).methodWithMultiValueRequestParams( - "1", Arrays.asList(3, 7), 5)); + public void fromLastCallWithPathVarAndMultiValueRequestParams() { + UriComponents uriComponents = this.builder.fromLastCall( + mock(ControllerWithMethods.class).methodWithMultiValueRequestParams( + "1", Arrays.asList(3, 7), 5)).build(); assertThat(uriComponents.getPath(), is("/something/1/foo")); @@ -198,7 +200,7 @@ public class DefaultMvcUrlsTests { @Test public void usesForwardedHostAsHostIfHeaderIsSet() { this.request.addHeader("X-Forwarded-Host", "somethingDifferent"); - UriComponents uriComponents = this.mvcUrls.linkToController(PersonControllerImpl.class).build(); + UriComponents uriComponents = this.builder.fromController(PersonControllerImpl.class).build(); assertThat(uriComponents.toUriString(), startsWith("http://somethingDifferent")); } @@ -206,7 +208,7 @@ public class DefaultMvcUrlsTests { @Test public void usesForwardedHostAndPortFromHeader() { request.addHeader("X-Forwarded-Host", "foobar:8088"); - UriComponents uriComponents = this.mvcUrls.linkToController(PersonControllerImpl.class).build(); + UriComponents uriComponents = this.builder.fromController(PersonControllerImpl.class).build(); assertThat(uriComponents.toUriString(), startsWith("http://foobar:8088")); } @@ -214,7 +216,7 @@ public class DefaultMvcUrlsTests { @Test public void usesFirstHostOfXForwardedHost() { request.addHeader("X-Forwarded-Host", "barfoo:8888, localhost:8088"); - UriComponents uriComponents = this.mvcUrls.linkToController(PersonControllerImpl.class).build(); + UriComponents uriComponents = this.builder.fromController(PersonControllerImpl.class).build(); assertThat(uriComponents.toUriString(), startsWith("http://barfoo:8888")); } @@ -254,6 +256,9 @@ public class DefaultMvcUrlsTests { class UnmappedController { + @RequestMapping + public void unmappedMethod() { + } } @RequestMapping("/something") @@ -287,4 +292,6 @@ public class DefaultMvcUrlsTests { return null; } } + + } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/support/MvcUrlUtilsTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/support/MvcUrlUtilsTests.java deleted file mode 100644 index 60e3e4ccd8b..00000000000 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/support/MvcUrlUtilsTests.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2012-2013 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.support; - -import java.lang.reflect.Method; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.mock.web.test.MockHttpServletRequest; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; -import org.springframework.web.servlet.mvc.support.MvcUrlUtils.ControllerMethodValues; - -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; - -/** - * Test fixture for {@link MvcUrlUtils}. - * - * @author Oliver Gierke - * @author Rossen Stoyanchev - */ -public class MvcUrlUtilsTests { - - private MockHttpServletRequest request; - - - @Before - public void setUp() { - this.request = new MockHttpServletRequest(); - ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request); - RequestContextHolder.setRequestAttributes(requestAttributes); - } - - @After - public void teardown() { - RequestContextHolder.resetRequestAttributes(); - } - - @Test - public void methodOn() { - HttpEntity result = MvcUrlUtils.controller(SampleController.class).someMethod(1L); - - assertTrue(result instanceof ControllerMethodValues); - assertEquals("someMethod", ((ControllerMethodValues) result).getControllerMethod().getName()); - } - - @Test - public void typeLevelMapping() { - assertThat(MvcUrlUtils.getTypeLevelMapping(MyController.class), is("/type")); - } - - @Test - public void typeLevelMappingNone() { - assertThat(MvcUrlUtils.getTypeLevelMapping(ControllerWithoutTypeLevelMapping.class), is("/")); - } - - @Test - public void methodLevelMapping() throws Exception { - Method method = MyController.class.getMethod("method"); - assertThat(MvcUrlUtils.getMethodMapping(method), is("/type/method")); - } - - @Test - public void methodLevelMappingWithoutTypeLevelMapping() throws Exception { - Method method = ControllerWithoutTypeLevelMapping.class.getMethod("method"); - assertThat(MvcUrlUtils.getMethodMapping(method), is("/method")); - } - - @Test - public void methodMappingWithControllerMappingOnly() throws Exception { - Method method = MyController.class.getMethod("noMethodMapping"); - assertThat(MvcUrlUtils.getMethodMapping(method), is("/type")); - } - - - @RequestMapping("/sample") - static class SampleController { - - @RequestMapping("/{id}/foo") - HttpEntity someMethod(@PathVariable("id") Long id) { - return new ResponseEntity(HttpStatus.OK); - } - } - - @RequestMapping("/type") - interface MyController { - - @RequestMapping("/method") - void method(); - - @RequestMapping - void noMethodMapping(); - } - - interface ControllerWithoutTypeLevelMapping { - - @RequestMapping("/method") - void method(); - } - -}