Consistent support for path variable and multipart binding
Closes gh-24107 Closes gh-22169 Closes gh-25265
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2020 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,11 +17,14 @@
|
||||
package org.springframework.web.bind;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.beans.MutablePropertyValues;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.web.multipart.MultipartRequest;
|
||||
import org.springframework.web.multipart.support.StandardServletPartUtils;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
|
||||
/**
|
||||
@@ -103,6 +106,12 @@ public class ServletRequestDataBinder extends WebDataBinder {
|
||||
if (multipartRequest != null) {
|
||||
bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
|
||||
}
|
||||
else if (StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/")) {
|
||||
HttpServletRequest httpServletRequest = WebUtils.getNativeRequest(request, HttpServletRequest.class);
|
||||
if (httpServletRequest != null) {
|
||||
StandardServletPartUtils.bindParts(httpServletRequest, mpvs, isBindEmptyMultipartFiles());
|
||||
}
|
||||
}
|
||||
addBindValues(mpvs, request);
|
||||
doBind(mpvs);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -37,6 +37,7 @@ import org.springframework.web.server.ServerWebExchange;
|
||||
* binding from URL query params or form data in the request data to Java objects.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Juergen Hoeller
|
||||
* @since 5.0
|
||||
*/
|
||||
public class WebExchangeDataBinder extends WebDataBinder {
|
||||
@@ -64,7 +65,7 @@ public class WebExchangeDataBinder extends WebDataBinder {
|
||||
|
||||
/**
|
||||
* Bind query params, form data, and or multipart form data to the binder target.
|
||||
* @param exchange the current exchange.
|
||||
* @param exchange the current exchange
|
||||
* @return a {@code Mono<Void>} when binding is complete
|
||||
*/
|
||||
public Mono<Void> bind(ServerWebExchange exchange) {
|
||||
@@ -76,8 +77,11 @@ public class WebExchangeDataBinder extends WebDataBinder {
|
||||
/**
|
||||
* Protected method to obtain the values for data binding. By default this
|
||||
* method delegates to {@link #extractValuesToBind(ServerWebExchange)}.
|
||||
* @param exchange the current exchange
|
||||
* @return a map of bind values
|
||||
* @since 5.3
|
||||
*/
|
||||
protected Mono<Map<String, Object>> getValuesToBind(ServerWebExchange exchange) {
|
||||
public Mono<Map<String, Object>> getValuesToBind(ServerWebExchange exchange) {
|
||||
return extractValuesToBind(exchange);
|
||||
}
|
||||
|
||||
@@ -107,7 +111,7 @@ public class WebExchangeDataBinder extends WebDataBinder {
|
||||
});
|
||||
}
|
||||
|
||||
private static void addBindValue(Map<String, Object> params, String key, List<?> values) {
|
||||
protected static void addBindValue(Map<String, Object> params, String key, List<?> values) {
|
||||
if (!CollectionUtils.isEmpty(values)) {
|
||||
values = values.stream()
|
||||
.map(value -> value instanceof FormFieldPart ? ((FormFieldPart) value).value() : value)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2020 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,19 +17,16 @@
|
||||
package org.springframework.web.bind.support;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.Part;
|
||||
|
||||
import org.springframework.beans.MutablePropertyValues;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.WebRequest;
|
||||
import org.springframework.web.multipart.MultipartException;
|
||||
import org.springframework.web.multipart.MultipartRequest;
|
||||
import org.springframework.web.multipart.support.StandardServletPartUtils;
|
||||
|
||||
/**
|
||||
* Special {@link org.springframework.validation.DataBinder} to perform data binding
|
||||
@@ -109,53 +106,21 @@ public class WebRequestDataBinder extends WebDataBinder {
|
||||
*/
|
||||
public void bind(WebRequest request) {
|
||||
MutablePropertyValues mpvs = new MutablePropertyValues(request.getParameterMap());
|
||||
if (isMultipartRequest(request) && request instanceof NativeWebRequest) {
|
||||
if (request instanceof NativeWebRequest) {
|
||||
MultipartRequest multipartRequest = ((NativeWebRequest) request).getNativeRequest(MultipartRequest.class);
|
||||
if (multipartRequest != null) {
|
||||
bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
|
||||
}
|
||||
else {
|
||||
else if (StringUtils.startsWithIgnoreCase(request.getHeader("Content-Type"), "multipart/")) {
|
||||
HttpServletRequest servletRequest = ((NativeWebRequest) request).getNativeRequest(HttpServletRequest.class);
|
||||
if (servletRequest != null) {
|
||||
bindParts(servletRequest, mpvs);
|
||||
StandardServletPartUtils.bindParts(servletRequest, mpvs, isBindEmptyMultipartFiles());
|
||||
}
|
||||
}
|
||||
}
|
||||
doBind(mpvs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the request is a multipart request (by checking its Content-Type header).
|
||||
* @param request the request with parameters to bind
|
||||
*/
|
||||
private boolean isMultipartRequest(WebRequest request) {
|
||||
String contentType = request.getHeader("Content-Type");
|
||||
return StringUtils.startsWithIgnoreCase(contentType, "multipart");
|
||||
}
|
||||
|
||||
private void bindParts(HttpServletRequest request, MutablePropertyValues mpvs) {
|
||||
try {
|
||||
MultiValueMap<String, Part> map = new LinkedMultiValueMap<>();
|
||||
for (Part part : request.getParts()) {
|
||||
map.add(part.getName(), part);
|
||||
}
|
||||
map.forEach((key, values) -> {
|
||||
if (values.size() == 1) {
|
||||
Part part = values.get(0);
|
||||
if (isBindEmptyMultipartFiles() || part.getSize() > 0) {
|
||||
mpvs.add(key, part);
|
||||
}
|
||||
}
|
||||
else {
|
||||
mpvs.add(key, values);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new MultipartException("Failed to get request parts", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Treats errors as fatal.
|
||||
* <p>Use this method only if it's an error if the input isn't valid.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -28,6 +28,9 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.Part;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
@@ -39,6 +42,7 @@ import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.validation.Errors;
|
||||
@@ -53,6 +57,9 @@ import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.multipart.MultipartRequest;
|
||||
import org.springframework.web.multipart.support.StandardServletPartUtils;
|
||||
|
||||
/**
|
||||
* Resolve {@code @ModelAttribute} annotated method arguments and handle
|
||||
@@ -242,15 +249,9 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
|
||||
* @throws Exception in case of constructor invocation failure
|
||||
* @since 5.1
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
protected Object constructAttribute(Constructor<?> ctor, String attributeName, MethodParameter parameter,
|
||||
WebDataBinderFactory binderFactory, NativeWebRequest webRequest) throws Exception {
|
||||
|
||||
Object constructed = constructAttribute(ctor, attributeName, binderFactory, webRequest);
|
||||
if (constructed != null) {
|
||||
return constructed;
|
||||
}
|
||||
|
||||
if (ctor.getParameterCount() == 0) {
|
||||
// A single default constructor -> clearly a standard JavaBeans arrangement.
|
||||
return BeanUtils.instantiateClass(ctor);
|
||||
@@ -279,10 +280,13 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
|
||||
if (fieldDefaultPrefix != null) {
|
||||
value = webRequest.getParameter(fieldDefaultPrefix + paramName);
|
||||
}
|
||||
if (value == null && fieldMarkerPrefix != null) {
|
||||
if (webRequest.getParameter(fieldMarkerPrefix + paramName) != null) {
|
||||
if (value == null) {
|
||||
if (fieldMarkerPrefix != null && webRequest.getParameter(fieldMarkerPrefix + paramName) != null) {
|
||||
value = binder.getEmptyValue(paramType);
|
||||
}
|
||||
else {
|
||||
value = resolveConstructorArgument(paramName, paramType, webRequest);
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
@@ -320,20 +324,6 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
|
||||
return BeanUtils.instantiateClass(ctor, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new attribute instance with the given constructor.
|
||||
* @since 5.0
|
||||
* @deprecated as of 5.1, in favor of
|
||||
* {@link #constructAttribute(Constructor, String, MethodParameter, WebDataBinderFactory, NativeWebRequest)}
|
||||
*/
|
||||
@Deprecated
|
||||
@Nullable
|
||||
protected Object constructAttribute(Constructor<?> ctor, String attributeName,
|
||||
WebDataBinderFactory binderFactory, NativeWebRequest webRequest) throws Exception {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension point to bind the request to the target object.
|
||||
* @param binder the data binder instance to use for the binding
|
||||
@@ -343,6 +333,29 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
|
||||
((WebRequestDataBinder) binder).bind(request);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Object resolveConstructorArgument(String paramName, Class<?> paramType, NativeWebRequest request)
|
||||
throws Exception {
|
||||
|
||||
MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class);
|
||||
if (multipartRequest != null) {
|
||||
List<MultipartFile> files = multipartRequest.getFiles(paramName);
|
||||
if (!files.isEmpty()) {
|
||||
return (files.size() == 1 ? files.get(0) : files);
|
||||
}
|
||||
}
|
||||
else if (StringUtils.startsWithIgnoreCase(request.getHeader("Content-Type"), "multipart/")) {
|
||||
HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
|
||||
if (servletRequest != null) {
|
||||
List<Part> parts = StandardServletPartUtils.getParts(servletRequest, paramName);
|
||||
if (!parts.isEmpty()) {
|
||||
return (parts.size() == 1 ? parts.get(0) : parts);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the model attribute if applicable.
|
||||
* <p>The default implementation checks for {@code @javax.validation.Valid},
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2002-2020 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
|
||||
*
|
||||
* https://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.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.Part;
|
||||
|
||||
import org.springframework.beans.MutablePropertyValues;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.multipart.MultipartException;
|
||||
|
||||
/**
|
||||
* Utility methods for standard Servlet {@link Part} handling.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 5.3
|
||||
* @see HttpServletRequest#getParts()
|
||||
* @see StandardServletMultipartResolver
|
||||
*/
|
||||
public abstract class StandardServletPartUtils {
|
||||
|
||||
/**
|
||||
* Retrieve all parts from the given servlet request.
|
||||
* @param request the servlet request
|
||||
* @return the parts in a MultiValueMap
|
||||
* @throws MultipartException in case of failures
|
||||
*/
|
||||
public static MultiValueMap<String, Part> getParts(HttpServletRequest request) throws MultipartException {
|
||||
try {
|
||||
MultiValueMap<String, Part> parts = new LinkedMultiValueMap<>();
|
||||
for (Part part : request.getParts()) {
|
||||
parts.add(part.getName(), part);
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new MultipartException("Failed to get request parts", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all parts with the given name from the given servlet request.
|
||||
* @param request the servlet request
|
||||
* @param name the name to look for
|
||||
* @return the parts in a MultiValueMap
|
||||
* @throws MultipartException in case of failures
|
||||
*/
|
||||
public static List<Part> getParts(HttpServletRequest request, String name) throws MultipartException {
|
||||
try {
|
||||
List<Part> parts = new LinkedList<>();
|
||||
for (Part part : request.getParts()) {
|
||||
if (part.getName().equals(name)) {
|
||||
parts.add(part);
|
||||
}
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new MultipartException("Failed to get request parts", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind all parts from the given servlet request.
|
||||
* @param request the servlet request
|
||||
* @param mpvs the property values to bind to
|
||||
* @param bindEmpty whether to bind empty parts as well
|
||||
* @throws MultipartException in case of failures
|
||||
*/
|
||||
public static void bindParts(HttpServletRequest request, MutablePropertyValues mpvs, boolean bindEmpty)
|
||||
throws MultipartException {
|
||||
|
||||
getParts(request).forEach((key, values) -> {
|
||||
if (values.size() == 1) {
|
||||
Part part = values.get(0);
|
||||
if (bindEmpty || part.getSize() > 0) {
|
||||
mpvs.add(key, part);
|
||||
}
|
||||
}
|
||||
else {
|
||||
mpvs.add(key, values);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user