Consistent java.util.Optional resolution, lenient handling of optional multipart files, correct Servlet 3.0 Part list/array selection
Issue: SPR-13418 Issue: SPR-13849 Issue: SPR-13850 Issue: SPR-13893
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2016 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.
|
||||
@@ -48,12 +48,13 @@ public class MatrixVariableMethodArgumentResolver extends AbstractNamedValueMeth
|
||||
super(null);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
if (!parameter.hasParameterAnnotation(MatrixVariable.class)) {
|
||||
return false;
|
||||
}
|
||||
if (Map.class.isAssignableFrom(parameter.getParameterType())) {
|
||||
if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
|
||||
String variableName = parameter.getParameterAnnotation(MatrixVariable.class).name();
|
||||
return StringUtils.hasText(variableName);
|
||||
}
|
||||
@@ -67,13 +68,10 @@ public class MatrixVariableMethodArgumentResolver extends AbstractNamedValueMeth
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, MultiValueMap<String, String>> pathParameters =
|
||||
(Map<String, MultiValueMap<String, String>>) request.getAttribute(
|
||||
HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
|
||||
|
||||
Map<String, MultiValueMap<String, String>> pathParameters = (Map<String, MultiValueMap<String, String>>)
|
||||
request.getAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
|
||||
if (CollectionUtils.isEmpty(pathParameters)) {
|
||||
return null;
|
||||
}
|
||||
@@ -92,10 +90,10 @@ public class MatrixVariableMethodArgumentResolver extends AbstractNamedValueMeth
|
||||
for (MultiValueMap<String, String> params : pathParameters.values()) {
|
||||
if (params.containsKey(name)) {
|
||||
if (found) {
|
||||
String paramType = parameter.getParameterType().getName();
|
||||
String paramType = parameter.getNestedParameterType().getName();
|
||||
throw new ServletRequestBindingException(
|
||||
"Found more than one match for URI path parameter '" + name +
|
||||
"' for parameter type [" + paramType + "]. Use pathVar attribute to disambiguate.");
|
||||
"' for parameter type [" + paramType + "]. Use 'pathVar' attribute to disambiguate.");
|
||||
}
|
||||
paramValues.addAll(params.get(name));
|
||||
found = true;
|
||||
@@ -117,7 +115,7 @@ public class MatrixVariableMethodArgumentResolver extends AbstractNamedValueMeth
|
||||
@Override
|
||||
protected void handleMissingValue(String name, MethodParameter parameter) throws ServletRequestBindingException {
|
||||
throw new ServletRequestBindingException("Missing matrix variable '" + name +
|
||||
"' for method parameter of type " + parameter.getParameterType().getSimpleName());
|
||||
"' for method parameter of type " + parameter.getNestedParameterType().getSimpleName());
|
||||
}
|
||||
|
||||
|
||||
@@ -127,4 +125,5 @@ public class MatrixVariableMethodArgumentResolver extends AbstractNamedValueMeth
|
||||
super(annotation.name(), annotation.required(), annotation.defaultValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethod
|
||||
if (!parameter.hasParameterAnnotation(PathVariable.class)) {
|
||||
return false;
|
||||
}
|
||||
if (Map.class.isAssignableFrom(parameter.getParameterType())) {
|
||||
if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
|
||||
String paramName = parameter.getParameterAnnotation(PathVariable.class).value();
|
||||
return StringUtils.hasText(paramName);
|
||||
}
|
||||
@@ -95,10 +95,9 @@ public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethod
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
|
||||
Map<String, String> uriTemplateVars =
|
||||
(Map<String, String>) request.getAttribute(
|
||||
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
|
||||
return (uriTemplateVars != null) ? uriTemplateVars.get(name) : null;
|
||||
Map<String, String> uriTemplateVars = (Map<String, String>) request.getAttribute(
|
||||
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
|
||||
return (uriTemplateVars != null ? uriTemplateVars.get(name) : null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -127,7 +126,7 @@ public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethod
|
||||
public void contributeMethodArgument(MethodParameter parameter, Object value,
|
||||
UriComponentsBuilder builder, Map<String, Object> uriVariables, ConversionService conversionService) {
|
||||
|
||||
if (Map.class.isAssignableFrom(parameter.getParameterType())) {
|
||||
if (Map.class.isAssignableFrom(parameter.getNestedParameterType())) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2016 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.
|
||||
@@ -16,20 +16,15 @@
|
||||
|
||||
package org.springframework.web.servlet.mvc.method.annotation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.Part;
|
||||
|
||||
import org.springframework.core.GenericCollectionTypeResolver;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.lang.UsesJava8;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
@@ -41,12 +36,10 @@ import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
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.multipart.support.MissingServletRequestPartException;
|
||||
import org.springframework.web.multipart.support.MultipartResolutionDelegate;
|
||||
import org.springframework.web.multipart.support.RequestPartServletServerHttpRequest;
|
||||
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
|
||||
/**
|
||||
* Resolves the following method arguments:
|
||||
@@ -65,12 +58,13 @@ import org.springframework.web.util.WebUtils;
|
||||
* it is derived from the name of the method argument.
|
||||
*
|
||||
* <p>Automatic validation may be applied if the argument is annotated with
|
||||
* {@code @javax.validation.Valid}. In case of validation failure, a
|
||||
* {@link MethodArgumentNotValidException} is raised and a 400 response status
|
||||
* code returned if {@link DefaultHandlerExceptionResolver} is configured.
|
||||
* {@code @javax.validation.Valid}. In case of validation failure, a {@link MethodArgumentNotValidException}
|
||||
* is raised and a 400 response status code returned if
|
||||
* {@link org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver} is configured.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Brian Clozel
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.1
|
||||
*/
|
||||
public class RequestPartMethodArgumentResolver extends AbstractMessageConverterMethodArgumentResolver {
|
||||
@@ -107,18 +101,10 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
if (parameter.hasParameterAnnotation(RequestParam.class)){
|
||||
return false;
|
||||
}
|
||||
else if (MultipartFile.class == parameter.getParameterType()) {
|
||||
return true;
|
||||
}
|
||||
else if ("javax.servlet.http.Part".equals(parameter.getParameterType().getName())) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
if (parameter.hasParameterAnnotation(RequestParam.class)) {
|
||||
return false;
|
||||
}
|
||||
return MultipartResolutionDelegate.isMultipartArgument(parameter.nestedIfOptional());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,87 +114,58 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
|
||||
NativeWebRequest request, WebDataBinderFactory binderFactory) throws Exception {
|
||||
|
||||
HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
|
||||
assertIsMultipartRequest(servletRequest);
|
||||
RequestPart requestPart = parameter.getParameterAnnotation(RequestPart.class);
|
||||
boolean isRequired = ((requestPart == null || requestPart.required()) && !parameter.isOptional());
|
||||
|
||||
MultipartHttpServletRequest multipartRequest =
|
||||
WebUtils.getNativeRequest(servletRequest, MultipartHttpServletRequest.class);
|
||||
String name = getPartName(parameter, requestPart);
|
||||
parameter = parameter.nestedIfOptional();
|
||||
Object arg = null;
|
||||
|
||||
Class<?> paramType = parameter.getParameterType();
|
||||
boolean optional = paramType.getName().equals("java.util.Optional");
|
||||
if (optional) {
|
||||
parameter.increaseNestingLevel();
|
||||
paramType = parameter.getNestedParameterType();
|
||||
}
|
||||
|
||||
String partName = getPartName(parameter);
|
||||
Object arg;
|
||||
|
||||
if (MultipartFile.class == paramType) {
|
||||
Assert.notNull(multipartRequest, "Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
|
||||
arg = multipartRequest.getFile(partName);
|
||||
}
|
||||
else if (isMultipartFileCollection(parameter)) {
|
||||
Assert.notNull(multipartRequest, "Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
|
||||
arg = multipartRequest.getFiles(partName);
|
||||
}
|
||||
else if (isMultipartFileArray(parameter)) {
|
||||
Assert.notNull(multipartRequest, "Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
|
||||
List<MultipartFile> files = multipartRequest.getFiles(partName);
|
||||
arg = files.toArray(new MultipartFile[files.size()]);
|
||||
}
|
||||
else if ("javax.servlet.http.Part".equals(paramType.getName())) {
|
||||
assertIsMultipartRequest(servletRequest);
|
||||
arg = servletRequest.getPart(partName);
|
||||
}
|
||||
else if (isPartCollection(parameter)) {
|
||||
assertIsMultipartRequest(servletRequest);
|
||||
arg = new ArrayList<Object>(servletRequest.getParts());
|
||||
}
|
||||
else if (isPartArray(parameter)) {
|
||||
assertIsMultipartRequest(servletRequest);
|
||||
arg = RequestPartResolver.resolvePart(servletRequest);
|
||||
Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
|
||||
if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
|
||||
arg = mpArg;
|
||||
}
|
||||
else {
|
||||
try {
|
||||
HttpInputMessage inputMessage = new RequestPartServletServerHttpRequest(servletRequest, partName);
|
||||
HttpInputMessage inputMessage = new RequestPartServletServerHttpRequest(servletRequest, name);
|
||||
arg = readWithMessageConverters(inputMessage, parameter, parameter.getNestedGenericParameterType());
|
||||
WebDataBinder binder = binderFactory.createBinder(request, arg, partName);
|
||||
WebDataBinder binder = binderFactory.createBinder(request, arg, name);
|
||||
if (arg != null) {
|
||||
validateIfApplicable(binder, parameter);
|
||||
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
|
||||
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
|
||||
}
|
||||
}
|
||||
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + partName, binder.getBindingResult());
|
||||
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
|
||||
}
|
||||
catch (MissingServletRequestPartException ex) {
|
||||
// handled below
|
||||
arg = null;
|
||||
if (isRequired) {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
catch (MultipartException ex) {
|
||||
if (isRequired) {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RequestPart requestPart = parameter.getParameterAnnotation(RequestPart.class);
|
||||
boolean isRequired = ((requestPart == null || requestPart.required()) && !optional);
|
||||
|
||||
if (arg == null && isRequired) {
|
||||
throw new MissingServletRequestPartException(partName);
|
||||
if (!MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
|
||||
throw new MultipartException("Current request is not a multipart request");
|
||||
}
|
||||
else {
|
||||
throw new MissingServletRequestPartException(name);
|
||||
}
|
||||
}
|
||||
if (optional) {
|
||||
arg = Optional.ofNullable(arg);
|
||||
if (parameter.isOptional()) {
|
||||
arg = OptionalResolver.resolveValue(arg);
|
||||
}
|
||||
|
||||
return arg;
|
||||
}
|
||||
|
||||
private static void assertIsMultipartRequest(HttpServletRequest request) {
|
||||
String contentType = request.getContentType();
|
||||
if (contentType == null || !contentType.toLowerCase().startsWith("multipart/")) {
|
||||
throw new MultipartException("The current request is not a multipart request");
|
||||
}
|
||||
}
|
||||
|
||||
private String getPartName(MethodParameter methodParam) {
|
||||
RequestPart requestPart = methodParam.getParameterAnnotation(RequestPart.class);
|
||||
private String getPartName(MethodParameter methodParam, RequestPart requestPart) {
|
||||
String partName = (requestPart != null ? requestPart.name() : "");
|
||||
if (partName.length() == 0) {
|
||||
partName = methodParam.getParameterName();
|
||||
@@ -221,46 +178,18 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
|
||||
return partName;
|
||||
}
|
||||
|
||||
private boolean isMultipartFileCollection(MethodParameter methodParam) {
|
||||
Class<?> collectionType = getCollectionParameterType(methodParam);
|
||||
return MultipartFile.class == collectionType;
|
||||
}
|
||||
|
||||
private boolean isMultipartFileArray(MethodParameter methodParam) {
|
||||
Class<?> paramType = methodParam.getNestedParameterType().getComponentType();
|
||||
return MultipartFile.class == paramType;
|
||||
}
|
||||
|
||||
private boolean isPartCollection(MethodParameter methodParam) {
|
||||
Class<?> collectionType = getCollectionParameterType(methodParam);
|
||||
return (collectionType != null && "javax.servlet.http.Part".equals(collectionType.getName()));
|
||||
}
|
||||
|
||||
private boolean isPartArray(MethodParameter methodParam) {
|
||||
Class<?> paramType = methodParam.getNestedParameterType().getComponentType();
|
||||
return (paramType != null && "javax.servlet.http.Part".equals(paramType.getName()));
|
||||
}
|
||||
|
||||
private Class<?> getCollectionParameterType(MethodParameter methodParam) {
|
||||
Class<?> paramType = methodParam.getNestedParameterType();
|
||||
if (Collection.class == paramType || List.class.isAssignableFrom(paramType)){
|
||||
Class<?> valueType = GenericCollectionTypeResolver.getCollectionParameterType(methodParam);
|
||||
if (valueType != null) {
|
||||
return valueType;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inner class to avoid hard-coded dependency on Servlet 3.0 Part type...
|
||||
* Inner class to avoid hard-coded dependency on Java 8 Optional type...
|
||||
*/
|
||||
private static class RequestPartResolver {
|
||||
private static class OptionalResolver {
|
||||
|
||||
public static Object resolvePart(HttpServletRequest servletRequest) throws Exception {
|
||||
Collection<Part> parts = servletRequest.getParts();
|
||||
return parts.toArray(new Part[parts.size()]);
|
||||
public static Object resolveValue(Object value) {
|
||||
if (value == null || (value instanceof Collection && ((Collection) value).isEmpty()) ||
|
||||
(value instanceof Object[] && ((Object[]) value).length == 0)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* Copyright 2002-2016 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.
|
||||
@@ -52,7 +52,7 @@ public class ServletCookieValueMethodArgumentResolver extends AbstractCookieValu
|
||||
protected Object resolveName(String cookieName, MethodParameter parameter, NativeWebRequest webRequest) throws Exception {
|
||||
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
|
||||
Cookie cookieValue = WebUtils.getCookie(servletRequest, cookieName);
|
||||
if (Cookie.class.isAssignableFrom(parameter.getParameterType())) {
|
||||
if (Cookie.class.isAssignableFrom(parameter.getNestedParameterType())) {
|
||||
return cookieValue;
|
||||
}
|
||||
else if (cookieValue != null) {
|
||||
|
||||
@@ -262,6 +262,12 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
|
||||
this.returnType = ResolvableType.forType(super.getGenericParameterType()).getGeneric(0);
|
||||
}
|
||||
|
||||
public ConcurrentResultMethodParameter(ConcurrentResultMethodParameter original) {
|
||||
super(original);
|
||||
this.returnValue = original.returnValue;
|
||||
this.returnType = original.returnType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getParameterType() {
|
||||
if (this.returnValue != null) {
|
||||
@@ -283,6 +289,11 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
|
||||
public Type getGenericParameterType() {
|
||||
return this.returnType.getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConcurrentResultMethodParameter clone() {
|
||||
return new ConcurrentResultMethodParameter(this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user