Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Uploading multiple files using Spring MVC 3.0.2 after HiddenHttpMethodFilter has been enabled

Important : This question is completely useless for any Spring version higher than 3.0.4 as the issue discussed in this thread had been fixed in that version a long ago and is no longer reproducible in subsequent versions of Spring.


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;

public final 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.


Very little changes to the code in the parseFileItems() method inside the class MultiCommonsMultipartResolver might make it upload multiple files but I couldn't succeed in my attempts (again with the Spring version 3.0.2 (3 or higher)).

like image 684
Tiny Avatar asked Dec 03 '12 15:12

Tiny


1 Answers

For upload multiple files in one request I used this code:

I have such jsp:

<p>Select files to upload. Press Add button to add more file inputs.</p>
<table>
    <tr>
        <td><input name="files" type="file" multiple="true"/></td>
    </tr>
    <tr>
        <td><input name="files" type="file" multiple="true"/></td>
    </tr>
</table>
<br/><input type="submit" value="Upload" />

File upload class bean:

import org.springframework.web.multipart.commons.CommonsMultipartFile;

public class FileUploadForm {

    private CommonsMultipartFile [] files;

    public CommonsMultipartFile[] getFiles() {
        return files;
    }

    public void setFiles( CommonsMultipartFile[] files ) {
        this.files = files;
    }
}

Controller:

@Controller
@RequestMapping("/upload")
public class FileUploadController {

    @RequestMapping(method = RequestMethod.GET)
    public String displayForm(ModelMap modelMap) {
        modelMap.addAttribute( new FileUploadForm() );
        return "uploadForm.jsp";
    }

    @RequestMapping(method = RequestMethod.POST)
    public String save(FileUploadForm uploadForm) {
        CommonsMultipartFile[] files = uploadForm.getFiles();
        if(files != null && files.length != 0) {
            for(MultipartFile file : files) {
                System.out.println( file.getOriginalFilename() );
            }
        }
        return "success.jsp";
    }
}

This code allows to upload multiple files in one request, and be possible to get instance of CommonsMultipartFile for each file.

like image 164
Victor Vorobey Avatar answered Sep 22 '22 21:09

Victor Vorobey