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.
|
||||
@@ -55,7 +55,6 @@ import org.springframework.web.multipart.MultipartResolver;
|
||||
* @author Arjen Poutsma
|
||||
* @author Sam Brannen
|
||||
* @since 3.1
|
||||
*
|
||||
* @see RequestParam
|
||||
* @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
|
||||
*/
|
||||
|
||||
@@ -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.
|
||||
@@ -280,6 +280,10 @@ public class HandlerMethod {
|
||||
super(HandlerMethod.this.bridgedMethod, index);
|
||||
}
|
||||
|
||||
protected HandlerMethodParameter(HandlerMethodParameter original) {
|
||||
super(original);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getContainingClass() {
|
||||
return HandlerMethod.this.getBeanType();
|
||||
@@ -289,6 +293,11 @@ public class HandlerMethod {
|
||||
public <T extends Annotation> T getMethodAnnotation(Class<T> annotationType) {
|
||||
return HandlerMethod.this.getMethodAnnotation(annotationType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerMethodParameter clone() {
|
||||
return new HandlerMethodParameter(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -304,10 +313,20 @@ public class HandlerMethod {
|
||||
this.returnValue = returnValue;
|
||||
}
|
||||
|
||||
protected ReturnValueMethodParameter(ReturnValueMethodParameter original) {
|
||||
super(original);
|
||||
this.returnValue = original.returnValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getParameterType() {
|
||||
return (this.returnValue != null ? this.returnValue.getClass() : super.getParameterType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReturnValueMethodParameter clone() {
|
||||
return new ReturnValueMethodParameter(this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -63,7 +63,7 @@ public abstract class AbstractCookieValueMethodArgumentResolver extends Abstract
|
||||
@Override
|
||||
protected void handleMissingValue(String name, MethodParameter parameter) throws ServletRequestBindingException {
|
||||
throw new ServletRequestBindingException("Missing cookie '" + name +
|
||||
"' for method parameter of type " + parameter.getParameterType().getSimpleName());
|
||||
"' for method parameter of type " + parameter.getNestedParameterType().getSimpleName());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -26,6 +26,7 @@ import org.springframework.beans.factory.config.BeanExpressionContext;
|
||||
import org.springframework.beans.factory.config.BeanExpressionResolver;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.web.bind.ServletRequestBindingException;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.annotation.ValueConstants;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
@@ -53,6 +54,7 @@ import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.1
|
||||
*/
|
||||
public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
@@ -61,7 +63,7 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle
|
||||
|
||||
private final BeanExpressionContext expressionContext;
|
||||
|
||||
private Map<MethodParameter, NamedValueInfo> namedValueInfoCache = new ConcurrentHashMap<MethodParameter, NamedValueInfo>(256);
|
||||
private final Map<MethodParameter, NamedValueInfo> namedValueInfoCache = new ConcurrentHashMap<MethodParameter, NamedValueInfo>(256);
|
||||
|
||||
|
||||
public AbstractNamedValueMethodArgumentResolver() {
|
||||
@@ -84,18 +86,18 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle
|
||||
public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
|
||||
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
|
||||
|
||||
Class<?> paramType = parameter.getParameterType();
|
||||
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
|
||||
MethodParameter nestedParameter = parameter.nestedIfOptional();
|
||||
|
||||
Object arg = resolveName(namedValueInfo.name, parameter, webRequest);
|
||||
Object arg = resolveName(namedValueInfo.name, nestedParameter, webRequest);
|
||||
if (arg == null) {
|
||||
if (namedValueInfo.defaultValue != null) {
|
||||
arg = resolveDefaultValue(namedValueInfo.defaultValue);
|
||||
}
|
||||
else if (namedValueInfo.required && !parameter.getParameterType().getName().equals("java.util.Optional")) {
|
||||
handleMissingValue(namedValueInfo.name, parameter);
|
||||
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
|
||||
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
|
||||
}
|
||||
arg = handleNullValue(namedValueInfo.name, arg, paramType);
|
||||
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
|
||||
}
|
||||
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
|
||||
arg = resolveDefaultValue(namedValueInfo.defaultValue);
|
||||
@@ -104,7 +106,7 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle
|
||||
if (binderFactory != null) {
|
||||
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
|
||||
try {
|
||||
arg = binder.convertIfNecessary(arg, paramType, parameter);
|
||||
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
|
||||
}
|
||||
catch (ConversionNotSupportedException ex) {
|
||||
throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
|
||||
@@ -151,7 +153,8 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle
|
||||
if (info.name.length() == 0) {
|
||||
name = parameter.getParameterName();
|
||||
if (name == null) {
|
||||
throw new IllegalArgumentException("Name for argument type [" + parameter.getParameterType().getName() +
|
||||
throw new IllegalArgumentException(
|
||||
"Name for argument type [" + parameter.getNestedParameterType().getName() +
|
||||
"] not available, and parameter name information not found in class file either.");
|
||||
}
|
||||
}
|
||||
@@ -160,18 +163,19 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the given parameter type and value name into an argument value.
|
||||
* Resolve the given parameter type and value name into an argument value.
|
||||
* @param name the name of the value being resolved
|
||||
* @param parameter the method parameter to resolve to an argument value
|
||||
* (pre-nested in case of a {@link java.util.Optional} declaration)
|
||||
* @param request the current request
|
||||
* @return the resolved argument. May be {@code null}
|
||||
* @return the resolved argument (may be {@code null})
|
||||
* @throws Exception in case of errors
|
||||
*/
|
||||
protected abstract Object resolveName(String name, MethodParameter parameter, NativeWebRequest request)
|
||||
throws Exception;
|
||||
|
||||
/**
|
||||
* Resolves the given default value into an argument value.
|
||||
* Resolve the given default value into an argument value.
|
||||
*/
|
||||
private Object resolveDefaultValue(String defaultValue) {
|
||||
if (this.configurableBeanFactory == null) {
|
||||
@@ -190,8 +194,23 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle
|
||||
* returned {@code null} and there is no default value. Subclasses typically throw an exception in this case.
|
||||
* @param name the name for the value
|
||||
* @param parameter the method parameter
|
||||
* @param request the current request
|
||||
* @since 4.3
|
||||
*/
|
||||
protected abstract void handleMissingValue(String name, MethodParameter parameter) throws ServletException;
|
||||
protected void handleMissingValue(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
|
||||
handleMissingValue(name, parameter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a named value is required, but {@link #resolveName(String, MethodParameter, NativeWebRequest)}
|
||||
* returned {@code null} and there is no default value. Subclasses typically throw an exception in this case.
|
||||
* @param name the name for the value
|
||||
* @param parameter the method parameter
|
||||
*/
|
||||
protected void handleMissingValue(String name, MethodParameter parameter) throws ServletException {
|
||||
throw new ServletRequestBindingException("Missing argument '" + name +
|
||||
"' for method parameter of type " + parameter.getNestedParameterType().getSimpleName());
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code null} results in a {@code false} value for {@code boolean}s or an exception for other primitives.
|
||||
@@ -202,7 +221,7 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
else if (paramType.isPrimitive()) {
|
||||
throw new IllegalStateException("Optional " + paramType + " parameter '" + name +
|
||||
throw new IllegalStateException("Optional " + paramType.getSimpleName() + " parameter '" + name +
|
||||
"' is present but cannot be translated into a null value due to being declared as a " +
|
||||
"primitive type. Consider declaring it as object wrapper for the corresponding primitive type.");
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ public class RequestHeaderMethodArgumentResolver extends AbstractNamedValueMetho
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return (parameter.hasParameterAnnotation(RequestHeader.class) &&
|
||||
!Map.class.isAssignableFrom(parameter.getParameterType()));
|
||||
!Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -79,7 +79,7 @@ public class RequestHeaderMethodArgumentResolver extends AbstractNamedValueMetho
|
||||
@Override
|
||||
protected void handleMissingValue(String name, MethodParameter parameter) throws ServletRequestBindingException {
|
||||
throw new ServletRequestBindingException("Missing request header '" + name +
|
||||
"' for method parameter of type " + parameter.getParameterType().getSimpleName());
|
||||
"' for method parameter of type " + parameter.getNestedParameterType().getSimpleName());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -17,22 +17,17 @@
|
||||
package org.springframework.web.method.annotation;
|
||||
|
||||
import java.beans.PropertyEditor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.Part;
|
||||
|
||||
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;
|
||||
import org.springframework.web.bind.MissingServletRequestParameterException;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
@@ -45,6 +40,8 @@ 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.util.UriComponentsBuilder;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
|
||||
@@ -124,9 +121,8 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod
|
||||
*/
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
Class<?> paramType = parameter.getParameterType();
|
||||
if (parameter.hasParameterAnnotation(RequestParam.class)) {
|
||||
if (Map.class.isAssignableFrom(paramType)) {
|
||||
if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
|
||||
String paramName = parameter.getParameterAnnotation(RequestParam.class).name();
|
||||
return StringUtils.hasText(paramName);
|
||||
}
|
||||
@@ -138,11 +134,12 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod
|
||||
if (parameter.hasParameterAnnotation(RequestPart.class)) {
|
||||
return false;
|
||||
}
|
||||
else if (MultipartFile.class == paramType || "javax.servlet.http.Part".equals(paramType.getName())) {
|
||||
parameter = parameter.nestedIfOptional();
|
||||
if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
|
||||
return true;
|
||||
}
|
||||
else if (this.useDefaultResolution) {
|
||||
return BeanUtils.isSimpleProperty(paramType);
|
||||
return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
@@ -157,112 +154,61 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest webRequest) throws Exception {
|
||||
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
|
||||
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
|
||||
HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
|
||||
MultipartHttpServletRequest multipartRequest =
|
||||
WebUtils.getNativeRequest(servletRequest, MultipartHttpServletRequest.class);
|
||||
Object arg;
|
||||
|
||||
if (MultipartFile.class == parameter.getParameterType()) {
|
||||
assertIsMultipartRequest(servletRequest);
|
||||
Assert.notNull(multipartRequest, "Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
|
||||
arg = multipartRequest.getFile(name);
|
||||
}
|
||||
else if (isMultipartFileCollection(parameter)) {
|
||||
assertIsMultipartRequest(servletRequest);
|
||||
Assert.notNull(multipartRequest, "Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
|
||||
arg = multipartRequest.getFiles(name);
|
||||
}
|
||||
else if (isMultipartFileArray(parameter)) {
|
||||
assertIsMultipartRequest(servletRequest);
|
||||
Assert.notNull(multipartRequest, "Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
|
||||
List<MultipartFile> multipartFiles = multipartRequest.getFiles(name);
|
||||
arg = multipartFiles.toArray(new MultipartFile[multipartFiles.size()]);
|
||||
}
|
||||
else if ("javax.servlet.http.Part".equals(parameter.getParameterType().getName())) {
|
||||
assertIsMultipartRequest(servletRequest);
|
||||
arg = servletRequest.getPart(name);
|
||||
}
|
||||
else if (isPartCollection(parameter)) {
|
||||
assertIsMultipartRequest(servletRequest);
|
||||
arg = new ArrayList<Object>(servletRequest.getParts());
|
||||
}
|
||||
else if (isPartArray(parameter)) {
|
||||
assertIsMultipartRequest(servletRequest);
|
||||
arg = RequestPartResolver.resolvePart(servletRequest);
|
||||
}
|
||||
else {
|
||||
arg = null;
|
||||
if (multipartRequest != null) {
|
||||
List<MultipartFile> files = multipartRequest.getFiles(name);
|
||||
if (!files.isEmpty()) {
|
||||
arg = (files.size() == 1 ? files.get(0) : files);
|
||||
}
|
||||
}
|
||||
if (arg == null) {
|
||||
String[] paramValues = webRequest.getParameterValues(name);
|
||||
if (paramValues != null) {
|
||||
arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
|
||||
}
|
||||
}
|
||||
Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
|
||||
if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
|
||||
return mpArg;
|
||||
}
|
||||
|
||||
Object arg = null;
|
||||
if (multipartRequest != null) {
|
||||
List<MultipartFile> files = multipartRequest.getFiles(name);
|
||||
if (!files.isEmpty()) {
|
||||
arg = (files.size() == 1 ? files.get(0) : files);
|
||||
}
|
||||
}
|
||||
if (arg == null) {
|
||||
String[] paramValues = request.getParameterValues(name);
|
||||
if (paramValues != null) {
|
||||
arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
|
||||
}
|
||||
}
|
||||
return arg;
|
||||
}
|
||||
|
||||
private 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 boolean isMultipartFileCollection(MethodParameter parameter) {
|
||||
return (MultipartFile.class == getCollectionParameterType(parameter));
|
||||
}
|
||||
|
||||
private boolean isMultipartFileArray(MethodParameter parameter) {
|
||||
return (MultipartFile.class == parameter.getParameterType().getComponentType());
|
||||
}
|
||||
|
||||
private boolean isPartCollection(MethodParameter parameter) {
|
||||
Class<?> collectionType = getCollectionParameterType(parameter);
|
||||
return (collectionType != null && "javax.servlet.http.Part".equals(collectionType.getName()));
|
||||
}
|
||||
|
||||
private boolean isPartArray(MethodParameter parameter) {
|
||||
Class<?> paramType = parameter.getParameterType().getComponentType();
|
||||
return (paramType != null && "javax.servlet.http.Part".equals(paramType.getName()));
|
||||
}
|
||||
|
||||
private Class<?> getCollectionParameterType(MethodParameter parameter) {
|
||||
Class<?> paramType = parameter.getParameterType();
|
||||
if (Collection.class == paramType || List.class.isAssignableFrom(paramType)){
|
||||
Class<?> valueType = GenericCollectionTypeResolver.getCollectionParameterType(parameter);
|
||||
if (valueType != null) {
|
||||
return valueType;
|
||||
@Override
|
||||
protected void handleMissingValue(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
|
||||
HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
|
||||
if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
|
||||
if (!MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
|
||||
throw new MultipartException("Current request is not a multipart request");
|
||||
}
|
||||
else {
|
||||
throw new MissingServletRequestPartException(name);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleMissingValue(String name, MethodParameter parameter) throws ServletException {
|
||||
throw new MissingServletRequestParameterException(name, parameter.getParameterType().getSimpleName());
|
||||
else {
|
||||
throw new MissingServletRequestParameterException(name, parameter.getNestedParameterType().getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contributeMethodArgument(MethodParameter parameter, Object value,
|
||||
UriComponentsBuilder builder, Map<String, Object> uriVariables, ConversionService conversionService) {
|
||||
|
||||
Class<?> paramType = parameter.getParameterType();
|
||||
Class<?> paramType = parameter.getNestedParameterType();
|
||||
if (Map.class.isAssignableFrom(paramType) || MultipartFile.class == paramType ||
|
||||
"javax.servlet.http.Part".equals(paramType.getName())) {
|
||||
return;
|
||||
}
|
||||
|
||||
RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
|
||||
String name = (requestParam == null || StringUtils.isEmpty(requestParam.name()) ? parameter.getParameterName() : requestParam.name());
|
||||
String name = (requestParam == null || StringUtils.isEmpty(requestParam.name()) ?
|
||||
parameter.getParameterName() : requestParam.name());
|
||||
|
||||
if (value == null) {
|
||||
builder.queryParam(name);
|
||||
@@ -305,12 +251,4 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class RequestPartResolver {
|
||||
|
||||
public static Object resolvePart(HttpServletRequest servletRequest) throws Exception {
|
||||
return servletRequest.getParts().toArray(new Part[servletRequest.getParts().size()]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2011 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.
|
||||
@@ -24,27 +24,28 @@ import org.springframework.web.multipart.MultipartResolver;
|
||||
* Raised when the part of a "multipart/form-data" request identified by its
|
||||
* name cannot be found.
|
||||
*
|
||||
* <p>This may be because the request is not a multipart/form-data
|
||||
*
|
||||
* either because the part is not present in the request, or
|
||||
* because the web application is not configured correctly for processing
|
||||
* multipart requests -- e.g. no {@link MultipartResolver}.
|
||||
* <p>This may be because the request is not a multipart/form-data request,
|
||||
* because the part is not present in the request, or because the web
|
||||
* application is not configured correctly for processing multipart requests,
|
||||
* e.g. no {@link MultipartResolver}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.1
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class MissingServletRequestPartException extends ServletException {
|
||||
|
||||
private static final long serialVersionUID = -1255077391966870705L;
|
||||
|
||||
private final String partName;
|
||||
|
||||
|
||||
public MissingServletRequestPartException(String partName) {
|
||||
super("Required request part '" + partName + "' is not present.");
|
||||
super("Required request part '" + partName + "' is not present");
|
||||
this.partName = partName;
|
||||
}
|
||||
|
||||
|
||||
public String getRequestPartName() {
|
||||
return this.partName;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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.multipart.support;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.Part;
|
||||
|
||||
import org.springframework.core.GenericCollectionTypeResolver;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.web.multipart.MultipartException;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.multipart.MultipartHttpServletRequest;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
|
||||
/**
|
||||
* A common delegate for {@code HandlerMethodArgumentResolver} implementations
|
||||
* which need to resolve {@link MultipartFile} and {@link Part} arguments.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 4.3
|
||||
*/
|
||||
public abstract class MultipartResolutionDelegate {
|
||||
|
||||
public static final Object UNRESOLVABLE = new Object();
|
||||
|
||||
|
||||
private static Class<?> servletPartClass = null;
|
||||
|
||||
static {
|
||||
try {
|
||||
servletPartClass = ClassUtils.forName(
|
||||
"javax.servlet.http.Part", MultipartResolutionDelegate.class.getClassLoader());
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
// Servlet 3.0 Part type not available - Part references simply not supported then.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean isMultipartRequest(HttpServletRequest request) {
|
||||
return (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null ||
|
||||
isMultipartContent(request));
|
||||
}
|
||||
|
||||
private static boolean isMultipartContent(HttpServletRequest request) {
|
||||
String contentType = request.getContentType();
|
||||
return (contentType != null && contentType.toLowerCase().startsWith("multipart/"));
|
||||
}
|
||||
|
||||
static MultipartHttpServletRequest asMultipartHttpServletRequest(HttpServletRequest request) {
|
||||
MultipartHttpServletRequest unwrapped = WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);
|
||||
if (unwrapped != null) {
|
||||
return unwrapped;
|
||||
}
|
||||
return adaptToMultipartHttpServletRequest(request);
|
||||
}
|
||||
|
||||
private static MultipartHttpServletRequest adaptToMultipartHttpServletRequest(HttpServletRequest request) {
|
||||
if (servletPartClass != null) {
|
||||
// Servlet 3.0 available ..
|
||||
return new StandardMultipartHttpServletRequest(request);
|
||||
}
|
||||
throw new MultipartException("Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
|
||||
}
|
||||
|
||||
|
||||
public static boolean isMultipartArgument(MethodParameter parameter) {
|
||||
Class<?> paramType = parameter.getNestedParameterType();
|
||||
return (MultipartFile.class == paramType || isMultipartFileCollection(parameter) ||
|
||||
isMultipartFileArray(parameter) || servletPartClass == paramType ||
|
||||
isPartCollection(parameter) || isPartArray(parameter));
|
||||
}
|
||||
|
||||
public static Object resolveMultipartArgument(String name, MethodParameter parameter, HttpServletRequest request)
|
||||
throws Exception {
|
||||
|
||||
MultipartHttpServletRequest multipartRequest =
|
||||
WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);
|
||||
boolean isMultipart = (multipartRequest != null || isMultipartContent(request));
|
||||
|
||||
if (MultipartFile.class == parameter.getNestedParameterType()) {
|
||||
if (multipartRequest == null && isMultipart) {
|
||||
multipartRequest = adaptToMultipartHttpServletRequest(request);
|
||||
}
|
||||
return (multipartRequest != null ? multipartRequest.getFile(name) : null);
|
||||
}
|
||||
else if (isMultipartFileCollection(parameter)) {
|
||||
if (multipartRequest == null && isMultipart) {
|
||||
multipartRequest = adaptToMultipartHttpServletRequest(request);
|
||||
}
|
||||
return (multipartRequest != null ? multipartRequest.getFiles(name) : null);
|
||||
}
|
||||
else if (isMultipartFileArray(parameter)) {
|
||||
if (multipartRequest == null && isMultipart) {
|
||||
multipartRequest = adaptToMultipartHttpServletRequest(request);
|
||||
}
|
||||
if (multipartRequest != null) {
|
||||
List<MultipartFile> multipartFiles = multipartRequest.getFiles(name);
|
||||
return multipartFiles.toArray(new MultipartFile[multipartFiles.size()]);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else if (parameter.getNestedParameterType() == servletPartClass) {
|
||||
return (isMultipart ? RequestPartResolver.resolvePart(request, name) : null);
|
||||
}
|
||||
else if (isPartCollection(parameter)) {
|
||||
return (isMultipart ? RequestPartResolver.resolvePartList(request, name) : null);
|
||||
}
|
||||
else if (isPartArray(parameter)) {
|
||||
return (isMultipart ? RequestPartResolver.resolvePartArray(request, name) : null);
|
||||
}
|
||||
else {
|
||||
return UNRESOLVABLE;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isMultipartFileCollection(MethodParameter methodParam) {
|
||||
return (MultipartFile.class == getCollectionParameterType(methodParam));
|
||||
}
|
||||
|
||||
private static boolean isMultipartFileArray(MethodParameter methodParam) {
|
||||
return (MultipartFile.class == methodParam.getNestedParameterType().getComponentType());
|
||||
}
|
||||
|
||||
private static boolean isPartCollection(MethodParameter methodParam) {
|
||||
return (servletPartClass == getCollectionParameterType(methodParam));
|
||||
}
|
||||
|
||||
private static boolean isPartArray(MethodParameter methodParam) {
|
||||
return (servletPartClass == methodParam.getNestedParameterType().getComponentType());
|
||||
}
|
||||
|
||||
private static 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...
|
||||
*/
|
||||
private static class RequestPartResolver {
|
||||
|
||||
public static Object resolvePart(HttpServletRequest servletRequest, String name) throws Exception {
|
||||
return servletRequest.getPart(name);
|
||||
}
|
||||
|
||||
public static Object resolvePartList(HttpServletRequest servletRequest, String name) throws Exception {
|
||||
Collection<Part> parts = servletRequest.getParts();
|
||||
List<Part> result = new ArrayList<Part>(parts.size());
|
||||
for (Part part : parts) {
|
||||
if (part.getName().equals(name)) {
|
||||
result.add(part);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Object resolvePartArray(HttpServletRequest servletRequest, String name) throws Exception {
|
||||
Collection<Part> parts = servletRequest.getParts();
|
||||
List<Part> result = new ArrayList<Part>(parts.size());
|
||||
for (Part part : parts) {
|
||||
if (part.getName().equals(name)) {
|
||||
result.add(part);
|
||||
}
|
||||
}
|
||||
return result.toArray(new Part[result.size()]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
@@ -26,13 +26,10 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.util.ClassUtils;
|
||||
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.WebUtils;
|
||||
|
||||
|
||||
/**
|
||||
* {@link ServerHttpRequest} implementation that accesses one part of a multipart
|
||||
@@ -54,46 +51,26 @@ public class RequestPartServletServerHttpRequest extends ServletServerHttpReques
|
||||
|
||||
|
||||
/**
|
||||
* Create a new instance.
|
||||
* @param request the current request
|
||||
* Create a new {@code RequestPartServletServerHttpRequest} instance.
|
||||
* @param request the current servlet request
|
||||
* @param partName the name of the part to adapt to the {@link ServerHttpRequest} contract
|
||||
* @throws MissingServletRequestPartException if the request part cannot be found
|
||||
* @throws IllegalArgumentException if MultipartHttpServletRequest cannot be initialized
|
||||
* @throws MultipartException if MultipartHttpServletRequest cannot be initialized
|
||||
*/
|
||||
public RequestPartServletServerHttpRequest(HttpServletRequest request, String partName)
|
||||
throws MissingServletRequestPartException {
|
||||
|
||||
super(request);
|
||||
|
||||
this.multipartRequest = asMultipartRequest(request);
|
||||
this.multipartRequest = MultipartResolutionDelegate.asMultipartHttpServletRequest(request);
|
||||
this.partName = partName;
|
||||
|
||||
this.headers = this.multipartRequest.getMultipartHeaders(this.partName);
|
||||
if (this.headers == null) {
|
||||
if (request instanceof MultipartHttpServletRequest) {
|
||||
throw new MissingServletRequestPartException(partName);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException(
|
||||
"Failed to obtain request part: " + partName + ". " +
|
||||
"The part is missing or multipart processing is not configured. " +
|
||||
"Check for a MultipartResolver bean or if Servlet 3.0 multipart processing is enabled.");
|
||||
}
|
||||
throw new MissingServletRequestPartException(partName);
|
||||
}
|
||||
}
|
||||
|
||||
private static MultipartHttpServletRequest asMultipartRequest(HttpServletRequest request) {
|
||||
MultipartHttpServletRequest unwrapped = WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);
|
||||
if (unwrapped != null) {
|
||||
return unwrapped;
|
||||
}
|
||||
else if (ClassUtils.hasMethod(HttpServletRequest.class, "getParts")) {
|
||||
// Servlet 3.0 available ..
|
||||
return new StandardMultipartHttpServletRequest(request);
|
||||
}
|
||||
throw new IllegalArgumentException("Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public HttpHeaders getHeaders() {
|
||||
|
||||
Reference in New Issue
Block a user