Question

I have seen that we can upload multiple files by flash file upload. Like SWFUpload or YUI Uploader. Is it possible to integrate these upload component with JSF?

What I want is to choose multiple file at once. Primefaces file uploader has this feature, but that don't work in IE7 as IE7 don't have any support for HTML5.

I need to create a Form with various fields, like dropdown menu, text input etc and also need to add a file uploader for choosing multiple file. When the JSF submit button will be clicked the Form will be validated and it will proceed after.

I have created a page for uploading multiple file, but that page use multiple input file component for multiple file.

Any reference would be very helpful for me. I have found another SO thread and the solution posted there use JSP. I cannot understand how can I use this to fulfill my requirement.

Update

I have created the following managed bean:

import com.mhis.massupload.ucm.Service;

import java.util.List;
import java.util.UUID;
import java.util.logging.Logger;

import javax.faces.context.FacesContext;

import org.apache.commons.fileupload.FileItem;


public class UploadBean { 
    private Logger log = Logger.getLogger(getClass().getName());
    private Service service;
    private String key;

    public UploadBean() {
        super();
        log.info("JYM");
        init();
    }

    private void init() {
        key = UUID.randomUUID().toString();
    }

    public String upload() {        
        System.out.println("Action Invoked.");
        List<FileItem> fileFields = (List<FileItem>)FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get(key);
        System.out.println(fileFields);

        return "";
    }

    public void setKey(String key) {
        this.key = key;
    }

    public String getKey() {
        return key;
    }
}

And the Servlet is:

import java.io.IOException;

import java.util.ArrayList;
import java.util.List;

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

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;


public class UploadServlet extends HttpServlet {

    @SuppressWarnings("compatibility:-3560436533054762606")
    private static final long serialVersionUID = 4122845079663279030L;

    public void init(ServletConfig config) throws ServletException {
        super.init(config);
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("UploadServlet invoked.");        
        List<FileItem> fileFields = new ArrayList<FileItem>();

        try {
            List<FileItem> items = new ServletFileUpload(new DiskFileItemFactory()).parseRequest(request);
            for (FileItem item : items) {
                if (!item.isFormField()) {
                    fileFields.add(item);
                    System.out.println(item.getName());
                }
            }
        } catch (Exception e) {
            throw new ServletException(e);
        }

        String key = request.getParameter("key");
        request.getSession().setAttribute(key, fileFields);
    }
}

The jspx page:

<?xml version='1.0' encoding='utf-8'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1"
          xmlns:f="http://java.sun.com/jsf/core"
          xmlns:h="http://java.sun.com/jsf/html">
  <jsp:output omit-xml-declaration="true" doctype-root-element="HTML"
              doctype-system="http://www.w3.org/TR/html4/loose.dtd"
              doctype-public="-//W3C//DTD HTML 4.01 Transitional//EN"/>
  <jsp:directive.page contentType="text/html;utf-8"/>
  <f:view>
    <html>
      <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <script src="js/jquery-1.8.3.min.js" type="text/javascript"></script>        
        <script src="uploadify/jquery.uploadify.js" type="text/javascript"></script>
        <link rel="stylesheet" media="screen" href="uploadify/uploadify.css"
              type="text/css"/>
        <script type="text/javascript">
            $(document).ready(function() {
                $('#uploadify').uploadify({
                    'swf': 'uploadify/uploadify.swf',
                    'uploader': '${pageContext.request.contextPath}/uploadServlet;jsessionid=${pageContext.session.id}?key=<h:outputText value="#{uploadBean.key}" />'                   
                });                                         
            });    

            var upload = function() {
                $('#uploadify').uploadify('upload','*');
            }
        </script>
        <title>test</title>
      </head>
      <body>
        <h:form enctype="multipart/form-data">
            <input id="uploadify" type="file"/>            
            <h:commandLink action="#{uploadBean.upload}" value="Upload" />
        </h:form>
      </body>
    </html>
  </f:view>
</jsp:root>

I am using Uploadify here.

I am having two issues:

  • The List<FileItem> fileFields of the upload method is sometime returning null, sometime showing the list. I am unable to find the reason. I have tried to get the HttpSession from the action method by FacesContext.getCurrentInstance().getExternalContext().getSession(false) and then called getAttribute() upon it, that is also always returning null.

  • If I set 'auto': false, that is the file upload will launch after calling upload(); method and modify the <h:commandLink/> as: <h:commandLink action="#{uploadBean.upload}" value="Upload" onclick="upload();"/> then I am getting exception, which is:

    org.apache.commons.fileupload.FileUploadBase$IOFileUploadException: Processing of multipart/form-data request failed. EOF after reading only: '2392369' of: '11626364' promised bytes, out of which at least: '0' were already buffered
    at org.apache.commons.fileupload.FileUploadBase.parseRequest(FileUploadBase.java:367)
    at org.apache.commons.fileupload.servlet.ServletFileUpload.parseRequest(ServletFileUpload.java:126)
    at com.edfx.massupload.servlet.UploadServlet.doPost(UploadServlet.java:33)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
    at weblogic.servlet.internal.StubSecurityHelper$ServletServiceAction.run(StubSecurityHelper.java:227)
    at weblogic.servlet.internal.StubSecurityHelper.invokeServlet(StubSecurityHelper.java:125)
    at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:300)
    at weblogic.servlet.internal.TailFilter.doFilter(TailFilter.java:26)
    at weblogic.servlet.internal.FilterChainImpl.doFilter(FilterChainImpl.java:56)
    at oracle.security.jps.ee.http.JpsAbsFilter$1.run(JpsAbsFilter.java:119)
    at java.security.AccessController.doPrivileged(Native Method)
    at oracle.security.jps.util.JpsSubject.doAsPrivileged(JpsSubject.java:315)
    at oracle.security.jps.ee.util.JpsPlatformUtil.runJaasMode(JpsPlatformUtil.java:442)
    at oracle.security.jps.ee.http.JpsAbsFilter.runJaasMode(JpsAbsFilter.java:103)
    at oracle.security.jps.ee.http.JpsAbsFilter.doFilter(JpsAbsFilter.java:171)
    at oracle.security.jps.ee.http.JpsFilter.doFilter(JpsFilter.java:71)
    at weblogic.servlet.internal.FilterChainImpl.doFilter(FilterChainImpl.java:56)
    at oracle.dms.servlet.DMSServletFilter.doFilter(DMSServletFilter.java:139)
    at weblogic.servlet.internal.FilterChainImpl.doFilter(FilterChainImpl.java:56)
    at weblogic.servlet.internal.RequestEventsFilter.doFilter(RequestEventsFilter.java:27)
    at weblogic.servlet.internal.FilterChainImpl.doFilter(FilterChainImpl.java:56)
    at weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction.wrapRun(WebAppServletContext.java:3715)
    at weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction.run(WebAppServletContext.java:3681)
    at weblogic.security.acl.internal.AuthenticatedSubject.doAs(AuthenticatedSubject.java:321)
    at weblogic.security.service.SecurityManager.runAs(SecurityManager.java:120)
    at weblogic.servlet.internal.WebAppServletContext.securedExecute(WebAppServletContext.java:2277)
    at weblogic.servlet.internal.WebAppServletContext.execute(WebAppServletContext.java:2183)
    at weblogic.servlet.internal.ServletRequestImpl.run(ServletRequestImpl.java:1454)
    at weblogic.work.ExecuteThread.execute(ExecuteThread.java:209)
    at weblogic.work.ExecuteThread.run(ExecuteThread.java:178)
    

And also in this case the action method is executing before the servlet get executed.

How can I solve these two issues?

PS

I need to modify the uploadify.js to set the correct path of the swf file and changed the css for the cancel-button. I have placed the whole directory of the uploadify inside Web-Content.

Was it helpful?

Solution 2

You're going to need a servlet for this. I've answered a Uploadify + Servlet question before which should give sufficient clues: Uploadify plugin doesn't call Java Servlet. The concept is not that different for SWFUpload and YUI Uploader. Note that you can just put plain HTML in a JSF page without problems.

Your main problem is enabling the JSF backing bean and the HTTP servlet to exchange data with each other. For this the HTTP session should be used. First let the JSF bean on initial request generate an unique key which is to be used as session attribute name:

key = UUID.randomUUID().toString();

Then, in the upload URL which you tell Uploadify/SWFUpload/etc to use, you need to include the jsessionid URL fragment (so that the servlet will use the same HTTP session) and the keyas URL query string parameter (so that the servlet will use this key to store relevant state about the file upload). Assuming that you need to specify it as a JS object key:

url: '${pageContext.request.contextPath}/uploadServlet;jsessionid=${pageContext.session.id}?key=<h:outputText value="#{uploadBean.key}" />'

Now, the servlet will be invoked within the same HTTP session (i.e. request.getSession() will be exactly the same one as available in JSF by ExternalContext#getSession() and its attributes by getSessionMap()). So, in doPost() just do:

String key = request.getParameter("key");
request.getSession().setAttribute(key, someStateAboutTheUpload);

Finally, when JSF is about to process the form submit, just get the desired data by the key (which you store for the subsequent request by <input type="hidden"> or <t:saveState> in case of a request scoped bean):

SomeState someState = (SomeState) externalContext.getSessionMap().get(key);

The unique key is not necessary for the functioning, it can even be a static key, but a on per-view basis generated key ensures that the data doesn't clash when the enduser has the same view open in multiple browser tabs/windows in the same session.

OTHER TIPS

Here is the solution that I have made, BalusC helped me a lot to developed it.

Platform and Framework

  • Uploadify v3.2
  • JSF 1.2
  • jQuery 1.8.3
  • WebLogic 10.3.5.0
  • Apache Commons Fileupload 1.2.2

Problem with multipart request

JSF 1.2 is cannot handle the multipart request. So if the <h:form/> contains enctype="multipart/form-data" as attribute then the action method of command component like <h:commandButton/> will not fire. The solution is to create a Filter which will work together with Faces Servlet and handle the mutipart request explicitly. Here is the filter:

package com.mhis.massupload.filter;

import java.io.IOException;

import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;


public class MultipartFilter implements Filter {
    private FilterConfig filterConfig = null;

    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;
    }

    public void destroy() {
        filterConfig = null;
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (!(request instanceof HttpServletRequest)) {
            chain.doFilter(request, response);
            return;
        }

        HttpServletRequest httpRequest = (HttpServletRequest)request;        
        boolean isMultipartContent = ServletFileUpload.isMultipartContent(httpRequest);

        if (!isMultipartContent) {
            chain.doFilter(request, response);
            return;
        }

        try {
            DiskFileItemFactory factory = new DiskFileItemFactory();
            ServletFileUpload upload = new ServletFileUpload(factory);
            upload.setHeaderEncoding("UTF-8");
            upload.setSizeMax(-1);

            List<FileItem> items = upload.parseRequest(httpRequest);
            final Map<String, String[]> parameterMap = new HashMap<String, String[]>();

            for (FileItem item : items) {
                if (item.isFormField()) {
                    processFormField(item, parameterMap);
                } 
            }

            chain.doFilter(new HttpServletRequestWrapper(httpRequest) {

                    public Map<String, String[]> getParameterMap() {
                        return parameterMap;
                    }

                    public String[] getParameterValues(String name) {
                        return (String[])parameterMap.get(name);
                    }

                    public String getParameter(String name) {
                        String[] params = getParameterValues(name);
                        if (params == null) {
                            return null;
                        }
                        return params[0];
                    }

                    public Enumeration<String> getParameterNames() {
                        return Collections.enumeration(parameterMap.keySet());
                    }
                }, response);
        } catch (Exception ex) {
            ServletException servletException = new ServletException();
            servletException.initCause(ex);
            throw servletException;
        }
    }

    private void processFormField(FileItem formField, Map<String, String[]> parameterMap) {
        String name = formField.getFieldName();
        String value = formField.getString();
        String[] values = parameterMap.get(name);

        if (values == null) {
            parameterMap.put(name, new String[] { value });
        } else {
            int length = values.length;
            String[] newValues = new String[length + 1];
            System.arraycopy(values, 0, newValues, 0, length);
            newValues[length] = value;
            parameterMap.put(name, newValues);
        }
    }
}

The configuration of this filter in the web.xml is:

<filter>
    <filter-name>MultipartFilter</filter-name>
    <filter-class>com.mhis.massupload.filter.MultipartFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>MultipartFilter</filter-name>
    <servlet-name>Faces Servlet</servlet-name>
</filter-mapping>

Javasctipt for Uploadify

$('#uploadify').uploadify({
    'auto': false,
    'buttonText' : 'Browse',
    'fileSizeLimit': 0,
    'swf': 'uploadify/uploadify.swf',
    'uploader': '${pageContext.request.contextPath}/uploadservlet?key=<h:outputText value="#{uploadBean.key}" />',
    'onQueueComplete' : function(queueData) {
        $('.checkIn').click();
    } 
});

In my requirement I need to upload all the files when the submit button of the form has been clicked not at then time when the files are added in the queue. That is why I have set 'auto': false. The uploadify folder has been put inside the Web-Content of my project and for that this plugin was unable to find the uploadify.swf file and the image of the cancel button. I have to modify the jquery.uploadify.js's line no 99 and I have changed it as swf: 'uploadify/uploadify.swf' and also the uploadify.css's line no 74 and I have changed it to:

.uploadify-queue-item .cancel a {
    background: url('../uploadify/uploadify-cancel.png') 0 0 no-repeat;
    float: right;
    height: 16px;
    text-indent: -9999px;
    width: 16px;
}

The background was set to url('../img/uploadify-cancel.png') 0 0 no-repeat;, but I don't have img folder.

Servlet

For the uploading the files I have used a Servlet, viz UplaodServlet; the configuration of this servlet is:

<servlet>
    <servlet-name>UploadServlet</servlet-name>
    <servlet-class>com.mhis.massupload.servlet.UploadServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>UploadServlet</servlet-name>
    <url-pattern>/uploadservlet</url-pattern>
</servlet-mapping>

The uploadservlet has been used as the uploader attribute of the uploadify's config. I need also to pass a unique key as request parameter of the Servlet. The code for the Servlet is:

package com.mhis.massupload.servlet;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

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

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;


public class UploadServlet extends HttpServlet {

    @SuppressWarnings("compatibility:-6472602315203858426")
    private static final long serialVersionUID = -3714619333861571457L;
    private transient Logger log = Logger.getLogger(getClass().getName());

    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
    }

    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException {
        boolean isMultipartContent = ServletFileUpload.isMultipartContent(request);

        if (!isMultipartContent) {
            return;
        }

        FileItem fileField = null;
        try {
            DiskFileItemFactory factory = new DiskFileItemFactory();
            ServletFileUpload upload = new ServletFileUpload(factory);
            upload.setHeaderEncoding("UTF-8");
            upload.setSizeMax(-1);

            List<FileItem> items = upload.parseRequest(request);
            final Map<String, String[]> parameterMap = new HashMap<String, String[]>();


            for (FileItem item : items) {
                if (!item.isFormField()) {
                    fileField = item;
                }
            }
        } catch (Exception ex) {
            log.severe(ex.getMessage());
        }

        if (fileField == null) {
            return;
        }

        String key = request.getParameter("key");

        List<FileItem> fileFields = (List<FileItem>)getServletContext().getAttribute(key);

        if (fileFields == null) {
            fileFields = new ArrayList<FileItem>();
            getServletContext().setAttribute(key, fileFields);
        }

        fileFields.add(fileField);
    }
}

I am unable to use Session to put the information about the uploaded file, so I have used ServletContext instead. For more info refer here.

JSF page and Upload button

As my need is to upload the files only when the form is submitted after the validation, so I have set 'auto': false in the configuration of uploadify. But that gave me a trouble, I have posted the issue in my original question. To solve the issue I have declared three input[type=button]. two of the are ordinary HTML button and the last one is <h:commandButton/>. I have set the visibility of this <h:commandButton/> to false and have used a dummy button which will initiate the file upload. When the upload finishes I have programmatically generated the click of ``. Also I have shown a dummy button which don't have any click event associated with it; this the fail safe if, someone clicks on the Upload button twice during the upload is taking place then the the aforementioned click event fire will work unexpectedly. That why I am showing and hiding the buttons. Here is the completed.jspx` pag:

<?xml version='1.0' encoding='utf-8'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1"
          xmlns:f="http://java.sun.com/jsf/core"
          xmlns:h="http://java.sun.com/jsf/html">
    <jsp:output omit-xml-declaration="true" doctype-root-element="HTML"
    doctype-system="http://www.w3.org/TR/html4/loose.dtd"
    doctype-public="-//W3C//DTD HTML 4.01 Transitional//EN"/>
    <jsp:directive.page contentType="text/html;charset=utf-8"/>
    <f:view>
    <html>
        <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        <script src="js/jquery-1.8.3.min.js" type="text/javascript"></script>        
        <script src="uploadify/jquery.uploadify.js" type="text/javascript"></script>
        <link rel="stylesheet" media="screen" href="uploadify/uploadify.css" type="text/css"/>

        <script type="text/javascript">
            $(function() {
            $('#uploadify').uploadify({
                'auto': false,
                'buttonText' : 'Browse',
                'fileSizeLimit': 0,
                'swf': 'uploadify/uploadify.swf',
                'uploader': '${pageContext.request.contextPath}/uploadservlet?key=<h:outputText value="#{uploadBean.key}" />',
                'onQueueComplete' : function(queueData) {
                $('.checkIn').click();
                } 
            });                                         

            $('input[name=actualCheckIn]').on('click', function(event){
                event.stopPropagation();
                $(this).hide();
                $('input[name=fakeCheckIn]').show();
                $('#uploadify').uploadify('upload','*');                
                return false;
            });
            });    

            var upload = function() {           
            }
        </script>

        <title>Upload</title>
        </head>
        <body>
        <h:form enctype="multipart/form-data">
            <input id="uploadify" type="file"/>
            <h:commandButton value="Check In" action="#{uploadBean.upload}" styleClass="checkIn" style="display: none"/>
            <input type="button" value="Check In" name="actualCheckIn"/>
            <input type="button" value="Check In" onclick="return false;" name="fakeCheckIn" style="display: none"/>
        </h:form>
        </body>
    </html>
    </f:view>
</jsp:root>

By this whay, when the uplaodify finishes it's upload to the servlet the acution JSF's is fired.

Managed Bean

The managed bean has the scoped Session and here is the code of it:

package com.mhis.massupload.bean;


import com.mhis.massupload.dto.DocInfo;

import java.io.Serializable;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.faces.context.FacesContext;

import javax.servlet.ServletContext;

import oracle.stellent.ridc.IdcClientException;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.lang3.StringUtils;


public class UploadBean implements Serializable {
    @SuppressWarnings("compatibility:-930710930183135088")
    private static final long serialVersionUID = -491462816764274947L;
    private transient Logger log = Logger.getLogger(getClass().getName());

    private String key;
    private transient Service service;

    public UploadBean() throws IdcClientException {
        init();
    }

    private void init() throws IdcClientException {
        key = UUID.randomUUID().toString();        
    }

    public String upload() {
        List<FileItem> fileFields = (List<FileItem>) FacesContext.getCurrentInstance().getExternalContext().getApplicationMap().remove(key);
        List<DocInfo> docInfos = new ArrayList<DocInfo>();

        if (fileFields != null) {
            for (FileItem fileField : fileFields) {
                if(StringUtils.isNotBlank(fileField.getName())) {
                    try {
                        System.out.println("Check in: " + fileField.getName());
                    } catch (Exception e) {
                        log.log(Level.SEVERE, e.getMessage());
                    }
                }                
            }
        }

        FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("docInfos", docInfos);

        return "report";
    }

    public void setKey(String key) {
        this.key = key;
    }

    public String getKey() {
        return key;
    }
}

This worked like charm.

Hope it would be helpful.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top