Integrate suggested support for creating MVC URLs

The key contract is MvcUrls. An instance is automatically created with
the Spring MVC namespace and the MVC Java config but can also be easily
created in any configuration.

Some example tests can be found in DefaultMvcUrlsTests.

Issue: SPR-10665, SPR-8826
This commit is contained in:
Rossen Stoyanchev
2013-10-20 10:01:54 -04:00
parent 4fd27b12fc
commit bafc73f147
33 changed files with 1313 additions and 2088 deletions

View File

@@ -29,6 +29,8 @@ import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.GenericCollectionTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -38,10 +40,12 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.ValueConstants;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.UriComponentsContributor;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.WebUtils;
/**
@@ -69,7 +73,10 @@ import org.springframework.web.util.WebUtils;
* @since 3.1
* @see RequestParamMapMethodArgumentResolver
*/
public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver
implements UriComponentsContributor {
private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
private final boolean useDefaultResolution;
@@ -83,7 +90,8 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod
* request parameter name is derived from the method parameter name.
*/
public RequestParamMethodArgumentResolver(ConfigurableBeanFactory beanFactory,
boolean useDefaultResolution) {
boolean useDefaultResolution) {
super(beanFactory);
this.useDefaultResolution = useDefaultResolution;
}
@@ -218,6 +226,39 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod
throw new MissingServletRequestParameterException(paramName, parameter.getParameterType().getSimpleName());
}
@Override
public void contributeMethodArgument(MethodParameter parameter, Object value,
UriComponentsBuilder builder, Map<String, Object> uriVariables, ConversionService conversionService) {
Class<?> paramType = parameter.getParameterType();
if (Map.class.isAssignableFrom(paramType) || MultipartFile.class.equals(paramType) ||
"javax.servlet.http.Part".equals(paramType.getName())) {
return;
}
RequestParam annot = parameter.getParameterAnnotation(RequestParam.class);
String name = StringUtils.isEmpty(annot.value()) ? parameter.getParameterName() : annot.value();
if (value == null) {
builder.queryParam(name);
}
else if (value instanceof Collection) {
for (Object v : (Collection) value) {
v = formatUriValue(conversionService, TypeDescriptor.nested(parameter, 1), v);
builder.queryParam(name, v);
}
}
else {
builder.queryParam(name, formatUriValue(conversionService, new TypeDescriptor(parameter), value));
}
}
protected String formatUriValue(ConversionService cs, TypeDescriptor sourceType, Object value) {
return (cs != null) ?
(String) cs.convert(value, sourceType, STRING_TYPE_DESCRIPTOR) : null;
}
private class RequestParamNamedValueInfo extends NamedValueInfo {
private RequestParamNamedValueInfo() {
@@ -228,4 +269,5 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod
super(annotation.value(), annotation.required(), annotation.defaultValue());
}
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 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 java.util.Map;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
/**
* Strategy for contributing to the building of a {@link UriComponents} by
* looking at a method parameter and an argument value and deciding what
* part of the target URL should be updated.
*
* @author Oliver Gierke
* @author Rossen Stoyanchev
* @since 4.0
*/
public interface UriComponentsContributor {
/**
* Whether this contributor supports the given method parameter.
*/
boolean supportsParameter(MethodParameter parameter);
/**
* Process the given method argument and either update the
* {@link UriComponentsBuilder} or add to the map with URI variables to use to
* expand the URI after all arguments are processed.
*
* @param parameter the controller method parameter, never {@literal null}.
* @param value the argument value, possibly {@literal null}.
* @param builder the builder to update, never {@literal null}.
* @param uriVariables a map to add URI variables to, never {@literal null}.
* @param conversionService a ConversionService to format values as Strings
*/
void contributeMethodArgument(MethodParameter parameter, Object value, UriComponentsBuilder builder,
Map<String, Object> uriVariables, ConversionService conversionService);
}

View File

@@ -248,7 +248,7 @@ public class UriComponentsBuilder {
throw new IllegalArgumentException("[" + httpUrl + "] is not a valid HTTP URL");
}
}
// build methods
@@ -362,6 +362,43 @@ public class UriComponentsBuilder {
return this;
}
/**
* Initializes all components of this URI builder from the given {@link UriComponents}.
* @param uriComponents the UriComponents instance
* @return this UriComponentsBuilder
*/
public UriComponentsBuilder uriComponents(UriComponents uriComponents) {
Assert.notNull(uriComponents, "'uriComponents' must not be null");
this.scheme = uriComponents.getScheme();
if (uriComponents instanceof OpaqueUriComponents) {
this.ssp = uriComponents.getSchemeSpecificPart();
resetHierarchicalComponents();
}
else {
if (uriComponents.getUserInfo() != null) {
this.userInfo = uriComponents.getUserInfo();
}
if (uriComponents.getHost() != null) {
this.host = uriComponents.getHost();
}
if (uriComponents.getPort() != -1) {
this.port = uriComponents.getPort();
}
if (StringUtils.hasLength(uriComponents.getPath())) {
this.pathBuilder = new CompositePathComponentBuilder(uriComponents.getPath());
}
if (!uriComponents.getQueryParams().isEmpty()) {
this.queryParams.clear();
this.queryParams.putAll(uriComponents.getQueryParams());
}
resetSchemeSpecificPart();
}
if (uriComponents.getFragment() != null) {
this.fragment = uriComponents.getFragment();
}
return this;
}
/**
* Set the URI scheme-specific-part. When invoked, this method overwrites
* {@linkplain #userInfo(String) user-info}, {@linkplain #host(String) host},
@@ -554,36 +591,6 @@ public class UriComponentsBuilder {
return this;
}
public UriComponentsBuilder with(UriComponentsBuilder builder) {
UriComponents components = builder.build().normalize();
if (StringUtils.hasText(components.getScheme())) {
scheme(components.getScheme());
}
if (StringUtils.hasText(components.getHost())) {
host(components.getHost());
}
if (components.getPort() != -1) {
port(components.getPort());
}
if (StringUtils.hasText(components.getPath())) {
path(components.getPath());
}
if (StringUtils.hasText(components.getQuery())) {
query(components.getQuery());
}
if (StringUtils.hasText(components.getFragment())) {
fragment(components.getFragment());
}
return this;
}
private interface PathComponentBuilder {