Consistent support for path variable and multipart binding
Closes gh-24107 Closes gh-22169 Closes gh-25265
This commit is contained in:
parent
5bdbbdfcfb
commit
d61c0ee57d
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -17,11 +17,14 @@
|
||||||
package org.springframework.web.bind;
|
package org.springframework.web.bind;
|
||||||
|
|
||||||
import javax.servlet.ServletRequest;
|
import javax.servlet.ServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
import org.springframework.beans.MutablePropertyValues;
|
import org.springframework.beans.MutablePropertyValues;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.validation.BindException;
|
import org.springframework.validation.BindException;
|
||||||
import org.springframework.web.multipart.MultipartRequest;
|
import org.springframework.web.multipart.MultipartRequest;
|
||||||
|
import org.springframework.web.multipart.support.StandardServletPartUtils;
|
||||||
import org.springframework.web.util.WebUtils;
|
import org.springframework.web.util.WebUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -103,6 +106,12 @@ public class ServletRequestDataBinder extends WebDataBinder {
|
||||||
if (multipartRequest != null) {
|
if (multipartRequest != null) {
|
||||||
bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
|
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);
|
addBindValues(mpvs, request);
|
||||||
doBind(mpvs);
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.
|
* binding from URL query params or form data in the request data to Java objects.
|
||||||
*
|
*
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
|
* @author Juergen Hoeller
|
||||||
* @since 5.0
|
* @since 5.0
|
||||||
*/
|
*/
|
||||||
public class WebExchangeDataBinder extends WebDataBinder {
|
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.
|
* 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
|
* @return a {@code Mono<Void>} when binding is complete
|
||||||
*/
|
*/
|
||||||
public Mono<Void> bind(ServerWebExchange exchange) {
|
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
|
* Protected method to obtain the values for data binding. By default this
|
||||||
* method delegates to {@link #extractValuesToBind(ServerWebExchange)}.
|
* 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);
|
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)) {
|
if (!CollectionUtils.isEmpty(values)) {
|
||||||
values = values.stream()
|
values = values.stream()
|
||||||
.map(value -> value instanceof FormFieldPart ? ((FormFieldPart) value).value() : value)
|
.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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -17,19 +17,16 @@
|
||||||
package org.springframework.web.bind.support;
|
package org.springframework.web.bind.support;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.Part;
|
|
||||||
|
|
||||||
import org.springframework.beans.MutablePropertyValues;
|
import org.springframework.beans.MutablePropertyValues;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
|
||||||
import org.springframework.util.MultiValueMap;
|
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.validation.BindException;
|
import org.springframework.validation.BindException;
|
||||||
import org.springframework.web.bind.WebDataBinder;
|
import org.springframework.web.bind.WebDataBinder;
|
||||||
import org.springframework.web.context.request.NativeWebRequest;
|
import org.springframework.web.context.request.NativeWebRequest;
|
||||||
import org.springframework.web.context.request.WebRequest;
|
import org.springframework.web.context.request.WebRequest;
|
||||||
import org.springframework.web.multipart.MultipartException;
|
|
||||||
import org.springframework.web.multipart.MultipartRequest;
|
import org.springframework.web.multipart.MultipartRequest;
|
||||||
|
import org.springframework.web.multipart.support.StandardServletPartUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Special {@link org.springframework.validation.DataBinder} to perform data binding
|
* Special {@link org.springframework.validation.DataBinder} to perform data binding
|
||||||
|
|
@ -109,53 +106,21 @@ public class WebRequestDataBinder extends WebDataBinder {
|
||||||
*/
|
*/
|
||||||
public void bind(WebRequest request) {
|
public void bind(WebRequest request) {
|
||||||
MutablePropertyValues mpvs = new MutablePropertyValues(request.getParameterMap());
|
MutablePropertyValues mpvs = new MutablePropertyValues(request.getParameterMap());
|
||||||
if (isMultipartRequest(request) && request instanceof NativeWebRequest) {
|
if (request instanceof NativeWebRequest) {
|
||||||
MultipartRequest multipartRequest = ((NativeWebRequest) request).getNativeRequest(MultipartRequest.class);
|
MultipartRequest multipartRequest = ((NativeWebRequest) request).getNativeRequest(MultipartRequest.class);
|
||||||
if (multipartRequest != null) {
|
if (multipartRequest != null) {
|
||||||
bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
|
bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
|
||||||
}
|
}
|
||||||
else {
|
else if (StringUtils.startsWithIgnoreCase(request.getHeader("Content-Type"), "multipart/")) {
|
||||||
HttpServletRequest servletRequest = ((NativeWebRequest) request).getNativeRequest(HttpServletRequest.class);
|
HttpServletRequest servletRequest = ((NativeWebRequest) request).getNativeRequest(HttpServletRequest.class);
|
||||||
if (servletRequest != null) {
|
if (servletRequest != null) {
|
||||||
bindParts(servletRequest, mpvs);
|
StandardServletPartUtils.bindParts(servletRequest, mpvs, isBindEmptyMultipartFiles());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
doBind(mpvs);
|
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.
|
* Treats errors as fatal.
|
||||||
* <p>Use this method only if it's an error if the input isn't valid.
|
* <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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.Optional;
|
||||||
import java.util.Set;
|
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.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
|
@ -39,6 +42,7 @@ import org.springframework.core.ParameterNameDiscoverer;
|
||||||
import org.springframework.core.annotation.AnnotationUtils;
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.validation.BindException;
|
import org.springframework.validation.BindException;
|
||||||
import org.springframework.validation.BindingResult;
|
import org.springframework.validation.BindingResult;
|
||||||
import org.springframework.validation.Errors;
|
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.HandlerMethodArgumentResolver;
|
||||||
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
|
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
|
||||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
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
|
* 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
|
* @throws Exception in case of constructor invocation failure
|
||||||
* @since 5.1
|
* @since 5.1
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
protected Object constructAttribute(Constructor<?> ctor, String attributeName, MethodParameter parameter,
|
protected Object constructAttribute(Constructor<?> ctor, String attributeName, MethodParameter parameter,
|
||||||
WebDataBinderFactory binderFactory, NativeWebRequest webRequest) throws Exception {
|
WebDataBinderFactory binderFactory, NativeWebRequest webRequest) throws Exception {
|
||||||
|
|
||||||
Object constructed = constructAttribute(ctor, attributeName, binderFactory, webRequest);
|
|
||||||
if (constructed != null) {
|
|
||||||
return constructed;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ctor.getParameterCount() == 0) {
|
if (ctor.getParameterCount() == 0) {
|
||||||
// A single default constructor -> clearly a standard JavaBeans arrangement.
|
// A single default constructor -> clearly a standard JavaBeans arrangement.
|
||||||
return BeanUtils.instantiateClass(ctor);
|
return BeanUtils.instantiateClass(ctor);
|
||||||
|
|
@ -279,10 +280,13 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
|
||||||
if (fieldDefaultPrefix != null) {
|
if (fieldDefaultPrefix != null) {
|
||||||
value = webRequest.getParameter(fieldDefaultPrefix + paramName);
|
value = webRequest.getParameter(fieldDefaultPrefix + paramName);
|
||||||
}
|
}
|
||||||
if (value == null && fieldMarkerPrefix != null) {
|
if (value == null) {
|
||||||
if (webRequest.getParameter(fieldMarkerPrefix + paramName) != null) {
|
if (fieldMarkerPrefix != null && webRequest.getParameter(fieldMarkerPrefix + paramName) != null) {
|
||||||
value = binder.getEmptyValue(paramType);
|
value = binder.getEmptyValue(paramType);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
value = resolveConstructorArgument(paramName, paramType, webRequest);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
|
@ -320,20 +324,6 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
|
||||||
return BeanUtils.instantiateClass(ctor, args);
|
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.
|
* Extension point to bind the request to the target object.
|
||||||
* @param binder the data binder instance to use for the binding
|
* @param binder the data binder instance to use for the binding
|
||||||
|
|
@ -343,6 +333,29 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
|
||||||
((WebRequestDataBinder) binder).bind(request);
|
((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.
|
* Validate the model attribute if applicable.
|
||||||
* <p>The default implementation checks for {@code @javax.validation.Valid},
|
* <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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -16,8 +16,16 @@
|
||||||
|
|
||||||
package org.springframework.web.reactive;
|
package org.springframework.web.reactive;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import org.springframework.http.codec.multipart.Part;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
import org.springframework.validation.support.BindingAwareConcurrentModel;
|
import org.springframework.validation.support.BindingAwareConcurrentModel;
|
||||||
import org.springframework.web.bind.support.WebBindingInitializer;
|
import org.springframework.web.bind.support.WebBindingInitializer;
|
||||||
import org.springframework.web.bind.support.WebExchangeDataBinder;
|
import org.springframework.web.bind.support.WebExchangeDataBinder;
|
||||||
|
|
@ -35,6 +43,7 @@ import org.springframework.web.server.ServerWebExchange;
|
||||||
* <p>Container for the default model for the request.
|
* <p>Container for the default model for the request.
|
||||||
*
|
*
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
|
* @author Juergen Hoeller
|
||||||
* @since 5.0
|
* @since 5.0
|
||||||
*/
|
*/
|
||||||
public class BindingContext {
|
public class BindingContext {
|
||||||
|
|
@ -79,7 +88,7 @@ public class BindingContext {
|
||||||
* @throws ServerErrorException if {@code @InitBinder} method invocation fails
|
* @throws ServerErrorException if {@code @InitBinder} method invocation fails
|
||||||
*/
|
*/
|
||||||
public WebExchangeDataBinder createDataBinder(ServerWebExchange exchange, @Nullable Object target, String name) {
|
public WebExchangeDataBinder createDataBinder(ServerWebExchange exchange, @Nullable Object target, String name) {
|
||||||
WebExchangeDataBinder dataBinder = new WebExchangeDataBinder(target, name);
|
WebExchangeDataBinder dataBinder = new ExtendedWebExchangeDataBinder(target, name);
|
||||||
if (this.initializer != null) {
|
if (this.initializer != null) {
|
||||||
this.initializer.initBinder(dataBinder);
|
this.initializer.initBinder(dataBinder);
|
||||||
}
|
}
|
||||||
|
|
@ -106,4 +115,34 @@ public class BindingContext {
|
||||||
return createDataBinder(exchange, null, name);
|
return createDataBinder(exchange, null, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extended variant of {@link WebExchangeDataBinder}, adding path variables.
|
||||||
|
*/
|
||||||
|
private static class ExtendedWebExchangeDataBinder extends WebExchangeDataBinder {
|
||||||
|
|
||||||
|
public ExtendedWebExchangeDataBinder(@Nullable Object target, String objectName) {
|
||||||
|
super(target, objectName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Map<String, Object>> getValuesToBind(ServerWebExchange exchange) {
|
||||||
|
Map<String, String> vars = exchange.getAttributeOrDefault(
|
||||||
|
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, Collections.emptyMap());
|
||||||
|
MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
|
||||||
|
Mono<MultiValueMap<String, String>> formData = exchange.getFormData();
|
||||||
|
Mono<MultiValueMap<String, Part>> multipartData = exchange.getMultipartData();
|
||||||
|
|
||||||
|
return Mono.zip(Mono.just(vars), Mono.just(queryParams), formData, multipartData)
|
||||||
|
.map(tuple -> {
|
||||||
|
Map<String, Object> result = new TreeMap<>();
|
||||||
|
tuple.getT1().forEach(result::put);
|
||||||
|
tuple.getT2().forEach((key, values) -> addBindValue(result, key, values));
|
||||||
|
tuple.getT3().forEach((key, values) -> addBindValue(result, key, values));
|
||||||
|
tuple.getT4().forEach((key, values) -> addBindValue(result, key, values));
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,6 @@ import org.springframework.util.ClassUtils;
|
||||||
import org.springframework.validation.BindingResult;
|
import org.springframework.validation.BindingResult;
|
||||||
import org.springframework.validation.Errors;
|
import org.springframework.validation.Errors;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.WebDataBinder;
|
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.support.WebExchangeBindException;
|
import org.springframework.web.bind.support.WebExchangeBindException;
|
||||||
import org.springframework.web.bind.support.WebExchangeDataBinder;
|
import org.springframework.web.bind.support.WebExchangeDataBinder;
|
||||||
|
|
@ -233,7 +232,8 @@ public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentR
|
||||||
}
|
}
|
||||||
|
|
||||||
// A single data class constructor -> resolve constructor arguments from request parameters.
|
// A single data class constructor -> resolve constructor arguments from request parameters.
|
||||||
return WebExchangeDataBinder.extractValuesToBind(exchange).map(bindValues -> {
|
WebExchangeDataBinder binder = context.createDataBinder(exchange, null, attributeName);
|
||||||
|
return getValuesToBind(binder, exchange).map(bindValues -> {
|
||||||
ConstructorProperties cp = ctor.getAnnotation(ConstructorProperties.class);
|
ConstructorProperties cp = ctor.getAnnotation(ConstructorProperties.class);
|
||||||
String[] paramNames = (cp != null ? cp.value() : parameterNameDiscoverer.getParameterNames(ctor));
|
String[] paramNames = (cp != null ? cp.value() : parameterNameDiscoverer.getParameterNames(ctor));
|
||||||
Assert.state(paramNames != null, () -> "Cannot resolve parameter names for constructor " + ctor);
|
Assert.state(paramNames != null, () -> "Cannot resolve parameter names for constructor " + ctor);
|
||||||
|
|
@ -241,7 +241,6 @@ public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentR
|
||||||
Assert.state(paramNames.length == paramTypes.length,
|
Assert.state(paramNames.length == paramTypes.length,
|
||||||
() -> "Invalid number of parameter names: " + paramNames.length + " for constructor " + ctor);
|
() -> "Invalid number of parameter names: " + paramNames.length + " for constructor " + ctor);
|
||||||
Object[] args = new Object[paramTypes.length];
|
Object[] args = new Object[paramTypes.length];
|
||||||
WebDataBinder binder = context.createDataBinder(exchange, null, attributeName);
|
|
||||||
String fieldDefaultPrefix = binder.getFieldDefaultPrefix();
|
String fieldDefaultPrefix = binder.getFieldDefaultPrefix();
|
||||||
String fieldMarkerPrefix = binder.getFieldMarkerPrefix();
|
String fieldMarkerPrefix = binder.getFieldMarkerPrefix();
|
||||||
for (int i = 0; i < paramNames.length; i++) {
|
for (int i = 0; i < paramNames.length; i++) {
|
||||||
|
|
@ -271,6 +270,18 @@ public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentR
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Protected method to obtain the values for data binding. By default this
|
||||||
|
* method delegates to {@link WebExchangeDataBinder#getValuesToBind}.
|
||||||
|
* @param binder the data binder in use
|
||||||
|
* @param exchange the current exchange
|
||||||
|
* @return a map of bind values
|
||||||
|
* @since 5.3
|
||||||
|
*/
|
||||||
|
public Mono<Map<String, Object>> getValuesToBind(WebExchangeDataBinder binder, ServerWebExchange exchange) {
|
||||||
|
return binder.getValuesToBind(exchange);
|
||||||
|
}
|
||||||
|
|
||||||
private boolean hasErrorsArgument(MethodParameter parameter) {
|
private boolean hasErrorsArgument(MethodParameter parameter) {
|
||||||
int i = parameter.getParameterIndex();
|
int i = parameter.getParameterIndex();
|
||||||
Class<?>[] paramTypes = parameter.getExecutable().getParameterTypes();
|
Class<?>[] paramTypes = parameter.getExecutable().getParameterTypes();
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,6 @@ import org.springframework.web.method.ControllerAdviceBean;
|
||||||
import org.springframework.web.method.HandlerMethod;
|
import org.springframework.web.method.HandlerMethod;
|
||||||
import org.springframework.web.method.annotation.ExceptionHandlerMethodResolver;
|
import org.springframework.web.method.annotation.ExceptionHandlerMethodResolver;
|
||||||
import org.springframework.web.method.annotation.MapMethodProcessor;
|
import org.springframework.web.method.annotation.MapMethodProcessor;
|
||||||
import org.springframework.web.method.annotation.ModelAttributeMethodProcessor;
|
|
||||||
import org.springframework.web.method.annotation.ModelMethodProcessor;
|
import org.springframework.web.method.annotation.ModelMethodProcessor;
|
||||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||||
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
|
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
|
||||||
|
|
@ -361,7 +360,7 @@ public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExce
|
||||||
getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice));
|
getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice));
|
||||||
|
|
||||||
// Annotation-based return value types
|
// Annotation-based return value types
|
||||||
handlers.add(new ModelAttributeMethodProcessor(false));
|
handlers.add(new ServletModelAttributeMethodProcessor(false));
|
||||||
handlers.add(new RequestResponseBodyMethodProcessor(
|
handlers.add(new RequestResponseBodyMethodProcessor(
|
||||||
getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice));
|
getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice));
|
||||||
|
|
||||||
|
|
@ -375,7 +374,7 @@ public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExce
|
||||||
}
|
}
|
||||||
|
|
||||||
// Catch-all
|
// Catch-all
|
||||||
handlers.add(new ModelAttributeMethodProcessor(true));
|
handlers.add(new ServletModelAttributeMethodProcessor(true));
|
||||||
|
|
||||||
return handlers;
|
return handlers;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -31,6 +31,8 @@ import org.springframework.web.servlet.HandlerMapping;
|
||||||
*
|
*
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
* @since 3.1
|
* @since 3.1
|
||||||
|
* @see ServletRequestDataBinder
|
||||||
|
* @see HandlerMapping#URI_TEMPLATE_VARIABLES_ATTRIBUTE
|
||||||
*/
|
*/
|
||||||
public class ExtendedServletRequestDataBinder extends ServletRequestDataBinder {
|
public class ExtendedServletRequestDataBinder extends ServletRequestDataBinder {
|
||||||
|
|
||||||
|
|
@ -60,9 +62,9 @@ public class ExtendedServletRequestDataBinder extends ServletRequestDataBinder {
|
||||||
* Merge URI variables into the property values to use for data binding.
|
* Merge URI variables into the property values to use for data binding.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {
|
protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {
|
||||||
String attr = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
|
String attr = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
Map<String, String> uriVars = (Map<String, String>) request.getAttribute(attr);
|
Map<String, String> uriVars = (Map<String, String>) request.getAttribute(attr);
|
||||||
if (uriVars != null) {
|
if (uriVars != null) {
|
||||||
uriVars.forEach((name, value) -> {
|
uriVars.forEach((name, value) -> {
|
||||||
|
|
|
||||||
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -58,7 +58,8 @@ public class ModelAndViewResolverMethodReturnValueHandler implements HandlerMeth
|
||||||
@Nullable
|
@Nullable
|
||||||
private final List<ModelAndViewResolver> mavResolvers;
|
private final List<ModelAndViewResolver> mavResolvers;
|
||||||
|
|
||||||
private final ModelAttributeMethodProcessor modelAttributeProcessor = new ModelAttributeMethodProcessor(true);
|
private final ModelAttributeMethodProcessor modelAttributeProcessor =
|
||||||
|
new ServletModelAttributeMethodProcessor(true);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,6 @@ import org.springframework.web.method.annotation.ErrorsMethodArgumentResolver;
|
||||||
import org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver;
|
import org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver;
|
||||||
import org.springframework.web.method.annotation.InitBinderDataBinderFactory;
|
import org.springframework.web.method.annotation.InitBinderDataBinderFactory;
|
||||||
import org.springframework.web.method.annotation.MapMethodProcessor;
|
import org.springframework.web.method.annotation.MapMethodProcessor;
|
||||||
import org.springframework.web.method.annotation.ModelAttributeMethodProcessor;
|
|
||||||
import org.springframework.web.method.annotation.ModelFactory;
|
import org.springframework.web.method.annotation.ModelFactory;
|
||||||
import org.springframework.web.method.annotation.ModelMethodProcessor;
|
import org.springframework.web.method.annotation.ModelMethodProcessor;
|
||||||
import org.springframework.web.method.annotation.RequestHeaderMapMethodArgumentResolver;
|
import org.springframework.web.method.annotation.RequestHeaderMapMethodArgumentResolver;
|
||||||
|
|
@ -739,7 +738,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
|
||||||
handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));
|
handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));
|
||||||
|
|
||||||
// Annotation-based return value types
|
// Annotation-based return value types
|
||||||
handlers.add(new ModelAttributeMethodProcessor(false));
|
handlers.add(new ServletModelAttributeMethodProcessor(false));
|
||||||
handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),
|
handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),
|
||||||
this.contentNegotiationManager, this.requestResponseBodyAdvice));
|
this.contentNegotiationManager, this.requestResponseBodyAdvice));
|
||||||
|
|
||||||
|
|
@ -757,7 +756,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
|
||||||
handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers()));
|
handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers()));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
handlers.add(new ModelAttributeMethodProcessor(true));
|
handlers.add(new ServletModelAttributeMethodProcessor(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
return handlers;
|
return handlers;
|
||||||
|
|
|
||||||
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -108,8 +108,8 @@ public class ServletModelAttributeMethodProcessor extends ModelAttributeMethodPr
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
protected final Map<String, String> getUriTemplateVariables(NativeWebRequest request) {
|
protected final Map<String, String> getUriTemplateVariables(NativeWebRequest request) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
Map<String, String> variables = (Map<String, String>) request.getAttribute(
|
Map<String, String> variables = (Map<String, String>) request.getAttribute(
|
||||||
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
|
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
|
||||||
return (variables != null ? variables : Collections.emptyMap());
|
return (variables != null ? variables : Collections.emptyMap());
|
||||||
|
|
@ -158,4 +158,23 @@ public class ServletModelAttributeMethodProcessor extends ModelAttributeMethodPr
|
||||||
servletBinder.bind(servletRequest);
|
servletBinder.bind(servletRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public Object resolveConstructorArgument(String paramName, Class<?> paramType, NativeWebRequest request)
|
||||||
|
throws Exception {
|
||||||
|
|
||||||
|
Object value = super.resolveConstructorArgument(paramName, paramType, request);
|
||||||
|
if (value != null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
|
||||||
|
if (servletRequest != null) {
|
||||||
|
String attr = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, String> uriVars = (Map<String, String>) servletRequest.getAttribute(attr);
|
||||||
|
return uriVars.get(paramName);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@ import javax.servlet.http.Cookie;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
|
import javax.servlet.http.Part;
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
import javax.xml.bind.annotation.XmlRootElement;
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
|
|
@ -108,6 +109,7 @@ import org.springframework.ui.Model;
|
||||||
import org.springframework.ui.ModelMap;
|
import org.springframework.ui.ModelMap;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.util.StreamUtils;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.validation.BindingResult;
|
import org.springframework.validation.BindingResult;
|
||||||
import org.springframework.validation.Errors;
|
import org.springframework.validation.Errors;
|
||||||
|
|
@ -139,6 +141,7 @@ import org.springframework.web.context.request.NativeWebRequest;
|
||||||
import org.springframework.web.context.request.WebRequest;
|
import org.springframework.web.context.request.WebRequest;
|
||||||
import org.springframework.web.context.support.GenericWebApplicationContext;
|
import org.springframework.web.context.support.GenericWebApplicationContext;
|
||||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import org.springframework.web.multipart.support.StringMultipartFileEditor;
|
import org.springframework.web.multipart.support.StringMultipartFileEditor;
|
||||||
import org.springframework.web.servlet.DispatcherServlet;
|
import org.springframework.web.servlet.DispatcherServlet;
|
||||||
import org.springframework.web.servlet.ModelAndView;
|
import org.springframework.web.servlet.ModelAndView;
|
||||||
|
|
@ -158,6 +161,7 @@ import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
|
||||||
import org.springframework.web.testfixture.servlet.MockHttpServletResponse;
|
import org.springframework.web.testfixture.servlet.MockHttpServletResponse;
|
||||||
import org.springframework.web.testfixture.servlet.MockMultipartFile;
|
import org.springframework.web.testfixture.servlet.MockMultipartFile;
|
||||||
import org.springframework.web.testfixture.servlet.MockMultipartHttpServletRequest;
|
import org.springframework.web.testfixture.servlet.MockMultipartHttpServletRequest;
|
||||||
|
import org.springframework.web.testfixture.servlet.MockPart;
|
||||||
import org.springframework.web.testfixture.servlet.MockServletConfig;
|
import org.springframework.web.testfixture.servlet.MockServletConfig;
|
||||||
import org.springframework.web.testfixture.servlet.MockServletContext;
|
import org.springframework.web.testfixture.servlet.MockServletContext;
|
||||||
|
|
||||||
|
|
@ -1934,6 +1938,44 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
|
||||||
assertThat(response.getContentAsString()).isEqualTo("value1-true-0");
|
assertThat(response.getContentAsString()).isEqualTo("value1-true-0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PathPatternsParameterizedTest
|
||||||
|
void dataClassBindingWithPathVariable(boolean usePathPatterns) throws Exception {
|
||||||
|
initDispatcherServlet(PathVariableDataClassController.class, usePathPatterns);
|
||||||
|
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bind/true");
|
||||||
|
request.addParameter("param1", "value1");
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
getServlet().service(request, response);
|
||||||
|
assertThat(response.getContentAsString()).isEqualTo("value1-true-0");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PathPatternsParameterizedTest
|
||||||
|
void dataClassBindingWithMultipartFile(boolean usePathPatterns) throws Exception {
|
||||||
|
initDispatcherServlet(MultipartFileDataClassController.class, usePathPatterns);
|
||||||
|
|
||||||
|
MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
|
||||||
|
request.setRequestURI("/bind");
|
||||||
|
request.addFile(new MockMultipartFile("param1", "value1".getBytes(StandardCharsets.UTF_8)));
|
||||||
|
request.addParameter("param2", "true");
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
getServlet().service(request, response);
|
||||||
|
assertThat(response.getContentAsString()).isEqualTo("value1-true-0");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PathPatternsParameterizedTest
|
||||||
|
void dataClassBindingWithServletPart(boolean usePathPatterns) throws Exception {
|
||||||
|
initDispatcherServlet(ServletPartDataClassController.class, usePathPatterns);
|
||||||
|
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
request.setContentType("multipart/form-data");
|
||||||
|
request.setRequestURI("/bind");
|
||||||
|
request.addPart(new MockPart("param1", "value1".getBytes(StandardCharsets.UTF_8)));
|
||||||
|
request.addParameter("param2", "true");
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
getServlet().service(request, response);
|
||||||
|
assertThat(response.getContentAsString()).isEqualTo("value1-true-0");
|
||||||
|
}
|
||||||
|
|
||||||
@PathPatternsParameterizedTest
|
@PathPatternsParameterizedTest
|
||||||
void dataClassBindingWithAdditionalSetter(boolean usePathPatterns) throws Exception {
|
void dataClassBindingWithAdditionalSetter(boolean usePathPatterns) throws Exception {
|
||||||
initDispatcherServlet(DataClassController.class, usePathPatterns);
|
initDispatcherServlet(DataClassController.class, usePathPatterns);
|
||||||
|
|
@ -3831,6 +3873,15 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public static class PathVariableDataClassController {
|
||||||
|
|
||||||
|
@RequestMapping("/bind/{param2}")
|
||||||
|
public String handle(DataClass data) {
|
||||||
|
return data.param1 + "-" + data.param2 + "-" + data.param3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
public static class ValidatedDataClassController {
|
public static class ValidatedDataClassController {
|
||||||
|
|
||||||
|
|
@ -3873,6 +3924,70 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class MultipartFileDataClass {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public final MultipartFile param1;
|
||||||
|
|
||||||
|
public final boolean param2;
|
||||||
|
|
||||||
|
public int param3;
|
||||||
|
|
||||||
|
@ConstructorProperties({"param1", "param2", "optionalParam"})
|
||||||
|
public MultipartFileDataClass(MultipartFile param1, boolean p2, Optional<Integer> optionalParam) {
|
||||||
|
this.param1 = param1;
|
||||||
|
this.param2 = p2;
|
||||||
|
Assert.notNull(optionalParam, "Optional must not be null");
|
||||||
|
optionalParam.ifPresent(integer -> this.param3 = integer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParam3(int param3) {
|
||||||
|
this.param3 = param3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public static class MultipartFileDataClassController {
|
||||||
|
|
||||||
|
@RequestMapping("/bind")
|
||||||
|
public String handle(MultipartFileDataClass data) throws IOException {
|
||||||
|
return StreamUtils.copyToString(data.param1.getInputStream(), StandardCharsets.UTF_8) +
|
||||||
|
"-" + data.param2 + "-" + data.param3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ServletPartDataClass {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public final Part param1;
|
||||||
|
|
||||||
|
public final boolean param2;
|
||||||
|
|
||||||
|
public int param3;
|
||||||
|
|
||||||
|
@ConstructorProperties({"param1", "param2", "optionalParam"})
|
||||||
|
public ServletPartDataClass(Part param1, boolean p2, Optional<Integer> optionalParam) {
|
||||||
|
this.param1 = param1;
|
||||||
|
this.param2 = p2;
|
||||||
|
Assert.notNull(optionalParam, "Optional must not be null");
|
||||||
|
optionalParam.ifPresent(integer -> this.param3 = integer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParam3(int param3) {
|
||||||
|
this.param3 = param3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public static class ServletPartDataClassController {
|
||||||
|
|
||||||
|
@RequestMapping("/bind")
|
||||||
|
public String handle(ServletPartDataClass data) throws IOException {
|
||||||
|
return StreamUtils.copyToString(data.param1.getInputStream(), StandardCharsets.UTF_8) +
|
||||||
|
"-" + data.param2 + "-" + data.param3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
public static class OptionalDataClassController {
|
public static class OptionalDataClassController {
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue