From 66a799552f9eae321226598714dead2d4d74f15a Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Fri, 28 Aug 2009 09:15:19 +0000 Subject: [PATCH] SPR-2784: Support MultipartFile-array property --- .../util/LinkedMultiValueMap.java | 14 +++- .../springframework/util/MultiValueMap.java | 12 +++ .../web/MockMultipartHttpServletRequest.java | 26 +++++- .../portlet/MockMultipartActionRequest.java | 26 +++++- .../DefaultMultipartActionRequest.java | 35 ++++++-- .../portlet/MockMultipartActionRequest.java | 25 +++++- .../ComplexPortletApplicationContext.java | 7 +- .../web/MockMultipartHttpServletRequest.java | 25 +++++- .../org/springframework/http/HttpHeaders.java | 14 ++++ .../web/multipart/MultipartRequest.java | 25 +++++- .../commons/CommonsFileUploadSupport.java | 16 ++-- .../AbstractMultipartHttpServletRequest.java | 32 +++++-- .../DefaultMultipartHttpServletRequest.java | 4 +- .../CommonsMultipartResolverTests.java | 83 ++++++++++++++----- 14 files changed, 277 insertions(+), 67 deletions(-) diff --git a/org.springframework.core/src/main/java/org/springframework/util/LinkedMultiValueMap.java b/org.springframework.core/src/main/java/org/springframework/util/LinkedMultiValueMap.java index ed1a82436c1..28b3c7ac089 100644 --- a/org.springframework.core/src/main/java/org/springframework/util/LinkedMultiValueMap.java +++ b/org.springframework.core/src/main/java/org/springframework/util/LinkedMultiValueMap.java @@ -64,7 +64,6 @@ public class LinkedMultiValueMap implements MultiValueMap { this.targetMap = new LinkedHashMap>(otherMap); } - // MultiValueMap implementation public void add(K key, V value) { @@ -87,6 +86,19 @@ public class LinkedMultiValueMap implements MultiValueMap { this.targetMap.put(key, values); } + public void setAll(Map values) { + for (Entry entry : values.entrySet()) { + set(entry.getKey(), entry.getValue()); + } + } + + public Map toSingleValueMap() { + LinkedHashMap singleValueMap = new LinkedHashMap(this.targetMap.size()); + for (Entry> entry : targetMap.entrySet()) { + singleValueMap.put(entry.getKey(), entry.getValue().get(0)); + } + return singleValueMap; + } // Map implementation diff --git a/org.springframework.core/src/main/java/org/springframework/util/MultiValueMap.java b/org.springframework.core/src/main/java/org/springframework/util/MultiValueMap.java index d9e1801d05d..46780371494 100644 --- a/org.springframework.core/src/main/java/org/springframework/util/MultiValueMap.java +++ b/org.springframework.core/src/main/java/org/springframework/util/MultiValueMap.java @@ -48,4 +48,16 @@ public interface MultiValueMap extends Map> { */ void set(K key, V value); + /** + * Set the given values under. + * @param values the values. + */ + void setAll(Map values); + + /** + * Returns the first values contained in this {@code MultiValueMap}. + * @return a single value representation of this map + */ + Map toSingleValueMap(); + } diff --git a/org.springframework.test/src/main/java/org/springframework/mock/web/MockMultipartHttpServletRequest.java b/org.springframework.test/src/main/java/org/springframework/mock/web/MockMultipartHttpServletRequest.java index 57b6aadeb50..5c639461618 100644 --- a/org.springframework.test/src/main/java/org/springframework/mock/web/MockMultipartHttpServletRequest.java +++ b/org.springframework.test/src/main/java/org/springframework/mock/web/MockMultipartHttpServletRequest.java @@ -20,8 +20,11 @@ import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; +import java.util.List; import org.springframework.util.Assert; +import org.springframework.util.MultiValueMap; +import org.springframework.util.LinkedMultiValueMap; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartHttpServletRequest; @@ -35,12 +38,13 @@ import org.springframework.web.multipart.MultipartHttpServletRequest; * * @author Juergen Hoeller * @author Eric Crampton + * @author Arjen Poutsma * @since 2.0 * @see MockMultipartFile */ public class MockMultipartHttpServletRequest extends MockHttpServletRequest implements MultipartHttpServletRequest { - private final Map multipartFiles = new LinkedHashMap(); + private final MultiValueMap multipartFiles = new LinkedMultiValueMap(); /** @@ -50,7 +54,7 @@ public class MockMultipartHttpServletRequest extends MockHttpServletRequest impl */ public void addFile(MultipartFile file) { Assert.notNull(file, "MultipartFile must not be null"); - this.multipartFiles.put(file.getName(), file); + this.multipartFiles.add(file.getName(), file); } public Iterator getFileNames() { @@ -58,11 +62,25 @@ public class MockMultipartHttpServletRequest extends MockHttpServletRequest impl } public MultipartFile getFile(String name) { - return this.multipartFiles.get(name); + return this.multipartFiles.getFirst(name); } + public List getFiles(String name) { + List multipartFiles = this.multipartFiles.get(name); + if (multipartFiles != null) { + return multipartFiles; + } + else { + return Collections.emptyList(); + } + } + public Map getFileMap() { - return Collections.unmodifiableMap(this.multipartFiles); + return Collections.unmodifiableMap(this.multipartFiles.toSingleValueMap()); + } + + public MultiValueMap getMultiFileMap() { + return new LinkedMultiValueMap(Collections.unmodifiableMap(this.multipartFiles)); } } diff --git a/org.springframework.test/src/main/java/org/springframework/mock/web/portlet/MockMultipartActionRequest.java b/org.springframework.test/src/main/java/org/springframework/mock/web/portlet/MockMultipartActionRequest.java index 50d095d3e14..97faefa1e12 100644 --- a/org.springframework.test/src/main/java/org/springframework/mock/web/portlet/MockMultipartActionRequest.java +++ b/org.springframework.test/src/main/java/org/springframework/mock/web/portlet/MockMultipartActionRequest.java @@ -20,8 +20,11 @@ import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; +import java.util.List; import org.springframework.util.Assert; +import org.springframework.util.MultiValueMap; +import org.springframework.util.LinkedMultiValueMap; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.portlet.multipart.MultipartActionRequest; @@ -34,12 +37,13 @@ import org.springframework.web.portlet.multipart.MultipartActionRequest; * populate these mock requests with files. * * @author Juergen Hoeller + * @author Arjen Poutsma * @since 2.0 * @see org.springframework.mock.web.MockMultipartFile */ public class MockMultipartActionRequest extends MockActionRequest implements MultipartActionRequest { - private final Map multipartFiles = new LinkedHashMap(); + private final MultiValueMap multipartFiles = new LinkedMultiValueMap(); /** @@ -49,7 +53,7 @@ public class MockMultipartActionRequest extends MockActionRequest implements Mul */ public void addFile(MultipartFile file) { Assert.notNull(file, "MultipartFile must not be null"); - this.multipartFiles.put(file.getName(), file); + this.multipartFiles.add(file.getName(), file); } public Iterator getFileNames() { @@ -57,11 +61,25 @@ public class MockMultipartActionRequest extends MockActionRequest implements Mul } public MultipartFile getFile(String name) { - return this.multipartFiles.get(name); + return this.multipartFiles.getFirst(name); } + public List getFiles(String name) { + List multipartFiles = this.multipartFiles.get(name); + if (multipartFiles != null) { + return multipartFiles; + } + else { + return Collections.emptyList(); + } + } + public Map getFileMap() { - return Collections.unmodifiableMap(this.multipartFiles); + return Collections.unmodifiableMap(this.multipartFiles.toSingleValueMap()); + } + + public MultiValueMap getMultiFileMap() { + return new LinkedMultiValueMap(Collections.unmodifiableMap(this.multipartFiles)); } } diff --git a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/multipart/DefaultMultipartActionRequest.java b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/multipart/DefaultMultipartActionRequest.java index a1de7c049b3..79b81ec7953 100644 --- a/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/multipart/DefaultMultipartActionRequest.java +++ b/org.springframework.web.portlet/src/main/java/org/springframework/web/portlet/multipart/DefaultMultipartActionRequest.java @@ -23,22 +23,26 @@ import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; +import java.util.List; import javax.portlet.ActionRequest; import javax.portlet.filter.ActionRequestWrapper; import org.springframework.web.multipart.MultipartFile; +import org.springframework.util.MultiValueMap; +import org.springframework.util.LinkedMultiValueMap; /** * Default implementation of the {@link MultipartActionRequest} interface. * Provides management of pre-generated parameter values. * * @author Juergen Hoeller + * @author Arjen Poutsma * @since 2.0 * @see PortletMultipartResolver */ public class DefaultMultipartActionRequest extends ActionRequestWrapper implements MultipartActionRequest { - private Map multipartFiles; + private MultiValueMap multipartFiles; private Map multipartParameters; @@ -51,7 +55,7 @@ public class DefaultMultipartActionRequest extends ActionRequestWrapper implemen * with Strings as keys and String arrays as values */ public DefaultMultipartActionRequest( - ActionRequest request, Map mpFiles, Map mpParams) { + ActionRequest request, MultiValueMap mpFiles, Map mpParams) { super(request); setMultipartFiles(mpFiles); @@ -72,13 +76,27 @@ public class DefaultMultipartActionRequest extends ActionRequestWrapper implemen } public MultipartFile getFile(String name) { - return getMultipartFiles().get(name); + return getMultipartFiles().getFirst(name); } + public List getFiles(String name) { + List multipartFiles = getMultipartFiles().get(name); + if (multipartFiles != null) { + return multipartFiles; + } + else { + return Collections.emptyList(); + } + } + + public Map getFileMap() { - return getMultipartFiles(); + return getMultipartFiles().toSingleValueMap(); } + public MultiValueMap getMultiFileMap() { + return getMultipartFiles(); + } @Override public Enumeration getParameterNames() { @@ -120,11 +138,12 @@ public class DefaultMultipartActionRequest extends ActionRequestWrapper implemen /** - * Set a Map with parameter names as keys and MultipartFile objects as values. + * Set a Map with parameter names as keys and list of MultipartFile objects as values. * To be invoked by subclasses on initialization. */ - protected final void setMultipartFiles(Map multipartFiles) { - this.multipartFiles = Collections.unmodifiableMap(multipartFiles); + protected final void setMultipartFiles(MultiValueMap multipartFiles) { + this.multipartFiles = + new LinkedMultiValueMap(Collections.unmodifiableMap(multipartFiles)); } /** @@ -132,7 +151,7 @@ public class DefaultMultipartActionRequest extends ActionRequestWrapper implemen * lazily initializing it if necessary. * @see #initializeMultipart() */ - protected Map getMultipartFiles() { + protected MultiValueMap getMultipartFiles() { if (this.multipartFiles == null) { initializeMultipart(); } diff --git a/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockMultipartActionRequest.java b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockMultipartActionRequest.java index 50d095d3e14..8ab007c0ced 100644 --- a/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockMultipartActionRequest.java +++ b/org.springframework.web.portlet/src/test/java/org/springframework/mock/web/portlet/MockMultipartActionRequest.java @@ -20,8 +20,11 @@ import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; +import java.util.List; import org.springframework.util.Assert; +import org.springframework.util.MultiValueMap; +import org.springframework.util.LinkedMultiValueMap; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.portlet.multipart.MultipartActionRequest; @@ -34,12 +37,13 @@ import org.springframework.web.portlet.multipart.MultipartActionRequest; * populate these mock requests with files. * * @author Juergen Hoeller + * @author Arjen Poutsma * @since 2.0 * @see org.springframework.mock.web.MockMultipartFile */ public class MockMultipartActionRequest extends MockActionRequest implements MultipartActionRequest { - private final Map multipartFiles = new LinkedHashMap(); + private final MultiValueMap multipartFiles = new LinkedMultiValueMap(); /** @@ -49,7 +53,7 @@ public class MockMultipartActionRequest extends MockActionRequest implements Mul */ public void addFile(MultipartFile file) { Assert.notNull(file, "MultipartFile must not be null"); - this.multipartFiles.put(file.getName(), file); + this.multipartFiles.add(file.getName(), file); } public Iterator getFileNames() { @@ -57,11 +61,24 @@ public class MockMultipartActionRequest extends MockActionRequest implements Mul } public MultipartFile getFile(String name) { - return this.multipartFiles.get(name); + return this.multipartFiles.getFirst(name); + } + + public List getFiles(String name) { + List multipartFiles = this.multipartFiles.get(name); + if (multipartFiles != null) { + return multipartFiles; + } + else { + return Collections.emptyList(); + } } public Map getFileMap() { - return Collections.unmodifiableMap(this.multipartFiles); + return Collections.unmodifiableMap(this.multipartFiles.toSingleValueMap()); } + public MultiValueMap getMultiFileMap() { + return new LinkedMultiValueMap(Collections.unmodifiableMap(this.multipartFiles)); + } } diff --git a/org.springframework.web.portlet/src/test/java/org/springframework/web/portlet/ComplexPortletApplicationContext.java b/org.springframework.web.portlet/src/test/java/org/springframework/web/portlet/ComplexPortletApplicationContext.java index a7299bee7ef..1fc911f2333 100644 --- a/org.springframework.web.portlet/src/test/java/org/springframework/web/portlet/ComplexPortletApplicationContext.java +++ b/org.springframework.web.portlet/src/test/java/org/springframework/web/portlet/ComplexPortletApplicationContext.java @@ -67,10 +67,13 @@ import org.springframework.web.portlet.multipart.MultipartActionRequest; import org.springframework.web.portlet.multipart.PortletMultipartResolver; import org.springframework.web.portlet.mvc.Controller; import org.springframework.web.portlet.mvc.SimpleControllerHandlerAdapter; +import org.springframework.util.MultiValueMap; +import org.springframework.util.LinkedMultiValueMap; /** * @author Juergen Hoeller * @author Mark Fisher + * @author Arjen Poutsma */ public class ComplexPortletApplicationContext extends StaticPortletApplicationContext { @@ -472,8 +475,8 @@ public class ComplexPortletApplicationContext extends StaticPortletApplicationCo throw new IllegalStateException("Already resolved"); } request.setAttribute("resolved", Boolean.TRUE); - Map files = new HashMap(); - files.put("someFile", "someFile"); + MultiValueMap files = new LinkedMultiValueMap(); + files.set("someFile", "someFile"); Map params = new HashMap(); params.put("someParam", "someParam"); return new DefaultMultipartActionRequest(request, files, params); diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/mock/web/MockMultipartHttpServletRequest.java b/org.springframework.web.servlet/src/test/java/org/springframework/mock/web/MockMultipartHttpServletRequest.java index a23d23f420b..b4b0f91b949 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/mock/web/MockMultipartHttpServletRequest.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/mock/web/MockMultipartHttpServletRequest.java @@ -20,8 +20,11 @@ import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; +import java.util.List; import org.springframework.util.Assert; +import org.springframework.util.MultiValueMap; +import org.springframework.util.LinkedMultiValueMap; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartHttpServletRequest; @@ -35,12 +38,13 @@ import org.springframework.web.multipart.MultipartHttpServletRequest; * * @author Juergen Hoeller * @author Eric Crampton + * @author Arjen Poutsma * @since 2.0 * @see MockMultipartFile */ public class MockMultipartHttpServletRequest extends MockHttpServletRequest implements MultipartHttpServletRequest { - private final Map multipartFiles = new LinkedHashMap(); + private final MultiValueMap multipartFiles = new LinkedMultiValueMap(); /** @@ -50,7 +54,7 @@ public class MockMultipartHttpServletRequest extends MockHttpServletRequest impl */ public void addFile(MultipartFile file) { Assert.notNull(file, "MultipartFile must not be null"); - this.multipartFiles.put(file.getName(), file); + this.multipartFiles.add(file.getName(), file); } public Iterator getFileNames() { @@ -58,11 +62,24 @@ public class MockMultipartHttpServletRequest extends MockHttpServletRequest impl } public MultipartFile getFile(String name) { - return this.multipartFiles.get(name); + return this.multipartFiles.getFirst(name); } + public List getFiles(String name) { + List multipartFiles = this.multipartFiles.get(name); + if (multipartFiles != null) { + return multipartFiles; + } + else { + return Collections.emptyList(); + } + } + public Map getFileMap() { - return Collections.unmodifiableMap(this.multipartFiles); + return Collections.unmodifiableMap(this.multipartFiles.toSingleValueMap()); } + public MultiValueMap getMultiFileMap() { + return new LinkedMultiValueMap(Collections.unmodifiableMap(this.multipartFiles)); + } } diff --git a/org.springframework.web/src/main/java/org/springframework/http/HttpHeaders.java b/org.springframework.web/src/main/java/org/springframework/http/HttpHeaders.java index ce6022f1579..b53be3ab016 100644 --- a/org.springframework.web/src/main/java/org/springframework/http/HttpHeaders.java +++ b/org.springframework.web/src/main/java/org/springframework/http/HttpHeaders.java @@ -32,6 +32,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.TimeZone; +import java.util.LinkedHashMap; import org.springframework.util.Assert; import org.springframework.util.LinkedCaseInsensitiveMap; @@ -494,6 +495,19 @@ public class HttpHeaders implements MultiValueMap { headers.put(headerName, headerValues); } + public void setAll(Map values) { + for (Entry entry : values.entrySet()) { + set(entry.getKey(), entry.getValue()); + } + } + + public Map toSingleValueMap() { + LinkedHashMap singleValueMap = new LinkedHashMap(this.headers.size()); + for (Entry> entry : headers.entrySet()) { + singleValueMap.put(entry.getKey(), entry.getValue().get(0)); + } + return singleValueMap; + } // Map implementation diff --git a/org.springframework.web/src/main/java/org/springframework/web/multipart/MultipartRequest.java b/org.springframework.web/src/main/java/org/springframework/web/multipart/MultipartRequest.java index d22ce7bde75..683d88d66dc 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/multipart/MultipartRequest.java +++ b/org.springframework.web/src/main/java/org/springframework/web/multipart/MultipartRequest.java @@ -18,6 +18,9 @@ package org.springframework.web.multipart; import java.util.Iterator; import java.util.Map; +import java.util.List; + +import org.springframework.util.MultiValueMap; /** * This interface defines the multipart request access operations @@ -26,6 +29,7 @@ import java.util.Map; * {@link org.springframework.web.portlet.multipart.MultipartActionRequest}. * * @author Juergen Hoeller + * @author Arjen Poutsma * @since 2.5.2 */ public interface MultipartRequest { @@ -43,16 +47,33 @@ public interface MultipartRequest { * Return the contents plus description of an uploaded file in this request, * or null if it does not exist. * @param name a String specifying the parameter name of the multipart file - * @return the uploaded content in the form of a {@link org.springframework.web.multipart.MultipartFile} object + * @return the uploaded content in the form of a {@link MultipartFile} object */ MultipartFile getFile(String name); + /** + * Return the contents plus description of uploaded files in this request, + * or an empty list if it does not exist. + * @param name a String specifying the parameter name of the multipart file + * @return the uploaded content in the form of a {@link MultipartFile} list + */ + List getFiles(String name); + /** * 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 org.springframework.web.multipart.MultipartFile} objects as values + * {@link MultipartFile} objects as values * @see MultipartFile */ Map getFileMap(); + /** + * 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 + */ + MultiValueMap getMultiFileMap(); + + } diff --git a/org.springframework.web/src/main/java/org/springframework/web/multipart/commons/CommonsFileUploadSupport.java b/org.springframework.web/src/main/java/org/springframework/web/multipart/commons/CommonsFileUploadSupport.java index 6ae7ca85246..3d79658bbf1 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/multipart/commons/CommonsFileUploadSupport.java +++ b/org.springframework.web/src/main/java/org/springframework/web/multipart/commons/CommonsFileUploadSupport.java @@ -31,8 +31,9 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.core.io.Resource; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; -import org.springframework.web.multipart.MultipartException; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.util.WebUtils; @@ -213,7 +214,7 @@ public abstract class CommonsFileUploadSupport { * @see CommonsMultipartFile#CommonsMultipartFile(org.apache.commons.fileupload.FileItem) */ protected MultipartParsingResult parseFileItems(List fileItems, String encoding) { - Map multipartFiles = new HashMap(); + MultiValueMap multipartFiles = new LinkedMultiValueMap(); Map multipartParameters = new HashMap(); // Extract multipart files and multipart parameters. @@ -249,10 +250,7 @@ public abstract class CommonsFileUploadSupport { else { // multipart file field CommonsMultipartFile file = new CommonsMultipartFile(fileItem); - if (multipartFiles.put(file.getName(), file) != null) { - throw new MultipartException("Multiple files for field name [" + file.getName() + - "] found - not supported by MultipartResolver"); - } + multipartFiles.add(file.getName(), file); if (logger.isDebugEnabled()) { logger.debug("Found multipart file [" + file.getName() + "] of size " + file.getSize() + " bytes with original filename [" + file.getOriginalFilename() + "], stored " + @@ -290,7 +288,7 @@ public abstract class CommonsFileUploadSupport { */ protected static class MultipartParsingResult { - private final Map multipartFiles; + private final MultiValueMap multipartFiles; private final Map multipartParameters; @@ -299,7 +297,7 @@ public abstract class CommonsFileUploadSupport { * @param mpFiles Map of field name to MultipartFile instance * @param mpParams Map of field name to form field String value */ - public MultipartParsingResult(Map mpFiles, Map mpParams) { + public MultipartParsingResult(MultiValueMap mpFiles, Map mpParams) { this.multipartFiles = mpFiles; this.multipartParameters = mpParams; } @@ -307,7 +305,7 @@ public abstract class CommonsFileUploadSupport { /** * Return the multipart files as Map of field name to MultipartFile instance. */ - public Map getMultipartFiles() { + public MultiValueMap getMultipartFiles() { return this.multipartFiles; } diff --git a/org.springframework.web/src/main/java/org/springframework/web/multipart/support/AbstractMultipartHttpServletRequest.java b/org.springframework.web/src/main/java/org/springframework/web/multipart/support/AbstractMultipartHttpServletRequest.java index cac07d02ba7..e8fa09c9dec 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/multipart/support/AbstractMultipartHttpServletRequest.java +++ b/org.springframework.web/src/main/java/org/springframework/web/multipart/support/AbstractMultipartHttpServletRequest.java @@ -19,24 +19,28 @@ package org.springframework.web.multipart.support; import java.util.Collections; import java.util.Iterator; import java.util.Map; +import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartHttpServletRequest; +import org.springframework.util.MultiValueMap; +import org.springframework.util.LinkedMultiValueMap; /** * Abstract base implementation of the MultipartHttpServletRequest interface. * Provides management of pre-generated MultipartFile instances. * * @author Juergen Hoeller + * @author Arjen Poutsma * @since 06.10.2003 */ public abstract class AbstractMultipartHttpServletRequest extends HttpServletRequestWrapper implements MultipartHttpServletRequest { - private Map multipartFiles; + private MultiValueMap multipartFiles; /** @@ -53,20 +57,34 @@ public abstract class AbstractMultipartHttpServletRequest extends HttpServletReq } public MultipartFile getFile(String name) { - return getMultipartFiles().get(name); + return getMultipartFiles().getFirst(name); + } + + public List getFiles(String name) { + List multipartFiles = getMultipartFiles().get(name); + if (multipartFiles != null) { + return multipartFiles; + } + else { + return Collections.emptyList(); + } } public Map getFileMap() { + return getMultipartFiles().toSingleValueMap(); + } + + public MultiValueMap getMultiFileMap() { return getMultipartFiles(); } - /** - * Set a Map with parameter names as keys and MultipartFile objects as values. + * Set a Map with parameter names as keys and list of MultipartFile objects as values. * To be invoked by subclasses on initialization. */ - protected final void setMultipartFiles(Map multipartFiles) { - this.multipartFiles = Collections.unmodifiableMap(multipartFiles); + protected final void setMultipartFiles(MultiValueMap multipartFiles) { + this.multipartFiles = + new LinkedMultiValueMap(Collections.unmodifiableMap(multipartFiles)); } /** @@ -74,7 +92,7 @@ public abstract class AbstractMultipartHttpServletRequest extends HttpServletReq * lazily initializing it if necessary. * @see #initializeMultipart() */ - protected Map getMultipartFiles() { + protected MultiValueMap getMultipartFiles() { if (this.multipartFiles == null) { initializeMultipart(); } diff --git a/org.springframework.web/src/main/java/org/springframework/web/multipart/support/DefaultMultipartHttpServletRequest.java b/org.springframework.web/src/main/java/org/springframework/web/multipart/support/DefaultMultipartHttpServletRequest.java index 656abf30d15..0a3d4590d9c 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/multipart/support/DefaultMultipartHttpServletRequest.java +++ b/org.springframework.web/src/main/java/org/springframework/web/multipart/support/DefaultMultipartHttpServletRequest.java @@ -25,6 +25,7 @@ import java.util.Set; import javax.servlet.http.HttpServletRequest; import org.springframework.web.multipart.MultipartFile; +import org.springframework.util.MultiValueMap; /** * Default implementation of the @@ -33,6 +34,7 @@ import org.springframework.web.multipart.MultipartFile; * * @author Trevor D. Cook * @author Juergen Hoeller + * @author Arjen Poutsma * @since 29.09.2003 * @see org.springframework.web.multipart.MultipartResolver */ @@ -49,7 +51,7 @@ public class DefaultMultipartHttpServletRequest extends AbstractMultipartHttpSer * with Strings as keys and String arrays as values */ public DefaultMultipartHttpServletRequest( - HttpServletRequest request, Map mpFiles, Map mpParams) { + HttpServletRequest request, MultiValueMap mpFiles, Map mpParams) { super(request); setMultipartFiles(mpFiles); diff --git a/org.springframework.web/src/test/java/org/springframework/web/multipart/commons/CommonsMultipartResolverTests.java b/org.springframework.web/src/test/java/org/springframework/web/multipart/commons/CommonsMultipartResolverTests.java index 6a4a65ac810..4ddd02e285b 100644 --- a/org.springframework.web/src/test/java/org/springframework/web/multipart/commons/CommonsMultipartResolverTests.java +++ b/org.springframework.web/src/test/java/org/springframework/web/multipart/commons/CommonsMultipartResolverTests.java @@ -30,7 +30,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; - import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; @@ -38,12 +37,13 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import junit.framework.TestCase; - import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.FileItemFactory; import org.apache.commons.fileupload.FileUpload; import org.apache.commons.fileupload.servlet.ServletFileUpload; +import static org.junit.Assert.*; +import org.junit.Test; + import org.springframework.beans.MutablePropertyValues; import org.springframework.mock.web.MockFilterConfig; import org.springframework.mock.web.MockHttpServletRequest; @@ -59,18 +59,22 @@ import org.springframework.web.multipart.support.ByteArrayMultipartFileEditor; import org.springframework.web.multipart.support.MultipartFilter; import org.springframework.web.multipart.support.StringMultipartFileEditor; import org.springframework.web.util.WebUtils; +import org.springframework.util.MultiValueMap; /** * @author Juergen Hoeller + * @author Arjen Poutsma * @since 08.10.2003 */ -public class CommonsMultipartResolverTests extends TestCase { +public class CommonsMultipartResolverTests { - public void testWithApplicationContext() throws Exception { + @Test + public void withApplicationContext() throws Exception { doTestWithApplicationContext(false); } - public void testWithApplicationContextAndLazyResolution() throws Exception { + @Test + public void withApplicationContextAndLazyResolution() throws Exception { doTestWithApplicationContext(true); } @@ -100,10 +104,18 @@ public class CommonsMultipartResolverTests extends TestCase { assertTrue(resolver.isMultipart(originalRequest)); MultipartHttpServletRequest request = resolver.resolveMultipart(originalRequest); - Set parameterNames = new HashSet(); + doTestParameters(request); + + doTestFiles(request); + + doTestBinding(resolver, originalRequest, request); + } + + private void doTestParameters(MultipartHttpServletRequest request) { + Set parameterNames = new HashSet(); Enumeration parameterEnum = request.getParameterNames(); while (parameterEnum.hasMoreElements()) { - parameterNames.add(parameterEnum.nextElement()); + parameterNames.add((String) parameterEnum.nextElement()); } assertEquals(3, parameterNames.size()); assertTrue(parameterNames.contains("field3")); @@ -121,10 +133,10 @@ public class CommonsMultipartResolverTests extends TestCase { assertEquals("value4", request.getParameter("field4")); assertEquals("getValue", request.getParameter("getField")); - List parameterMapKeys = new ArrayList(); - List parameterMapValues = new ArrayList(); - for (Iterator parameterMapIter = request.getParameterMap().keySet().iterator(); parameterMapIter.hasNext();) { - String key = (String) parameterMapIter.next(); + List parameterMapKeys = new ArrayList(); + List parameterMapValues = new ArrayList(); + for (Object o : request.getParameterMap().keySet()) { + String key = (String) o; parameterMapKeys.add(key); parameterMapValues.add(request.getParameterMap().get(key)); } @@ -146,11 +158,13 @@ public class CommonsMultipartResolverTests extends TestCase { parameterValues = Arrays.asList((String[]) parameterMapValues.get(getFieldIndex)); assertEquals(1, parameterValues.size()); assertTrue(parameterValues.contains("getValue")); + } - Set fileNames = new HashSet(); + private void doTestFiles(MultipartHttpServletRequest request) throws IOException { + Set fileNames = new HashSet(); Iterator fileIter = request.getFileNames(); while (fileIter.hasNext()) { - fileNames.add(fileIter.next()); + fileNames.add((String) fileIter.next()); } assertEquals(3, fileNames.size()); assertTrue(fileNames.contains("field1")); @@ -159,7 +173,8 @@ public class CommonsMultipartResolverTests extends TestCase { CommonsMultipartFile file1 = (CommonsMultipartFile) request.getFile("field1"); CommonsMultipartFile file2 = (CommonsMultipartFile) request.getFile("field2"); CommonsMultipartFile file2x = (CommonsMultipartFile) request.getFile("field2x"); - Map fileMap = request.getFileMap(); + + Map fileMap = request.getFileMap(); assertEquals(3, fileMap.size()); assertTrue(fileMap.containsKey("field1")); assertTrue(fileMap.containsKey("field2")); @@ -168,6 +183,18 @@ public class CommonsMultipartResolverTests extends TestCase { assertEquals(file2, fileMap.get("field2")); assertEquals(file2x, fileMap.get("field2x")); + MultiValueMap multiFileMap = request.getMultiFileMap(); + assertEquals(3, multiFileMap.size()); + assertTrue(multiFileMap.containsKey("field1")); + assertTrue(multiFileMap.containsKey("field2")); + assertTrue(multiFileMap.containsKey("field2x")); + List field1Files = multiFileMap.get("field1"); + assertEquals(2, field1Files.size()); + assertTrue(field1Files.contains(file1)); + assertEquals(file1, multiFileMap.getFirst("field1")); + assertEquals(file2, multiFileMap.getFirst("field2")); + assertEquals(file2x, multiFileMap.getFirst("field2x")); + assertEquals("type1", file1.getContentType()); assertEquals("type2", file2.getContentType()); assertEquals("type2", file2x.getContentType()); @@ -181,18 +208,24 @@ public class CommonsMultipartResolverTests extends TestCase { assertTrue(file1.getInputStream() instanceof ByteArrayInputStream); assertTrue(file2.getInputStream() instanceof ByteArrayInputStream); File transfer1 = new File("C:/transfer1"); - File transfer2 = new File("C:/transfer2"); file1.transferTo(transfer1); + File transfer2 = new File("C:/transfer2"); file2.transferTo(transfer2); assertEquals(transfer1, ((MockFileItem) file1.getFileItem()).writtenFile); assertEquals(transfer2, ((MockFileItem) file2.getFileItem()).writtenFile); + } + + private void doTestBinding(MockCommonsMultipartResolver resolver, MockHttpServletRequest originalRequest, + MultipartHttpServletRequest request) throws UnsupportedEncodingException { MultipartTestBean1 mtb1 = new MultipartTestBean1(); assertEquals(null, mtb1.getField1()); assertEquals(null, mtb1.getField2()); ServletRequestDataBinder binder = new ServletRequestDataBinder(mtb1, "mybean"); binder.registerCustomEditor(byte[].class, new ByteArrayMultipartFileEditor()); binder.bind(request); + CommonsMultipartFile file1 = (CommonsMultipartFile) request.getFile("field1"); + CommonsMultipartFile file2 = (CommonsMultipartFile) request.getFile("field2"); assertEquals(file1, mtb1.getField1()); assertEquals(new String(file2.getBytes()), new String(mtb1.getField2())); @@ -224,7 +257,8 @@ public class CommonsMultipartResolverTests extends TestCase { assertTrue(mtb2.getField1().length() == 0); } - public void testWithServletContextAndFilter() throws Exception { + @Test + public void withServletContextAndFilter() throws Exception { StaticWebApplicationContext wac = new StaticWebApplicationContext(); wac.setServletContext(new MockServletContext()); wac.registerSingleton("filterMultipartResolver", MockCommonsMultipartResolver.class, new MutablePropertyValues()); @@ -240,7 +274,7 @@ public class CommonsMultipartResolverTests extends TestCase { final MultipartFilter filter = new MultipartFilter(); filter.init(filterConfig); - final List files = new ArrayList(); + final List files = new ArrayList(); final FilterChain filterChain = new FilterChain() { public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse) { MultipartHttpServletRequest request = (MultipartHttpServletRequest) servletRequest; @@ -263,7 +297,8 @@ public class CommonsMultipartResolverTests extends TestCase { assertTrue(((MockFileItem) file2.getFileItem()).deleted); } - public void testWithServletContextAndFilterWithCustomBeanName() throws Exception { + @Test + public void withServletContextAndFilterWithCustomBeanName() throws Exception { StaticWebApplicationContext wac = new StaticWebApplicationContext(); wac.setServletContext(new MockServletContext()); wac.refresh(); @@ -276,7 +311,7 @@ public class CommonsMultipartResolverTests extends TestCase { MockFilterConfig filterConfig = new MockFilterConfig(wac.getServletContext(), "filter"); filterConfig.addInitParameter("multipartResolverBeanName", "myMultipartResolver"); - final List files = new ArrayList(); + final List files = new ArrayList(); FilterChain filterChain = new FilterChain() { public void doFilter(ServletRequest originalRequest, ServletResponse response) { if (originalRequest instanceof MultipartHttpServletRequest) { @@ -288,6 +323,7 @@ public class CommonsMultipartResolverTests extends TestCase { MultipartFilter filter = new MultipartFilter() { private boolean invoked = false; + @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { super.doFilterInternal(request, response, filterChain); @@ -321,15 +357,19 @@ public class CommonsMultipartResolverTests extends TestCase { this.empty = empty; } + @Override protected FileUpload newFileUpload(FileItemFactory fileItemFactory) { return new ServletFileUpload() { + @Override public List parseRequest(HttpServletRequest request) { if (request instanceof MultipartHttpServletRequest) { throw new IllegalStateException("Already a multipart request"); } - List fileItems = new ArrayList(); + List fileItems = new ArrayList(); MockFileItem fileItem1 = new MockFileItem( "field1", "type1", empty ? "" : "field1.txt", empty ? "" : "text1"); + MockFileItem fileItem1x = new MockFileItem( + "field1", "type1", empty ? "" : "field1.txt", empty ? "" : "text1"); MockFileItem fileItem2 = new MockFileItem( "field2", "type2", empty ? "" : "C:/field2.txt", empty ? "" : "text2"); MockFileItem fileItem2x = new MockFileItem( @@ -338,6 +378,7 @@ public class CommonsMultipartResolverTests extends TestCase { MockFileItem fileItem4 = new MockFileItem("field4", null, null, "value4"); MockFileItem fileItem5 = new MockFileItem("field4", null, null, "value5"); fileItems.add(fileItem1); + fileItems.add(fileItem1x); fileItems.add(fileItem2); fileItems.add(fileItem2x); fileItems.add(fileItem3);