Replace MvcUrls with MvcUriComponentsBuilder
Issue: SPR-8826
This commit is contained in:
parent
e7f89f87c1
commit
cf5db8362b
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
* @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) { … }
|
||||
* }
|
||||
* </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();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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">
|
||||
* @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);
|
||||
* </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);
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue