Replace MvcUrls with MvcUriComponentsBuilder

Issue: SPR-8826
This commit is contained in:
Rossen Stoyanchev 2013-10-25 22:25:06 -04:00
parent e7f89f87c1
commit cf5db8362b
13 changed files with 722 additions and 819 deletions

View File

@ -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<UriComponentsContributor> contributors = new ArrayList<UriComponentsContributor>();
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.
* <p>
* 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<String, Object> 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<String, Object> uriVariables) {
this.contributeMethodArgument(parameter, value, builder, uriVariables, this.conversionService);
}
}

View File

@ -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<String, String> 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

View File

@ -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<CompositeUriComponentsContributor> {
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;
}
}
}

View File

@ -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}.
* <p>
* 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<MvcUrls> {
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;
}
}

View File

@ -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());
}
/**

View File

@ -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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* For example given this controller:
*
* <pre class="code">
* &#064;RequestMapping("/people/{id}/addresses")
* class AddressController {
*
* &#064;RequestMapping("/{country}")
* public HttpEntity<Void> getAddressesForCountry(&#064;PathVariable String country) { }
*
* &#064;RequestMapping(value="/", method=RequestMethod.POST)
* public void addAddress(Address address) { }
* }
* </pre>
*
* A "mock" controller can be used as follows:
*
* <pre>
*
* // 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);
*
* </pre>
*
* The above supports {@codce @PathVariable} and {@code @RequestParam} method parameters.
* Any other arguments can be provided as {@literal null} and will be ignored.
* <p>
* 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<String, Object> uriVars = new HashMap<String, Object>();
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)}.
* <p>
* 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:
*
* <pre>
* 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();
* </pre>
*
* If a controller method returns void, use the following style:
*
* <pre>
* CustomerController controller = MvcUriComponentsBuilder.mock(CustomController.class);
*
* controller.handleFoo(1);
* MvcUriComponentsBuilder.fromLastCall(controller).build();
*
* controller.handleFoo(2)
* MvcUriComponentsBuilder.fromLastCall(controller).build();
* </pre>
*
* @param controllerType the type of controller to mock
*/
public static <T> T mock(Class<T> controllerType) {
Assert.notNull(controllerType, "'controllerType' must not be null");
return initProxy(controllerType, new ControllerMethodInvocationInterceptor());
}
@SuppressWarnings("unchecked")
private static <T> 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();
}
}

View File

@ -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<UriComponentsContributor> contributors = new ArrayList<UriComponentsContributor>();
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.
*
* <p>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<String, Object> uriVars = new HashMap<String, Object>();
return applyContributers(builder, method, argumentValues, uriVars);
}
private UriComponents applyContributers(UriComponentsBuilder builder, Method method,
Object[] argumentValues, Map<String, Object> 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<String, Object> uriVars = new HashMap<String, Object>();
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<String, Object> uriVariables) {
Object[] values = info.getTypeLevelUriVariables();
if (!ObjectUtils.isEmpty(values)) {
String mapping = MvcUrlUtils.getTypeLevelMapping(info.getControllerMethod().getDeclaringClass());
List<String> 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]);
}
}
}
}

View File

@ -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<String> 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> T controller(Class<T> controllerType, Object... typeLevelUriVariables) {
Assert.notNull(controllerType, "'type' must not be null");
return initProxy(controllerType, new ControllerMethodInvocationInterceptor(typeLevelUriVariables));
}
@SuppressWarnings("unchecked")
private static <T> 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.
* <p>
* 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();
}
}

View File

@ -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.
*
* <p>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.
*
* <p>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.
*
* <p>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:
*
* <pre class="code">
* &#064;RequestMapping("/people/{id}/addresses")
* class AddressController {
*
* &#064;RequestMapping("/{country}")
* public HttpEntity&lt;Void&gt; getAddressesForCountry(&#064;PathVariable String country) { ... }
*
* &#064;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);
* </pre>
*
* 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);
}

View File

@ -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)

View File

@ -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

View File

@ -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<HandlerMethodArgumentResolver> 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<String, String> 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;
}
}
}

View File

@ -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<Void> 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<Void> someMethod(@PathVariable("id") Long id) {
return new ResponseEntity<Void>(HttpStatus.OK);
}
}
@RequestMapping("/type")
interface MyController {
@RequestMapping("/method")
void method();
@RequestMapping
void noMethodMapping();
}
interface ControllerWithoutTypeLevelMapping {
@RequestMapping("/method")
void method();
}
}