How to get JSF to upload file with Apache Commons FileUpload

Thang Pham picture Thang Pham · Feb 7, 2012 · Viewed 11.7k times · Source

I know how to do file upload using Primefaces or using Tomahawk, however, I am trying to doing file upload using Apache Commons FileUpload and so far I am having a bit of road block. Even though my form use multipart/form-data, when I submit my form, the content type become application/x-www-form-urlencoded. Here is my code

<h:body>
    <h:form enctype="multipart/form-data">
        Upload File
        <input type="file" name="file"/>
        <p:commandButton value="Submit" action="#{viewBean.submit}"/>
    </h:form>
</h:body>

Here is my ViewBean

@ManagedBean
@ViewScoped
public class ViewBean implements Serializable {
    public void submit() {
        String url = "/FileUploadServlet";
        FacesContext context = FacesContext.getCurrentInstance();
        try {
            String contentType = context.getExternalContext().getRequestContentType();
            context.getExternalContext().dispatch(url);
        } catch (Exception e) {
            logger.log(Level.SEVERE, "Exception when calling Servlet", e);
        } finally {
            context.responseComplete();
        }
    }
}

So when I try to print the content type above, it showed application/x-www-form-urlencoded. If I put ajax="false" to my p:commandButton, then the submit() method is not even invoked, but if I take out enctype="multipart/form-data" (still keep ajax="false"), then submit() is invoked but it is not multipart, it is application/x-www-form-urlencoded, so apache commons fileupload throw an exception since it is not multipart. Seems like whatever I do, I cant seems to get the multipart requrest. Please help

Answer

BalusC picture BalusC · Feb 7, 2012

So when I try to print the content type above, it showed application/x-www-form-urlencoded.

The <p:commandButton> sends by default an ajax request of level 1 XMLHttpRequest. This does not support multipart/form-data. Only level 2 XMLHttpRequest supports it, but it's only supported in the newest browsers (those also supporting HTML5) and not implemented in JSF JS API nor in PrimeFaces JS API.


If I put ajax="false" to my p:commandButton, then the submit() method is not even invoked

This way however a fullworthy multipart/form-data will be sent. That the submit method is not invoked is just because JSF prior to version 2.2 does not support multipart/form-data requests out the box. JSF collects the submitted data by default using request.getParameter() and getParameterMap() on the underlying HTTP servlet request. However, this will return null when an encoding other than application/x-www-form-urlencoded is been used. As JSF determines the to-be-invoked action method based on submitted data, it won't be able to locate and invoke it when the data is null.

In theory, if you create a Filter which uses Apache Commons FileUpload or the new Servlet 3.0 request.getPart()/getParts() methods to extract the data from the multipart/form-data request and wraps the current HTTP servlet request with a custom implementation which overrides the getParameter() calls wherein a mapping of the extracted data is been supplied, then JSF will be able to do the needed job based on results of getParameter() calls. You can find a concrete example utilizing the Servlet 3.0 API in this article and the same example which is slightly changed to utilize Apache Commons FileUpload in this answer.

The upcoming JSF 2.2 will have a new <h:inputFile> component which is bindable to a Servlet 3.0 Part property.

<h:form enctype="multipart/form-data">
    <h:inputFile value="#{bean.file}" />
    <h:commandButton value="submit" action="#{bean.submit}" />
</h:form>

with

private Part file;

JSF 2.2 final release is scheduled for late end of Q1, but is currently available as a snapshot release.