Spring MVC multiple file upload

********Multiple File Upload in Spring MVC applications************
It has been really a hassle implementing the multiple file upload feature in spring application.
Either you need to allow a single file upload or some specified number rather than the number of file uploads as desired by the user.
Here is a fully implemented and tested approach that allows the user to upload the number of files desired.

First we need to have a fileupload.jsp page with the contents as follows:
<html>
    <head>
        <title>Upload a file please</title>
        <script SRC="../js/prototype.js"></script>
        <script type="text/javascript">
         var items=1;
         function AddItem() {
          var div=document.getElementById("items");
          var button=document.getElementById("add");
          items++;
          newitem="<br><br>File:"+ items;
          newitem+="<input type=\"file\" name=\"file";// + items;
          newitem+="\" size=\"30\">";
          newnode=document.createElement("span");
          newnode.innerHTML=newitem;
          div.insertBefore(newnode,button);
         }
        </script>
    </head>
    <body>
        <h1>Please upload a file</h1>
        <form method="post" enctype="multipart/form-data">
            <div ID="items">
            File:1
            <input id="uploadFile" type="file" name="file1" size="30"/>
            <input type="button" value="More" name="more" onclick="AddItem();"/>
            </div>
            <input type="submit" value="Done!"/>
        </form>
    </body>
</html>

this is javascript to add the desired number of file input elements:

<script type="text/javascript">
         var items=1;
         function AddItem() {
          var div=document.getElementById("items");
          var button=document.getElementById("add");
          items++;
          newitem="<br><br>File:"+ items;
          newitem+="<input type=\"file\" name=\"file";// + items;
          newitem+="\" size=\"30\">";
          newnode=document.createElement("span");
          newnode.innerHTML=newitem;
          div.insertBefore(newnode,button);
         }
        </script>

Further more....
   
To allow the user add and delete the required number of <file... elements in the page, the following javascript will work:
  
    <script type="text/javascript">
             var items=1;
             function AddItem() {
              var div=document.getElementById("items");
              var button=document.getElementById("add");
              items++;
              newitem="<br><br>File:"+ items;
              newitem+="<p><input type=\"file\" name=\"file\"";// + items;
              newitem+="\"id=file"+items;
              newitem+="\" size=\"30\">";
              newitem+="<p><input type=\"button\" id=\"delButton"+items;
              newitem+="\" value=\"Delete\" name=\"button"+items;
              newitem+="\" onclick=deletethisRow("+items+")>";
                       newnode=document.createElement("div");
              newnode.setAttribute("id","child"+items);
              newnode.innerHTML=newitem;
              div.insertBefore(newnode,button);
               }
               function deletethisRow(obj){
                var fileElement=document.getElementById("file"+obj);
                var buttonElement=document.getElementById("delButton"+obj);
                var childDivName="child"+obj;
                if (buttonElement) {    
                      var child = document.getElementById(childDivName);
                      var parent = document.getElementById("items");
                      parent.removeChild(child);
                 }

               
                     }

  
    In addition, this dynamically generates the <file element and the delete button along with it. Click on the delete button if you no more want it. Click more button if you need to have more files upload.



in your dispatcher servlet add the following entries:


<!-- for multiple file uploads -->
<bean id="multipartResolver" class="com.d2hawkeye.util.MultiCommonsMultipartResolver"/>



<bean id="fileUploadController" class="com.....web.controller.FileUploadFormController">
    <property name="commandName" value="fileManager"/>
    <property name="formView" value="fileuploadform"/>
    <property name="successView" value="SuccessPage"/>
    <property name="
fileManager" ref="fileManager"/>
    <property name="fileUploadPath" value="uplaodedFiles"/>
</bean>




 
   
   
   




   
   
   
   
   

The successPage is simply a jsp page(successPage.jsp) with success msg.
 The fileuploadform is the jsp page with name fileuploadform.jsp which is listed above.

The fileManager is reference to the Service class that manipulates the uploaded file.


The fileupload controller is listed below:



import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.support.ByteArrayMultipartFileEditor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.SimpleFormController;

import com....api.service.FileManager;

/**
 *
 * @author Ramesh Raj Baral
 * @since:Jan 11, 2010
 * @Version:1.0
 */
public class FileUploadFormController extends SimpleFormController {

    /** Logger for this class and subclasses */
    protected final Log logger = LogFactory.getLog(getClass());
    private FileManager fileManager;
    private String fileUploadPath;//path on server where file is to be uploaded
   
     protected Object formBackingObject(HttpServletRequest request) throws ServletException {
        return productionManager;
    }

    public FileManager get
FileManager() {
        return fileManager;
    }

    public void setFileManager(FileManager
fileManager) {
        this.
fileManager = fileManager;
    }
   
    public String getFileUploadPath() {
        return fileUploadPath;
    }

    public void setFileUploadPath(String filePath) {
        this.fileUploadPath = filePath;
    }
    protected ModelAndView onSubmit(
            HttpServletRequest request,
            HttpServletResponse response,
            Object command,
            BindException errors) throws ServletException, IOException,Exception {
            System.out.println("adding uploaded files"+command.getClass());
            logger.info("Adding the uploaded files"); 
            MultipartHttpServletRequest multiRequest = (MultipartHttpServletRequest) request;
            final Map files = multiRequest.getFileMap();
            logger.info("no of files:"+files.size());
            System.out.println(fileUploadPath);
            if (files == null) {
                  logger.info("no file was sent");
            }
            else{
                String tempDestPath;
                Iterator fileIterator=files.values().iterator();
                MultipartFile file;
                while(fileIterator.hasNext()) {
                    tempDestPath=new File(new File(".").getCanonicalPath()).toString()+System.getProperty("file.separator")+this.fileUploadPath+System.getProperty("file.separator");
                    System.out.println("uploading to:"+tempDestPath);
                    file =(MultipartFile)fileIterator.next();
                    tempDestPath+=file.getOriginalFilename();
                    logger.info("file name:"+file.getOriginalFilename());
                  
                    if(StringUtils.hasLength(file.getOriginalFilename()) &amp;&amp; file.getSize()&gt;0){
                        filesList.add(file);
                    }
                    file.transferTo(new File(tempDestPath));
                    //make an entry if the transfer succeeds
                }

               
               
                 
            }
            return super.onSubmit(request, response, command, errors);

//simply display the success page
        }
  

}



This is simply the controller that is responsible for handling the uploaded files.



The class MultiCommonsMultipartResolver is listed below:




import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
 * inspired from
 * http://dhruba.name/2008/12/27/implementing-single-and-multiple-file-multipart-uploads-using-spring-25/
 * modified by:
 * @author Ramesh Raj Baral
 * @since Jan 11 2010
 */
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.commons.CommonsMultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
public class MultiCommonsMultipartResolver extends CommonsMultipartResolver {

     public MultiCommonsMultipartResolver() {
        }

        public MultiCommonsMultipartResolver(ServletContext servletContext) {
            super(servletContext);
        }

        @Override
        @SuppressWarnings("unchecked")
        protected MultipartParsingResult parseFileItems(List fileItems, String encoding) {
            Map multipartFiles = new HashMap();
            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);
                    logger.info("filename:"+file.getOriginalFilename());
                    /**
                     * if the filename is blank-donot add it to the map
                     * @author Ramesh Raj Baral
                     * @since Jan 11 2010,5:24 pm
                     *
                     */
                    if(StringUtils.hasLength(file.getOriginalFilename())){
                    if (multipartFiles.put(fileItem.getName(), file) != null) {
                        throw new MultipartException("Multiple files for field name [" + file.getName()
                                + "] found - not supported by MultipartResolver");
                    }
                    }
                    else{
                        logger.info("empty or invalid filename submitted:"+file.getOriginalFilename());
                    }
                    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);
        }

    }


The purpose of this class is to override the functionality of  conventional MultipartResolver from commons-fileupload so that you can do multiple uploads.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.


Further queries can be resolved from a helpful link here:
http://dhruba.name/2008/12/27/implementing-single-and-multiple-file-multipart-uploads-using-spring-25/


Ps: this has been tested for Spring 2.5

Thank you,
Ramesh.

4 comments:

  1. sorry frens,
    due to the limitation of blogger, the source is misinterpreted:

    add the jsp file as listed below:
    //javascript code
    var items=1;
    function AddItem() {
    var div.=document.getElementById("items");
    var button.=document.getElementById("add");
    items++;
    newitem="

    File:"+ items;
    newitem+="";
    newnode=document.createElement("span");
    newnode.innerHTML=newitem;
    div.insertBefore(newnode,button);
    }


    body.
    h1.Please upload a file h1.
    form. method="post" enctype="multipart/form-data"
    div. ID="items"
    File:1
    input. id="uploadFile" type="file" name="file1" size="30"
    input. type="button" value="More" name="more" onclick="AddItem();"
    div --closes
    input. type="submit" value="Done!"
    form.
    body
    html.

    add the below snippet in dispatcher servlet:


    bean id="multipartResolver" class="com.d2hawkeye.util.MultiCommonsMultipartResolver"



    bean id="fileUploadController" class="com.....web.controller.FileUploadFormController">
    property name="commandName" value="fileManager"
    property name="formView" value="fileuploadform"
    property name="successView" value="SuccessPage"
    property name="fileManager" ref="fileManager"
    property name="fileUploadPath" value="UploadedFiles"
    --bean

    ReplyDelete
  2. Man, thanks! Really nice post ...but you might want to take a look at this one http://tinyurl.com/3hadznh

    ReplyDelete
  3. Hi Ramesh,

    Which spring version did you try this code? Does it work for Spring 3.0 and above version?

    ReplyDelete
    Replies
    1. Hi Anisha,

      This was tested in Spring 2.5 and hope it works for Spring 3.0 onwards as well.

      Thanks.

      Delete