Skip to content

Commit 255d1ad

Browse files
committed
WebDataBinder and @MVC request param binding detect and introspect MultipartFile arrays as well (SPR-2784)
1 parent 5b0448c commit 255d1ad

File tree

8 files changed

+179
-38
lines changed

8 files changed

+179
-38
lines changed

org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/bind/PortletRequestDataBinder.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2009 the original author or authors.
2+
* Copyright 2002-2010 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -107,7 +107,7 @@ public void bind(PortletRequest request) {
107107
MutablePropertyValues mpvs = new PortletRequestParameterPropertyValues(request);
108108
if (request instanceof MultipartRequest) {
109109
MultipartRequest multipartRequest = (MultipartRequest) request;
110-
bindMultipartFiles(multipartRequest.getFileMap(), mpvs);
110+
bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
111111
}
112112
doBind(mpvs);
113113
}

org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/ServletAnnotationControllerTests.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@
8686
import org.springframework.http.converter.xml.MarshallingHttpMessageConverter;
8787
import org.springframework.mock.web.MockHttpServletRequest;
8888
import org.springframework.mock.web.MockHttpServletResponse;
89+
import org.springframework.mock.web.MockMultipartFile;
90+
import org.springframework.mock.web.MockMultipartHttpServletRequest;
8991
import org.springframework.mock.web.MockServletConfig;
9092
import org.springframework.mock.web.MockServletContext;
9193
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
@@ -119,6 +121,7 @@
119121
import org.springframework.web.context.request.NativeWebRequest;
120122
import org.springframework.web.context.request.WebRequest;
121123
import org.springframework.web.context.support.GenericWebApplicationContext;
124+
import org.springframework.web.multipart.support.StringMultipartFileEditor;
122125
import org.springframework.web.servlet.DispatcherServlet;
123126
import org.springframework.web.servlet.ModelAndView;
124127
import org.springframework.web.servlet.View;
@@ -1699,6 +1702,43 @@ public void customMapEditor() throws Exception {
16991702
assertEquals("test-{foo=bar}", response.getContentAsString());
17001703
}
17011704

1705+
@Test
1706+
public void multipartFileAsSingleString() throws Exception {
1707+
initServlet(MultipartController.class);
1708+
1709+
MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
1710+
request.setRequestURI("/singleString");
1711+
request.addFile(new MockMultipartFile("content", "Juergen".getBytes()));
1712+
MockHttpServletResponse response = new MockHttpServletResponse();
1713+
servlet.service(request, response);
1714+
assertEquals("Juergen", response.getContentAsString());
1715+
}
1716+
1717+
@Test
1718+
public void multipartFileAsStringArray() throws Exception {
1719+
initServlet(MultipartController.class);
1720+
1721+
MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
1722+
request.setRequestURI("/stringArray");
1723+
request.addFile(new MockMultipartFile("content", "Juergen".getBytes()));
1724+
MockHttpServletResponse response = new MockHttpServletResponse();
1725+
servlet.service(request, response);
1726+
assertEquals("Juergen", response.getContentAsString());
1727+
}
1728+
1729+
@Test
1730+
public void multipartFilesAsStringArray() throws Exception {
1731+
initServlet(MultipartController.class);
1732+
1733+
MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
1734+
request.setRequestURI("/stringArray");
1735+
request.addFile(new MockMultipartFile("content", "Juergen".getBytes()));
1736+
request.addFile(new MockMultipartFile("content", "Eva".getBytes()));
1737+
MockHttpServletResponse response = new MockHttpServletResponse();
1738+
servlet.service(request, response);
1739+
assertEquals("Juergen,Eva", response.getContentAsString());
1740+
}
1741+
17021742

17031743
/*
17041744
* Controllers
@@ -2922,4 +2962,23 @@ public void setAsText(String text) throws IllegalArgumentException {
29222962

29232963
}
29242964

2965+
@Controller
2966+
public static class MultipartController {
2967+
2968+
@InitBinder
2969+
public void initBinder(WebDataBinder binder) {
2970+
binder.registerCustomEditor(String.class, new StringMultipartFileEditor());
2971+
}
2972+
2973+
@RequestMapping("/singleString")
2974+
public void processMultipart(@RequestParam("content") String content, HttpServletResponse response) throws IOException {
2975+
response.getWriter().write(content);
2976+
}
2977+
2978+
@RequestMapping("/stringArray")
2979+
public void processMultipart(@RequestParam("content") String[] content, HttpServletResponse response) throws IOException {
2980+
response.getWriter().write(StringUtils.arrayToCommaDelimitedString(content));
2981+
}
2982+
}
2983+
29252984
}

org.springframework.web/src/main/java/org/springframework/web/bind/ServletRequestDataBinder.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2009 the original author or authors.
2+
* Copyright 2002-2010 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -105,7 +105,7 @@ public void bind(ServletRequest request) {
105105
MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
106106
if (request instanceof MultipartRequest) {
107107
MultipartRequest multipartRequest = (MultipartRequest) request;
108-
bindMultipartFiles(multipartRequest.getFileMap(), mpvs);
108+
bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
109109
}
110110
doBind(mpvs);
111111
}

org.springframework.web/src/main/java/org/springframework/web/bind/WebDataBinder.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2008 the original author or authors.
2+
* Copyright 2002-2010 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
1717
package org.springframework.web.bind;
1818

1919
import java.lang.reflect.Array;
20+
import java.util.List;
2021
import java.util.Map;
2122

2223
import org.springframework.beans.MutablePropertyValues;
@@ -276,7 +277,10 @@ else if (fieldType != null && fieldType.isArray()) {
276277
* @param mpvs the property values to be bound (can be modified)
277278
* @see org.springframework.web.multipart.MultipartFile
278279
* @see #setBindEmptyMultipartFiles
280+
* @deprecated as of Spring 3.0, in favor of {@link #bindMultipart} which binds
281+
* all multipart files, even if more than one sent for the same name
279282
*/
283+
@Deprecated
280284
protected void bindMultipartFiles(Map<String, MultipartFile> multipartFiles, MutablePropertyValues mpvs) {
281285
for (Map.Entry<String, MultipartFile> entry : multipartFiles.entrySet()) {
282286
String key = entry.getKey();
@@ -287,4 +291,30 @@ protected void bindMultipartFiles(Map<String, MultipartFile> multipartFiles, Mut
287291
}
288292
}
289293

294+
/**
295+
* Bind all multipart files contained in the given request, if any
296+
* (in case of a multipart request).
297+
* <p>Multipart files will only be added to the property values if they
298+
* are not empty or if we're configured to bind empty multipart files too.
299+
* @param multipartFiles Map of field name String to MultipartFile object
300+
* @param mpvs the property values to be bound (can be modified)
301+
* @see org.springframework.web.multipart.MultipartFile
302+
* @see #setBindEmptyMultipartFiles
303+
*/
304+
protected void bindMultipart(Map<String, List<MultipartFile>> multipartFiles, MutablePropertyValues mpvs) {
305+
for (Map.Entry<String, List<MultipartFile>> entry : multipartFiles.entrySet()) {
306+
String key = entry.getKey();
307+
List<MultipartFile> values = entry.getValue();
308+
if (values.size() == 1) {
309+
MultipartFile value = values.get(0);
310+
if (isBindEmptyMultipartFiles() || !value.isEmpty()) {
311+
mpvs.add(key, value);
312+
}
313+
}
314+
else {
315+
mpvs.add(key, values);
316+
}
317+
}
318+
}
319+
290320
}

org.springframework.web/src/main/java/org/springframework/web/bind/annotation/support/HandlerMethodInvoker.java

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
import org.springframework.web.bind.support.WebRequestDataBinder;
7979
import org.springframework.web.context.request.NativeWebRequest;
8080
import org.springframework.web.context.request.WebRequest;
81+
import org.springframework.web.multipart.MultipartFile;
8182
import org.springframework.web.multipart.MultipartRequest;
8283

8384
/**
@@ -153,7 +154,7 @@ public final Object invokeHandlerMethod(Method handlerMethod, Object handler,
153154
if (debug) {
154155
logger.debug("Invoking model attribute method: " + attributeMethodToInvoke);
155156
}
156-
String attrName = AnnotationUtils.findAnnotation(attributeMethodToInvoke, ModelAttribute.class).value();
157+
String attrName = AnnotationUtils.findAnnotation(attributeMethod, ModelAttribute.class).value();
157158
if (!"".equals(attrName) && implicitModel.containsAttribute(attrName)) {
158159
continue;
159160
}
@@ -381,7 +382,7 @@ protected void initBinder(Object handler, String attrName, WebDataBinder binder,
381382
boolean debug = logger.isDebugEnabled();
382383
for (Method initBinderMethod : initBinderMethods) {
383384
Method methodToInvoke = BridgeMethodResolver.findBridgedMethod(initBinderMethod);
384-
String[] targetNames = AnnotationUtils.findAnnotation(methodToInvoke, InitBinder.class).value();
385+
String[] targetNames = AnnotationUtils.findAnnotation(initBinderMethod, InitBinder.class).value();
385386
if (targetNames.length == 0 || Arrays.asList(targetNames).contains(attrName)) {
386387
Object[] initBinderArgs =
387388
resolveInitBinderArguments(handler, methodToInvoke, binder, webRequest);
@@ -481,15 +482,25 @@ private Object resolveRequestParam(String paramName, boolean required, String de
481482
Object paramValue = null;
482483
MultipartRequest multipartRequest = webRequest.getNativeRequest(MultipartRequest.class);
483484
if (multipartRequest != null) {
484-
paramValue = multipartRequest.getFile(paramName);
485+
List<MultipartFile> files = multipartRequest.getFiles(paramName);
486+
if (!files.isEmpty()) {
487+
if (files.size() == 1 && !paramType.isArray() && !Collection.class.isAssignableFrom(paramType)) {
488+
paramValue = files.get(0);
489+
}
490+
else {
491+
paramValue = files;
492+
}
493+
}
485494
}
486495
if (paramValue == null) {
487496
String[] paramValues = webRequest.getParameterValues(paramName);
488-
if (paramValues != null && !paramType.isArray()) {
489-
paramValue = (paramValues.length == 1 ? paramValues[0] : paramValues);
490-
}
491-
else {
492-
paramValue = paramValues;
497+
if (paramValues != null) {
498+
if (paramValues.length == 1 && !paramType.isArray() && !Collection.class.isAssignableFrom(paramType)) {
499+
paramValue = paramValues[0];
500+
}
501+
else {
502+
paramValue = paramValues;
503+
}
493504
}
494505
}
495506
if (paramValue == null) {

org.springframework.web/src/main/java/org/springframework/web/bind/support/WebRequestDataBinder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public void bind(WebRequest request) {
103103
if (request instanceof NativeWebRequest) {
104104
MultipartRequest multipartRequest = ((NativeWebRequest) request).getNativeRequest(MultipartRequest.class);
105105
if (multipartRequest != null) {
106-
bindMultipartFiles(multipartRequest.getFileMap(), mpvs);
106+
bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
107107
}
108108
}
109109
doBind(mpvs);

org.springframework.web/src/main/java/org/springframework/web/multipart/MultipartRequest.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2009 the original author or authors.
2+
* Copyright 2002-2010 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -64,15 +64,13 @@ public interface MultipartRequest {
6464
* Return a {@link java.util.Map} of the multipart files contained in this request.
6565
* @return a map containing the parameter names as keys, and the
6666
* {@link MultipartFile} objects as values
67-
* @see MultipartFile
6867
*/
6968
Map<String, MultipartFile> getFileMap();
7069

7170
/**
7271
* Return a {@link MultiValueMap} of the multipart files contained in this request.
7372
* @return a map containing the parameter names as keys, and a list of
7473
* {@link MultipartFile} objects as values
75-
* @see MultipartFile
7674
* @since 3.0
7775
*/
7876
MultiValueMap<String, MultipartFile> getMultiFileMap();

org.springframework.web/src/test/java/org/springframework/web/bind/support/WebRequestDataBinderTests.java

Lines changed: 64 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2009 the original author or authors.
2+
* Copyright 2002-2010 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,8 +29,11 @@
2929
import org.springframework.beans.PropertyValues;
3030
import org.springframework.beans.TestBean;
3131
import org.springframework.mock.web.MockHttpServletRequest;
32+
import org.springframework.mock.web.MockMultipartFile;
33+
import org.springframework.mock.web.MockMultipartHttpServletRequest;
3234
import org.springframework.web.bind.ServletRequestParameterPropertyValues;
3335
import org.springframework.web.context.request.ServletWebRequest;
36+
import org.springframework.web.multipart.support.StringMultipartFileEditor;
3437

3538
/**
3639
* @author Juergen Hoeller
@@ -187,6 +190,46 @@ public void testEnumBinding() {
187190
assertEquals(MyEnum.FOO, target.getMyEnum());
188191
}
189192

193+
@Test
194+
public void testMultipartFileAsString() {
195+
TestBean target = new TestBean();
196+
WebRequestDataBinder binder = new WebRequestDataBinder(target);
197+
binder.registerCustomEditor(String.class, new StringMultipartFileEditor());
198+
199+
MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
200+
request.addFile(new MockMultipartFile("name", "Juergen".getBytes()));
201+
binder.bind(new ServletWebRequest(request));
202+
assertEquals("Juergen", target.getName());
203+
}
204+
205+
@Test
206+
public void testMultipartFileAsStringArray() {
207+
TestBean target = new TestBean();
208+
WebRequestDataBinder binder = new WebRequestDataBinder(target);
209+
binder.registerCustomEditor(String.class, new StringMultipartFileEditor());
210+
211+
MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
212+
request.addFile(new MockMultipartFile("stringArray", "Juergen".getBytes()));
213+
binder.bind(new ServletWebRequest(request));
214+
assertEquals(1, target.getStringArray().length);
215+
assertEquals("Juergen", target.getStringArray()[0]);
216+
}
217+
218+
@Test
219+
public void testMultipartFilesAsStringArray() {
220+
TestBean target = new TestBean();
221+
WebRequestDataBinder binder = new WebRequestDataBinder(target);
222+
binder.registerCustomEditor(String.class, new StringMultipartFileEditor());
223+
224+
MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
225+
request.addFile(new MockMultipartFile("stringArray", "Juergen".getBytes()));
226+
request.addFile(new MockMultipartFile("stringArray", "Eva".getBytes()));
227+
binder.bind(new ServletWebRequest(request));
228+
assertEquals(2, target.getStringArray().length);
229+
assertEquals("Juergen", target.getStringArray()[0]);
230+
assertEquals("Eva", target.getStringArray()[1]);
231+
}
232+
190233
@Test
191234
public void testNoPrefix() throws Exception {
192235
MockHttpServletRequest request = new MockHttpServletRequest();
@@ -213,26 +256,6 @@ public void testPrefix() throws Exception {
213256
doTestTony(pvs);
214257
}
215258

216-
@Test
217-
public void testNoParameters() throws Exception {
218-
MockHttpServletRequest request = new MockHttpServletRequest();
219-
ServletRequestParameterPropertyValues pvs = new ServletRequestParameterPropertyValues(request);
220-
assertTrue("Found no parameters", pvs.getPropertyValues().length == 0);
221-
}
222-
223-
@Test
224-
public void testMultipleValuesForParameter() throws Exception {
225-
MockHttpServletRequest request = new MockHttpServletRequest();
226-
String[] original = new String[] {"Tony", "Rod"};
227-
request.addParameter("forname", original);
228-
229-
ServletRequestParameterPropertyValues pvs = new ServletRequestParameterPropertyValues(request);
230-
assertTrue("Found 1 parameter", pvs.getPropertyValues().length == 1);
231-
assertTrue("Found array value", pvs.getPropertyValue("forname").getValue() instanceof String[]);
232-
String[] values = (String[]) pvs.getPropertyValue("forname").getValue();
233-
assertEquals("Correct values", Arrays.asList(values), Arrays.asList(original));
234-
}
235-
236259
/**
237260
* Must contain: forname=Tony surname=Blair age=50
238261
*/
@@ -258,6 +281,26 @@ protected void doTestTony(PropertyValues pvs) throws Exception {
258281
assertTrue("Map size is 0", m.size() == 0);
259282
}
260283

284+
@Test
285+
public void testNoParameters() throws Exception {
286+
MockHttpServletRequest request = new MockHttpServletRequest();
287+
ServletRequestParameterPropertyValues pvs = new ServletRequestParameterPropertyValues(request);
288+
assertTrue("Found no parameters", pvs.getPropertyValues().length == 0);
289+
}
290+
291+
@Test
292+
public void testMultipleValuesForParameter() throws Exception {
293+
MockHttpServletRequest request = new MockHttpServletRequest();
294+
String[] original = new String[] {"Tony", "Rod"};
295+
request.addParameter("forname", original);
296+
297+
ServletRequestParameterPropertyValues pvs = new ServletRequestParameterPropertyValues(request);
298+
assertTrue("Found 1 parameter", pvs.getPropertyValues().length == 1);
299+
assertTrue("Found array value", pvs.getPropertyValue("forname").getValue() instanceof String[]);
300+
String[] values = (String[]) pvs.getPropertyValue("forname").getValue();
301+
assertEquals("Correct values", Arrays.asList(values), Arrays.asList(original));
302+
}
303+
261304

262305
public static class EnumHolder {
263306

0 commit comments

Comments
 (0)