WebDataBinder and @MVC request param binding detect and introspect MultipartFile arrays as well (SPR-2784)

This commit is contained in:
Juergen Hoeller 2010-08-15 22:51:02 +00:00
parent 5b0448c609
commit 255d1ad434
8 changed files with 179 additions and 38 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* Copyright 2002-2010 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.
@ -107,7 +107,7 @@ public class PortletRequestDataBinder extends WebDataBinder {
MutablePropertyValues mpvs = new PortletRequestParameterPropertyValues(request);
if (request instanceof MultipartRequest) {
MultipartRequest multipartRequest = (MultipartRequest) request;
bindMultipartFiles(multipartRequest.getFileMap(), mpvs);
bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
}
doBind(mpvs);
}

View File

@ -86,6 +86,8 @@ import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.xml.MarshallingHttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.mock.web.MockMultipartHttpServletRequest;
import org.springframework.mock.web.MockServletConfig;
import org.springframework.mock.web.MockServletContext;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
@ -119,6 +121,7 @@ import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.multipart.support.StringMultipartFileEditor;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
@ -1699,6 +1702,43 @@ public class ServletAnnotationControllerTests {
assertEquals("test-{foo=bar}", response.getContentAsString());
}
@Test
public void multipartFileAsSingleString() throws Exception {
initServlet(MultipartController.class);
MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
request.setRequestURI("/singleString");
request.addFile(new MockMultipartFile("content", "Juergen".getBytes()));
MockHttpServletResponse response = new MockHttpServletResponse();
servlet.service(request, response);
assertEquals("Juergen", response.getContentAsString());
}
@Test
public void multipartFileAsStringArray() throws Exception {
initServlet(MultipartController.class);
MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
request.setRequestURI("/stringArray");
request.addFile(new MockMultipartFile("content", "Juergen".getBytes()));
MockHttpServletResponse response = new MockHttpServletResponse();
servlet.service(request, response);
assertEquals("Juergen", response.getContentAsString());
}
@Test
public void multipartFilesAsStringArray() throws Exception {
initServlet(MultipartController.class);
MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
request.setRequestURI("/stringArray");
request.addFile(new MockMultipartFile("content", "Juergen".getBytes()));
request.addFile(new MockMultipartFile("content", "Eva".getBytes()));
MockHttpServletResponse response = new MockHttpServletResponse();
servlet.service(request, response);
assertEquals("Juergen,Eva", response.getContentAsString());
}
/*
* Controllers
@ -2922,4 +2962,23 @@ public class ServletAnnotationControllerTests {
}
@Controller
public static class MultipartController {
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(String.class, new StringMultipartFileEditor());
}
@RequestMapping("/singleString")
public void processMultipart(@RequestParam("content") String content, HttpServletResponse response) throws IOException {
response.getWriter().write(content);
}
@RequestMapping("/stringArray")
public void processMultipart(@RequestParam("content") String[] content, HttpServletResponse response) throws IOException {
response.getWriter().write(StringUtils.arrayToCommaDelimitedString(content));
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* Copyright 2002-2010 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.
@ -105,7 +105,7 @@ public class ServletRequestDataBinder extends WebDataBinder {
MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);
if (request instanceof MultipartRequest) {
MultipartRequest multipartRequest = (MultipartRequest) request;
bindMultipartFiles(multipartRequest.getFileMap(), mpvs);
bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
}
doBind(mpvs);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2010 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,6 +17,7 @@
package org.springframework.web.bind;
import java.lang.reflect.Array;
import java.util.List;
import java.util.Map;
import org.springframework.beans.MutablePropertyValues;
@ -276,7 +277,10 @@ public class WebDataBinder extends DataBinder {
* @param mpvs the property values to be bound (can be modified)
* @see org.springframework.web.multipart.MultipartFile
* @see #setBindEmptyMultipartFiles
* @deprecated as of Spring 3.0, in favor of {@link #bindMultipart} which binds
* all multipart files, even if more than one sent for the same name
*/
@Deprecated
protected void bindMultipartFiles(Map<String, MultipartFile> multipartFiles, MutablePropertyValues mpvs) {
for (Map.Entry<String, MultipartFile> entry : multipartFiles.entrySet()) {
String key = entry.getKey();
@ -287,4 +291,30 @@ public class WebDataBinder extends DataBinder {
}
}
/**
* Bind all multipart files contained in the given request, if any
* (in case of a multipart request).
* <p>Multipart files will only be added to the property values if they
* are not empty or if we're configured to bind empty multipart files too.
* @param multipartFiles Map of field name String to MultipartFile object
* @param mpvs the property values to be bound (can be modified)
* @see org.springframework.web.multipart.MultipartFile
* @see #setBindEmptyMultipartFiles
*/
protected void bindMultipart(Map<String, List<MultipartFile>> multipartFiles, MutablePropertyValues mpvs) {
for (Map.Entry<String, List<MultipartFile>> entry : multipartFiles.entrySet()) {
String key = entry.getKey();
List<MultipartFile> values = entry.getValue();
if (values.size() == 1) {
MultipartFile value = values.get(0);
if (isBindEmptyMultipartFiles() || !value.isEmpty()) {
mpvs.add(key, value);
}
}
else {
mpvs.add(key, values);
}
}
}
}

View File

@ -78,6 +78,7 @@ import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.bind.support.WebRequestDataBinder;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartRequest;
/**
@ -153,7 +154,7 @@ public class HandlerMethodInvoker {
if (debug) {
logger.debug("Invoking model attribute method: " + attributeMethodToInvoke);
}
String attrName = AnnotationUtils.findAnnotation(attributeMethodToInvoke, ModelAttribute.class).value();
String attrName = AnnotationUtils.findAnnotation(attributeMethod, ModelAttribute.class).value();
if (!"".equals(attrName) && implicitModel.containsAttribute(attrName)) {
continue;
}
@ -381,7 +382,7 @@ public class HandlerMethodInvoker {
boolean debug = logger.isDebugEnabled();
for (Method initBinderMethod : initBinderMethods) {
Method methodToInvoke = BridgeMethodResolver.findBridgedMethod(initBinderMethod);
String[] targetNames = AnnotationUtils.findAnnotation(methodToInvoke, InitBinder.class).value();
String[] targetNames = AnnotationUtils.findAnnotation(initBinderMethod, InitBinder.class).value();
if (targetNames.length == 0 || Arrays.asList(targetNames).contains(attrName)) {
Object[] initBinderArgs =
resolveInitBinderArguments(handler, methodToInvoke, binder, webRequest);
@ -481,15 +482,25 @@ public class HandlerMethodInvoker {
Object paramValue = null;
MultipartRequest multipartRequest = webRequest.getNativeRequest(MultipartRequest.class);
if (multipartRequest != null) {
paramValue = multipartRequest.getFile(paramName);
List<MultipartFile> files = multipartRequest.getFiles(paramName);
if (!files.isEmpty()) {
if (files.size() == 1 && !paramType.isArray() && !Collection.class.isAssignableFrom(paramType)) {
paramValue = files.get(0);
}
else {
paramValue = files;
}
}
}
if (paramValue == null) {
String[] paramValues = webRequest.getParameterValues(paramName);
if (paramValues != null && !paramType.isArray()) {
paramValue = (paramValues.length == 1 ? paramValues[0] : paramValues);
}
else {
paramValue = paramValues;
if (paramValues != null) {
if (paramValues.length == 1 && !paramType.isArray() && !Collection.class.isAssignableFrom(paramType)) {
paramValue = paramValues[0];
}
else {
paramValue = paramValues;
}
}
}
if (paramValue == null) {

View File

@ -103,7 +103,7 @@ public class WebRequestDataBinder extends WebDataBinder {
if (request instanceof NativeWebRequest) {
MultipartRequest multipartRequest = ((NativeWebRequest) request).getNativeRequest(MultipartRequest.class);
if (multipartRequest != null) {
bindMultipartFiles(multipartRequest.getFileMap(), mpvs);
bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
}
}
doBind(mpvs);

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* Copyright 2002-2010 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.
@ -64,7 +64,6 @@ public interface MultipartRequest {
* Return a {@link java.util.Map} of the multipart files contained in this request.
* @return a map containing the parameter names as keys, and the
* {@link MultipartFile} objects as values
* @see MultipartFile
*/
Map<String, MultipartFile> getFileMap();
@ -72,7 +71,6 @@ public interface MultipartRequest {
* Return a {@link MultiValueMap} of the multipart files contained in this request.
* @return a map containing the parameter names as keys, and a list of
* {@link MultipartFile} objects as values
* @see MultipartFile
* @since 3.0
*/
MultiValueMap<String, MultipartFile> getMultiFileMap();

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2009 the original author or authors.
* Copyright 2002-2010 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.
@ -29,8 +29,11 @@ import org.springframework.beans.PropertyValue;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.TestBean;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.mock.web.MockMultipartHttpServletRequest;
import org.springframework.web.bind.ServletRequestParameterPropertyValues;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.multipart.support.StringMultipartFileEditor;
/**
* @author Juergen Hoeller
@ -187,6 +190,46 @@ public class WebRequestDataBinderTests {
assertEquals(MyEnum.FOO, target.getMyEnum());
}
@Test
public void testMultipartFileAsString() {
TestBean target = new TestBean();
WebRequestDataBinder binder = new WebRequestDataBinder(target);
binder.registerCustomEditor(String.class, new StringMultipartFileEditor());
MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
request.addFile(new MockMultipartFile("name", "Juergen".getBytes()));
binder.bind(new ServletWebRequest(request));
assertEquals("Juergen", target.getName());
}
@Test
public void testMultipartFileAsStringArray() {
TestBean target = new TestBean();
WebRequestDataBinder binder = new WebRequestDataBinder(target);
binder.registerCustomEditor(String.class, new StringMultipartFileEditor());
MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
request.addFile(new MockMultipartFile("stringArray", "Juergen".getBytes()));
binder.bind(new ServletWebRequest(request));
assertEquals(1, target.getStringArray().length);
assertEquals("Juergen", target.getStringArray()[0]);
}
@Test
public void testMultipartFilesAsStringArray() {
TestBean target = new TestBean();
WebRequestDataBinder binder = new WebRequestDataBinder(target);
binder.registerCustomEditor(String.class, new StringMultipartFileEditor());
MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
request.addFile(new MockMultipartFile("stringArray", "Juergen".getBytes()));
request.addFile(new MockMultipartFile("stringArray", "Eva".getBytes()));
binder.bind(new ServletWebRequest(request));
assertEquals(2, target.getStringArray().length);
assertEquals("Juergen", target.getStringArray()[0]);
assertEquals("Eva", target.getStringArray()[1]);
}
@Test
public void testNoPrefix() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
@ -213,26 +256,6 @@ public class WebRequestDataBinderTests {
doTestTony(pvs);
}
@Test
public void testNoParameters() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
ServletRequestParameterPropertyValues pvs = new ServletRequestParameterPropertyValues(request);
assertTrue("Found no parameters", pvs.getPropertyValues().length == 0);
}
@Test
public void testMultipleValuesForParameter() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
String[] original = new String[] {"Tony", "Rod"};
request.addParameter("forname", original);
ServletRequestParameterPropertyValues pvs = new ServletRequestParameterPropertyValues(request);
assertTrue("Found 1 parameter", pvs.getPropertyValues().length == 1);
assertTrue("Found array value", pvs.getPropertyValue("forname").getValue() instanceof String[]);
String[] values = (String[]) pvs.getPropertyValue("forname").getValue();
assertEquals("Correct values", Arrays.asList(values), Arrays.asList(original));
}
/**
* Must contain: forname=Tony surname=Blair age=50
*/
@ -258,6 +281,26 @@ public class WebRequestDataBinderTests {
assertTrue("Map size is 0", m.size() == 0);
}
@Test
public void testNoParameters() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
ServletRequestParameterPropertyValues pvs = new ServletRequestParameterPropertyValues(request);
assertTrue("Found no parameters", pvs.getPropertyValues().length == 0);
}
@Test
public void testMultipleValuesForParameter() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
String[] original = new String[] {"Tony", "Rod"};
request.addParameter("forname", original);
ServletRequestParameterPropertyValues pvs = new ServletRequestParameterPropertyValues(request);
assertTrue("Found 1 parameter", pvs.getPropertyValues().length == 1);
assertTrue("Found array value", pvs.getPropertyValue("forname").getValue() instanceof String[]);
String[] values = (String[]) pvs.getPropertyValue("forname").getValue();
assertEquals("Correct values", Arrays.asList(values), Arrays.asList(original));
}
public static class EnumHolder {