Support for @RequestParam Map declared with MultipartFile/Part values

Issue: SPR-17405
This commit is contained in:
Juergen Hoeller
2018-10-24 20:44:58 +02:00
parent 488a1d4561
commit f0f1979fc5
4 changed files with 247 additions and 96 deletions

View File

@@ -16,10 +16,14 @@
package org.springframework.web.method.annotation;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.Part;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
@@ -29,22 +33,29 @@ import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
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.MultipartResolutionDelegate;
/**
* Resolves {@link Map} method arguments annotated with an @{@link RequestParam}
* where the annotation does not specify a request parameter name.
* See {@link RequestParamMethodArgumentResolver} for resolving {@link Map}
* method arguments with a request parameter name.
*
* <p>The created {@link Map} contains all request parameter name/value pairs.
* If the method parameter type is {@link MultiValueMap} instead, the created
* map contains all request parameters and all there values for cases where
* request parameters have multiple values.
* <p>The created {@link Map} contains all request parameter name/value pairs,
* or all multipart files for a given parameter name if specifically declared
* with {@link MultipartFile} as the value type. If the method parameter type is
* {@link MultiValueMap} instead, the created map contains all request parameters
* and all their values for cases where request parameters have multiple values
* (or multiple multipart files of the same name).
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 3.1
* @see RequestParamMethodArgumentResolver
* @see HttpServletRequest#getParameterMap()
* @see MultipartRequest#getMultiFileMap()
* @see MultipartRequest#getFileMap()
*/
public class RequestParamMapMethodArgumentResolver implements HandlerMethodArgumentResolver {
@@ -59,26 +70,71 @@ public class RequestParamMapMethodArgumentResolver implements HandlerMethodArgum
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
Class<?> paramType = parameter.getParameterType();
ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
Map<String, String[]> parameterMap = webRequest.getParameterMap();
if (MultiValueMap.class.isAssignableFrom(paramType)) {
MultiValueMap<String, String> result = new LinkedMultiValueMap<>(parameterMap.size());
parameterMap.forEach((key, values) -> {
for (String value : values) {
result.add(key, value);
if (MultiValueMap.class.isAssignableFrom(parameter.getParameterType())) {
// MultiValueMap
Class<?> valueType = resolvableType.as(MultiValueMap.class).getGeneric(1).resolve();
if (valueType == MultipartFile.class) {
MultipartRequest multipartRequest = MultipartResolutionDelegate.resolveMultipartRequest(webRequest);
return (multipartRequest != null ? multipartRequest.getMultiFileMap() : new LinkedMultiValueMap<>(0));
}
else if (valueType == Part.class) {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
if (servletRequest != null && MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
Collection<Part> parts = servletRequest.getParts();
LinkedMultiValueMap<String, Part> result = new LinkedMultiValueMap<>(parts.size());
for (Part part : parts) {
result.add(part.getName(), part);
}
return result;
}
});
return result;
return new LinkedMultiValueMap<>(0);
}
else {
Map<String, String[]> parameterMap = webRequest.getParameterMap();
MultiValueMap<String, String> result = new LinkedMultiValueMap<>(parameterMap.size());
parameterMap.forEach((key, values) -> {
for (String value : values) {
result.add(key, value);
}
});
return result;
}
}
else {
Map<String, String> result = new LinkedHashMap<>(parameterMap.size());
parameterMap.forEach((key, values) -> {
if (values.length > 0) {
result.put(key, values[0]);
// Regular Map
Class<?> valueType = resolvableType.asMap().getGeneric(1).resolve();
if (valueType == MultipartFile.class) {
MultipartRequest multipartRequest = MultipartResolutionDelegate.resolveMultipartRequest(webRequest);
return (multipartRequest != null ? multipartRequest.getFileMap() : new LinkedHashMap<>(0));
}
else if (valueType == Part.class) {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
if (servletRequest != null && MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
Collection<Part> parts = servletRequest.getParts();
LinkedHashMap<String, Part> result = new LinkedHashMap<>(parts.size());
for (Part part : parts) {
if (!result.containsKey(part.getName())) {
result.put(part.getName(), part);
}
}
return result;
}
});
return result;
return new LinkedHashMap<>(0);
}
else {
Map<String, String[]> parameterMap = webRequest.getParameterMap();
Map<String, String> result = new LinkedHashMap<>(parameterMap.size());
parameterMap.forEach((key, values) -> {
if (values.length > 0) {
result.put(key, values[0]);
}
});
return result;
}
}
}
}

View File

@@ -25,8 +25,10 @@ import javax.servlet.http.Part;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartRequest;
import org.springframework.web.util.WebUtils;
/**
@@ -44,6 +46,19 @@ public abstract class MultipartResolutionDelegate {
public static final Object UNRESOLVABLE = new Object();
@Nullable
public static MultipartRequest resolveMultipartRequest(NativeWebRequest webRequest) {
MultipartRequest multipartRequest = webRequest.getNativeRequest(MultipartRequest.class);
if (multipartRequest != null) {
return multipartRequest;
}
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
if (servletRequest != null && isMultipartContent(servletRequest)) {
return new StandardMultipartHttpServletRequest(servletRequest);
}
return null;
}
public static boolean isMultipartRequest(HttpServletRequest request) {
return (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null ||
isMultipartContent(request));
@@ -103,13 +118,13 @@ public abstract class MultipartResolutionDelegate {
}
}
else if (Part.class == parameter.getNestedParameterType()) {
return (isMultipart ? resolvePart(request, name) : null);
return (isMultipart ? request.getPart(name): null);
}
else if (isPartCollection(parameter)) {
return (isMultipart ? resolvePartList(request, name) : null);
}
else if (isPartArray(parameter)) {
return (isMultipart ? resolvePartArray(request, name) : null);
return (isMultipart ? resolvePartList(request, name).toArray(new Part[0]) : null);
}
else {
return UNRESOLVABLE;
@@ -144,12 +159,8 @@ public abstract class MultipartResolutionDelegate {
return null;
}
private static Part resolvePart(HttpServletRequest servletRequest, String name) throws Exception {
return servletRequest.getPart(name);
}
private static List<Part> resolvePartList(HttpServletRequest servletRequest, String name) throws Exception {
Collection<Part> parts = servletRequest.getParts();
private static List<Part> resolvePartList(HttpServletRequest request, String name) throws Exception {
Collection<Part> parts = request.getParts();
List<Part> result = new ArrayList<>(parts.size());
for (Part part : parts) {
if (part.getName().equals(name)) {
@@ -159,15 +170,4 @@ public abstract class MultipartResolutionDelegate {
return result;
}
private static Part[] resolvePartArray(HttpServletRequest servletRequest, String name) throws Exception {
Collection<Part> parts = servletRequest.getParts();
List<Part> result = new ArrayList<>(parts.size());
for (Part part : parts) {
if (part.getName().equals(name)) {
result.add(part);
}
}
return result.toArray(new Part[0]);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2018 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.
@@ -18,74 +18,68 @@ package org.springframework.web.method.annotation;
import java.util.Collections;
import java.util.Map;
import javax.servlet.http.Part;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.MethodParameter;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse;
import org.springframework.mock.web.test.MockMultipartFile;
import org.springframework.mock.web.test.MockMultipartHttpServletRequest;
import org.springframework.mock.web.test.MockPart;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.ResolvableMethod;
import org.springframework.web.multipart.MultipartFile;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.springframework.web.method.MvcAnnotationPredicates.requestParam;
import static org.junit.Assert.*;
import static org.springframework.web.method.MvcAnnotationPredicates.*;
/**
* Test fixture with {@link RequestParamMapMethodArgumentResolver}.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @author Juergen Hoeller
*/
public class RequestParamMapMethodArgumentResolverTests {
private RequestParamMapMethodArgumentResolver resolver;
private RequestParamMapMethodArgumentResolver resolver = new RequestParamMapMethodArgumentResolver();
private NativeWebRequest webRequest;
private MockHttpServletRequest request = new MockHttpServletRequest();
private MockHttpServletRequest request;
private NativeWebRequest webRequest = new ServletWebRequest(request, new MockHttpServletResponse());
private ResolvableMethod testMethod = ResolvableMethod.on(getClass()).named("handle").build();
@Before
public void setUp() throws Exception {
resolver = new RequestParamMapMethodArgumentResolver();
request = new MockHttpServletRequest();
webRequest = new ServletWebRequest(request, new MockHttpServletResponse());
}
@Test
public void supportsParameter() {
MethodParameter param = this.testMethod.annot(requestParam().noName()).arg(Map.class);
MethodParameter param = this.testMethod.annot(requestParam().noName()).arg(Map.class, String.class, String.class);
assertTrue(resolver.supportsParameter(param));
param = this.testMethod.annotPresent(RequestParam.class).arg(MultiValueMap.class);
param = this.testMethod.annotPresent(RequestParam.class).arg(MultiValueMap.class, String.class, String.class);
assertTrue(resolver.supportsParameter(param));
param = this.testMethod.annot(requestParam().name("name")).arg(Map.class);
param = this.testMethod.annot(requestParam().name("name")).arg(Map.class, String.class, String.class);
assertFalse(resolver.supportsParameter(param));
param = this.testMethod.annotNotPresent(RequestParam.class).arg(Map.class);
param = this.testMethod.annotNotPresent(RequestParam.class).arg(Map.class, String.class, String.class);
assertFalse(resolver.supportsParameter(param));
}
@Test
public void resolveMapArgument() throws Exception {
public void resolveMapOfString() throws Exception {
String name = "foo";
String value = "bar";
request.addParameter(name, value);
Map<String, String> expected = Collections.singletonMap(name, value);
MethodParameter param = this.testMethod.annot(requestParam().noName()).arg(Map.class);
MethodParameter param = this.testMethod.annot(requestParam().noName()).arg(Map.class, String.class, String.class);
Object result = resolver.resolveArgument(param, null, webRequest, null);
assertTrue(result instanceof Map);
@@ -93,7 +87,7 @@ public class RequestParamMapMethodArgumentResolverTests {
}
@Test
public void resolveMultiValueMapArgument() throws Exception {
public void resolveMultiValueMapOfString() throws Exception {
String name = "foo";
String value1 = "bar";
String value2 = "baz";
@@ -103,19 +97,115 @@ public class RequestParamMapMethodArgumentResolverTests {
expected.add(name, value1);
expected.add(name, value2);
MethodParameter param = this.testMethod.annotPresent(RequestParam.class).arg(MultiValueMap.class);
MethodParameter param = this.testMethod.annotPresent(RequestParam.class).arg(MultiValueMap.class, String.class, String.class);
Object result = resolver.resolveArgument(param, null, webRequest, null);
assertTrue(result instanceof MultiValueMap);
assertEquals("Invalid result", expected, result);
}
@Test
@SuppressWarnings("unchecked")
public void resolveMapOfMultipartFile() throws Exception {
MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
MultipartFile expected1 = new MockMultipartFile("mfile", "Hello World".getBytes());
MultipartFile expected2 = new MockMultipartFile("other", "Hello World 3".getBytes());
request.addFile(expected1);
request.addFile(expected2);
webRequest = new ServletWebRequest(request);
MethodParameter param = this.testMethod.annot(requestParam().noName()).arg(Map.class, String.class, MultipartFile.class);
Object result = resolver.resolveArgument(param, null, webRequest, null);
assertTrue(result instanceof Map);
Map<String, MultipartFile> resultMap = (Map<String, MultipartFile>) result;
assertEquals(2, resultMap.size());
assertEquals(expected1, resultMap.get("mfile"));
assertEquals(expected2, resultMap.get("other"));
}
@Test
@SuppressWarnings("unchecked")
public void resolveMultiValueMapOfMultipartFile() throws Exception {
MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
MultipartFile expected1 = new MockMultipartFile("mfilelist", "Hello World 1".getBytes());
MultipartFile expected2 = new MockMultipartFile("mfilelist", "Hello World 2".getBytes());
MultipartFile expected3 = new MockMultipartFile("other", "Hello World 3".getBytes());
request.addFile(expected1);
request.addFile(expected2);
request.addFile(expected3);
webRequest = new ServletWebRequest(request);
MethodParameter param = this.testMethod.annot(requestParam().noName()).arg(MultiValueMap.class, String.class, MultipartFile.class);
Object result = resolver.resolveArgument(param, null, webRequest, null);
assertTrue(result instanceof MultiValueMap);
MultiValueMap<String, MultipartFile> resultMap = (MultiValueMap<String, MultipartFile>) result;
assertEquals(2, resultMap.size());
assertEquals(2, resultMap.get("mfilelist").size());
assertEquals(expected1, resultMap.get("mfilelist").get(0));
assertEquals(expected2, resultMap.get("mfilelist").get(1));
assertEquals(1, resultMap.get("other").size());
assertEquals(expected3, resultMap.get("other").get(0));
}
@Test
@SuppressWarnings("unchecked")
public void resolveMapOfPart() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setContentType("multipart/form-data");
Part expected1 = new MockPart("mfile", "Hello World".getBytes());
Part expected2 = new MockPart("other", "Hello World 3".getBytes());
request.addPart(expected1);
request.addPart(expected2);
webRequest = new ServletWebRequest(request);
MethodParameter param = this.testMethod.annot(requestParam().noName()).arg(Map.class, String.class, Part.class);
Object result = resolver.resolveArgument(param, null, webRequest, null);
assertTrue(result instanceof Map);
Map<String, Part> resultMap = (Map<String, Part>) result;
assertEquals(2, resultMap.size());
assertEquals(expected1, resultMap.get("mfile"));
assertEquals(expected2, resultMap.get("other"));
}
@Test
@SuppressWarnings("unchecked")
public void resolveMultiValueMapOfPart() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setContentType("multipart/form-data");
Part expected1 = new MockPart("mfilelist", "Hello World 1".getBytes());
Part expected2 = new MockPart("mfilelist", "Hello World 2".getBytes());
Part expected3 = new MockPart("other", "Hello World 3".getBytes());
request.addPart(expected1);
request.addPart(expected2);
request.addPart(expected3);
webRequest = new ServletWebRequest(request);
MethodParameter param = this.testMethod.annot(requestParam().noName()).arg(MultiValueMap.class, String.class, Part.class);
Object result = resolver.resolveArgument(param, null, webRequest, null);
assertTrue(result instanceof MultiValueMap);
MultiValueMap<String, Part> resultMap = (MultiValueMap<String, Part>) result;
assertEquals(2, resultMap.size());
assertEquals(2, resultMap.get("mfilelist").size());
assertEquals(expected1, resultMap.get("mfilelist").get(0));
assertEquals(expected2, resultMap.get("mfilelist").get(1));
assertEquals(1, resultMap.get("other").size());
assertEquals(expected3, resultMap.get("other").get(0));
}
public void handle(
@RequestParam Map<?, ?> param1,
@RequestParam MultiValueMap<?, ?> param2,
@RequestParam("name") Map<?, ?> param3,
Map<?, ?> param4) {
@RequestParam Map<String, String> param1,
@RequestParam MultiValueMap<String, String> param2,
@RequestParam Map<String, MultipartFile> param3,
@RequestParam MultiValueMap<String, MultipartFile> param4,
@RequestParam Map<String, Part> param5,
@RequestParam MultiValueMap<String, Part> param6,
@RequestParam("name") Map<String, String> param7,
Map<String, String> param8) {
}
}