Uploading multiple files using Spring MVC 3.0.2 after HiddenHttpMethodFilter has been enabled
- by Tiny
I'm using Spring version 3.0.2. I need to upload multiple files using the multiple="multiple" attribute of a file browser such as,
<input type="file" id="myFile" name="myFile" multiple="multiple"/>
(and not using multiple file browsers something like the one stated by this answer, it indeed works I tried).
Although no versions of Internet Explorer supports this approach unless an appropriate jQuery plugin/widget is used, I don't care about it right now (since most other browsers support this).
This works fine with commons fileupload but in addition to using RequestMethod.POST and RequestMethod.GET methods, I also want to use other request methods supported and suggested by Spring like RequestMethod.PUT and RequestMethod.DELETE in their own appropriate places. For this to be so, I have configured Spring with HiddenHttpMethodFilter which goes fine as this question indicates.
but it can upload only one file at a time even though multiple files in the file browser are chosen. In the Spring controller class, a method is mapped as follows.
@RequestMapping(method={RequestMethod.POST}, value={"admin_side/Temp"})
public String onSubmit(@RequestParam("myFile") List<MultipartFile> files, @ModelAttribute("tempBean") TempBean tempBean, BindingResult error, Map model, HttpServletRequest request, HttpServletResponse response) throws IOException, FileUploadException
{
for(MultipartFile file:files)
{
System.out.println(file.getOriginalFilename());
}
}
Even with the request parameter @RequestParam("myFile") List<MultipartFile> files which is a List of type MultipartFile (it can always have only one file at a time).
I could find a strategy which is likely to work with multiple files on this blog. I have gone through it carefully.
The solution below the section SOLUTION 2 – USE THE RAW REQUEST says,
If however the client insists on using the same form input name such
as ‘files[]‘ or ‘files’ and then populating that name with multiple
files then a small hack is necessary as follows. As noted above Spring
2.5 throws an exception if it detects the same form input name of type file more than once. CommonsFileUploadSupport – the class which throws
that exception is not final and the method which throws that exception
is protected so using the wonders of inheritance and subclassing one
can simply fix/modify the logic a little bit as follows. The change
I’ve made is literally one word representing one method invocation
which enables us to have multiple files incoming under the same form
input name.
It attempts to override the method
protected MultipartParsingResult parseFileItems(List fileItems, String encoding)
{}
of the abstract class CommonsFileUploadSupport by extending the class CommonsMultipartResolver such as,
package multipartResolver;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletContext;
import org.apache.commons.fileupload.FileItem;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
final public class MultiCommonsMultipartResolver extends CommonsMultipartResolver
{
public MultiCommonsMultipartResolver()
{
}
public MultiCommonsMultipartResolver(ServletContext servletContext)
{
super(servletContext);
}
@Override
@SuppressWarnings("unchecked")
protected MultipartParsingResult parseFileItems(List fileItems, String encoding)
{
Map<String, MultipartFile> multipartFiles = new HashMap<String, MultipartFile>();
Map multipartParameters = new HashMap();
// Extract multipart files and multipart parameters.
for (Iterator it = fileItems.iterator(); it.hasNext();)
{
FileItem fileItem = (FileItem) it.next();
if (fileItem.isFormField())
{
String value = null;
if (encoding != null)
{
try
{
value = fileItem.getString(encoding);
}
catch (UnsupportedEncodingException ex)
{
if (logger.isWarnEnabled())
{
logger.warn("Could not decode multipart item '" + fileItem.getFieldName()
+ "' with encoding '" + encoding + "': using platform default");
}
value = fileItem.getString();
}
}
else
{
value = fileItem.getString();
}
String[] curParam = (String[]) multipartParameters.get(fileItem.getFieldName());
if (curParam == null)
{
// simple form field
multipartParameters.put(fileItem.getFieldName(), new String[] { value });
}
else
{
// array of simple form fields
String[] newParam = StringUtils.addStringToArray(curParam, value);
multipartParameters.put(fileItem.getFieldName(), newParam);
}
}
else
{
// multipart file field
CommonsMultipartFile file = new CommonsMultipartFile(fileItem);
if (multipartFiles.put(fileItem.getName(), file) != null)
{
throw new MultipartException("Multiple files for field name [" + file.getName()
+ "] found - not supported by MultipartResolver");
}
if (logger.isDebugEnabled())
{
logger.debug("Found multipart file [" + file.getName() + "] of size " + file.getSize()
+ " bytes with original filename [" + file.getOriginalFilename() + "], stored "
+ file.getStorageDescription());
}
}
}
return new MultipartParsingResult(multipartFiles, multipartParameters);
}
}
What happens is that the last line in the method parseFileItems() (the return statement) i.e.
return new MultipartParsingResult(multipartFiles, multipartParameters);
causes a compile-time error because the first parameter multipartFiles is a type of Map implemented by HashMap but in reality, it requires a parameter of type MultiValueMap<String, MultipartFile>
It is a constructor of a static class inside the abstract class CommonsFileUploadSupport,
public abstract class CommonsFileUploadSupport
{
protected static class MultipartParsingResult
{
public MultipartParsingResult(MultiValueMap<String, MultipartFile> mpFiles, Map<String, String[]> mpParams)
{
}
}
}
The reason might be - this solution is about the Spring version 2.5 and I'm using the Spring version 3.0.2 which might be inappropriate for this version.
I however tried to replace the Map with MultiValueMap in various ways such as the one shown in the following segment of code,
MultiValueMap<String, MultipartFile>mul=new LinkedMultiValueMap<String, MultipartFile>();
for(Entry<String, MultipartFile>entry:multipartFiles.entrySet())
{
mul.add(entry.getKey(), entry.getValue());
}
return new MultipartParsingResult(mul, multipartParameters);
but no success. I'm not sure how to replace Map with MultiValueMap and even doing so could work either. After doing this, the browser shows the Http response,
HTTP Status 400 -
type Status report
message
description The request sent by the client was syntactically incorrect
().
Apache Tomcat/6.0.26
I have tried to shorten the question as possible as I could and I haven't included unnecessary code.
How could be made it possible to upload multiple files after Spring has been configured with HiddenHttpMethodFilter?
That blog indicates that It is a long standing, high priority bug.
If there is no solution regarding the version 3.0.2 (3 or higher) then I have to disable Spring support forever and continue to use commons-fileupolad as suggested by the third solution on that blog omitting the PUT, DELETE and other request methods forever.
Just curiously waiting for a solution and/or suggestion.
Very little changes to the code in the parseFileItems() method inside the class MultiCommonsMultipartResolver might make it to upload multiple files but I couldn't succeed in my attempts (again with the Spring version 3.0.2 (3 or higher)).