ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Upload Files with JSF and MyFaces

by Andrei Cioroianu
07/13/2005

Web browsers provide an easy way for sending files to web applications, but the current versions of the Java web standards (servlets, JSP, and JSF) do not offer any help. Fortunately, there are third-party frameworks, such as Apache Commons File Upload, Apache MyFaces, and Oracle ADF Faces, that implement this feature, exposing simple APIs and custom tags. The first half of this article explains how file uploading works, walking you through the source code of MyFaces and Commons File Upload (the former uses the latter internally). It is helpful to know what happens inside of these open source frameworks in order to use them efficiently, and to be able to modify them if you have to. In the second half of the article, you'll find a sample application that lets users upload files using their web browsers.

Web-Based File Uploading

The term "upload" is somewhat overused. A webmaster would say that he uploads a file when he publishes it on his website. A web developer would say that he implements file uploading when he creates a HTML form and a script that lets regular users send files using their web browsers.

There is some overlapping between these two meanings, because a webmaster could use a web-based interface to publish files (pages, images, scripts, etc.). Companies that host personal web sites for free, such as Yahoo, implement web-based file uploading in order to let people upload their pages. This allows anyone with a web browser and internet access to publish a small website. However, there are much better ways for publishing your web content, such as FTP or secure FTP. In this case, you would use a dedicated application, such as a FTP client, instead of your web browser for uploading your content to the web server.

This article discusses file uploading from the web developer's point of view. For example, a web-based mail application, such as Yahoo mail, implements file uploading so that users can send messages with attachments. Another good example is a job website that lets you send your resume to technical recruiters. The example application of this article computes hash values of the uploaded files. You can do anything you want with the uploaded files in your own applications, such as storing their content into a database or mailing them as attachments. Now, let's see how to implement file uploading in a web application.

An HTML form can contain one or more <input type="file"> elements that the browser renders as input fields, where users are allowed to enter file paths. Next to each file input field, the web browser adds a button that opens a file dialog, letting users select a file instead of entering the path:

Web form containing a file input field
Figure 1. Web form containing a file input field

When the user clicks the form's Submit button, the web browser encodes the form data, which includes the content of the selected file along with its name (or path) and the other parameters of the form. Then, the browser sends the encoded form data to the web server, which passes this data to the script specified as the action attribute of the <form> tag. If you develop a Java web application, the action script can be a servlet or a JSP page.

Because the default encoding of the form data and the default GET method are not suitable for file uploading, a form that contains file input fields must specify the multipart/form-data encoding and the POST method in the <form> tag:

<form enctype="multipart/form-data" method="POST" action="...">
    ...
    <input type="file" name="...">
    ...
</form>

Things aren't as simple as they seem, however, because application servers implementing the servlet and JSP specifications are not required to handle the multipart/form-data encoding. Therefore, you need a parser for the request's input stream, such as Apache Commons File Upload, which is a small Java package that lets you obtain the content of the uploaded file from the encoded form data. The API of this package is flexible, allowing you to keep small files in memory while large files are stored on disk in a temporary directory. You can specify the size threshold beyond which files are written to disk instead of being kept in memory, and you can also specify the maximum size that is allowed for the uploaded files.

The org.apache.commons.fileupload package contains a class named DiskFileUpload whose parseRequest() method gets a HttpServletRequest parameter and returns a List of org.apache.commons.fileupload.FileItem instances. The encoded form data is read from the stream returned by the getInputStream() method of the servlet request. The FileItem name is somewhat misleading, because the instances of this interface represent both uploaded files and regular request parameters.

The API provided by the Commons File Upload package gives you access to the parsed from data, but the getParameter() and getParameterValues() methods of the servlet request won't work. This is a problem, since these two methods are called by the standard JSF components that run behind regular input fields, checkboxes, radio buttons, and lists. The Servlets API provides two features (filters and request wrappers) that can be used to solve this problem. The next section describes how Apache MyFaces implements a filter that adds the much-needed support for file uploading without breaking the existing JSF components. In addition, MyFaces provides APIs for JavaBeans along with a custom JSF component that renders the <input type="file"> elements.

Configuring JSF and MyFaces Extensions

Currently, there is a main implementation of the JSF specification called JSF Reference Implementation (RI) and there is another one provided by Apache, which is known as MyFaces. There might be other JSF implementations, but JSF RI and MyFaces are the most popular. Many developers prefer the former because it's the "official" implementation from Sun, but MyFaces has some interesting extensions, such as the support for uploading files. You can use the MyFaces extensions together with the JSF RI from Sun if you want. You just have to put the myfaces-extensions.jar file together with the JAR files of JSF RI and commons-fileupload-1.0.jar in the WEB-INF/lib directory of your web application. Here are the JAR files that you need:

JSF 1.1 RI jsf-api.jar
jsf-impl.jar
JSTL 1.1 RI jstl.jar
standard.jar
MyFaces Extensions myfaces-extensions.jar
Apache Commons
(used by JSF and
MyFaces Extensions)
commons-collections.jar
commons-digester.jar
commons-beanutils.jar
commons-logging.jar
commons-fileupload-1.0.jar

A class named MultipartRequestWrapper, which can be found in the org.apache.myfaces.component.html.util package, creates the bridge between MyFaces and Commons File Upload. This class extends HttpServletRequestWrapper, overriding the getParameterMap(), getParameterNames(), getParameter(), and getParameterValues() methods so that they can work properly with the multipart/form-data encoding. In addition, MultipartRequestWrapper provides two new methods, named getFileItem() and getFileItems(), that give you access to the uploaded files through the org.apache.commons.fileupload.FileItem interface.

The MyFaces ExtensionsFilter from the org.apache.myfaces.component.html.util package creates the MultipartRequestWrapper instance when it detects the multipart/form-data encoding. Therefore, you shouldn't be concerned about the parsing of the form data, but it's useful to know how this is implemented in case you want to change the way uploaded files are handled. In a typical application, you just have to configure the ExtensionsFilter in the web.xml descriptor of your web application so that it can intercept the HTTP requests before the FacesServlet of JSF:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE web-app PUBLIC
    "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

    <context-param>
        <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
        <param-value>client</param-value>
    </context-param>

    <servlet>
        <servlet-name>FacesServlet</servlet-name>
        <servlet-class>
            javax.faces.webapp.FacesServlet
        </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>FacesServlet</servlet-name>
        <url-pattern>/faces/*</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
        <servlet-name>FacesServlet</servlet-name>
        <url-pattern>*.faces</url-pattern>
    </servlet-mapping>

    <filter>
        <filter-name>ExtensionsFilter</filter-name>
        <filter-class>
            org.apache.myfaces.component.html.util.ExtensionsFilter
        </filter-class>
        <init-param>
            <param-name>uploadMaxFileSize</param-name>
            <param-value>10m</param-value>
        </init-param>
        <init-param>
            <param-name>uploadThresholdSize</param-name>
            <param-value>100k</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>ExtensionsFilter</filter-name>
        <servlet-name>FacesServlet</servlet-name>
    </filter-mapping>

    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>

</web-app>

The two filter parameters from the preceding example tell MyFaces to keep files whose size is less that 100K in memory and to ignore files that take more than 10MB of disk space. A file whose size is between uploadThresholdSize and uploadMaxFileSize is stored on disk as a temporary file. If you try to upload a file that is too large, the current version of MyFaces ignores all form data, as if the user submitted an empty form. If you want to signal the failed upload to the user, you would have to change the source code of the MultipartRequestWrapper class of MyFaces. Look for the place where the SizeLimitExceededException exception is caught and use FacesContext.getCurrentInstance().addMessage() to warn the user.

As already mentioned, MyFaces Extensions contain a file-uploading component that can be used in JSF pages. The next section shows how to do this.

Using the File Upload Component of MyFaces

In order to use JSF and MyFaces in a web page, you have to declare their tag libraries with the <%@taglib%> directive:

<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@ taglib uri="http://myfaces.apache.org/extensions" prefix="x"%>

The <h:form> tag of JSF doesn't have a method attribute, since it supports only the POST method, but it has an enctype attribute, which you must use if you want to upload files, specifying the multipart encoding type for the form data:

<f:view>

    <h:form id="MyForm" enctype="multipart/form-data" >
        ...
        <x:inputFileUpload id="myFileId"
            value="#{myBean.myFile}"
            storage="file"
            required="true"/>
        ...
    </h:form>

</f:view>

The <x:inputFileUpload> tag of MyFaces lets you define the attributes of the UI component whose renderer produces the <input type="file"> element. The org.apache.myfaces.custom.fileupload package contains the UI component's class (HtmlInputFileUpload), its renderer (HtmlFileUploadRenderer), the custom tag handler (HtmlInputFileUploadTag), the UploadedFile interface, and other helper classes. The HtmlInputFileUpload class extends the standard HtmlInputText component of JSF, overriding some of its methods. The HtmlFileUploadRenderer is responsible for generating the HTML markup and for getting the FileItem from the MultipartRequestWrapper.

Instead of giving you access to the FileItem instance created by Commons File Upload, MyFaces provides its own UploadedFile interface for getting the content of the uploaded file, the content type, the file name, and its size. The backing bean of your JSF form must have a property of the type UploadedFile. The preceding example binds the value of the UI component to such a bean property with a JSF expression (#{myBean.myFile}). The JSF framework will get the value of the HtmlInputFileUpload component, which is a UploadedFile instance, and will set the myFile property of the backing bean:

import org.apache.myfaces.custom.fileupload.UploadedFile;
...
public class MyBean {
    private UploadedFile myFile;

    public UploadedFile getMyFile() {
        return myFile;
    }

    public void setMyFile(UploadedFile myFile) {
        this.myFile = myFile;
    }
    ...
}

It is important to know that MyFaces has two implementations of its UploadedFile interface: UploadedFileDefaultMemoryImpl and UploadedFileDefaultFileImpl. The former is used when the <x:inputFileUpload> tag doesn't have a storage attribute or when the value of this attribute is memory. The latter is used when the value of the storage attribute is file.

The UploadedFileDefaultMemoryImpl class gets the content of the uploaded file as well as its name, size, and content type from a FileItem instance and stores all of this information into private fields. Therefore, even if Commons File Upload keeps the file on disk, this implementation of the UploadedFile interface maintains the content of the uploaded file in memory, wasting resources.

The UploadedFileDefaultFileImpl class uses a transient field to keep a reference to the a FileItem instance, which is used to obtain the content of the uploaded file only when the getInputStream() method is called. This implementation saves memory space, but if it's serialized, you cannot obtain the file content after deserialization. Therefore, the backing bean of the file-uploading form should not be kept in the session scope, because application servers serialize session beans when the application is restarted or when the server shuts down.

If you want an efficient solution that works properly, keep the backing beans in the request scope and specify storage="file" within <x:inputFileUpload> to save memory resources. You could also solve the serialization problem of the UploadedFileDefaultFileImpl class by adding a writeObject() method that should serialize the content of the uploaded file. To keep this implementation of UploadedFile efficient, the corresponding readObject() method should recreate the temporary file instead of reading its content in memory.

There is one more thing that you should take into account when using the MyFaces component. The UploadedFile interface doesn't define a method for deleting the temporary files that Commons File Upload creates on disk. These files will be deleted only when the FileItem instances are garbage collected. The DefaultFileItem class of Commons File Upload has a finalize() method that deletes the temporary file managed by the object that is removed from memory. If your application is uploading large files, you might want to delete them right after they are processed, without waiting for garbage collection. To be able to do that, you would have to add a getFileItem() method (in UploadedFileDefaultFileImpl) that should return the FileItem instance, which has a delete() method.

Sample Application

The previous sections have described how MyFaces supports file uploading with the help of Commons File Upload. Now let's see a real application that uses this feature. A JSF form (MyForm.jsp) lets users select a file and choose a message-digest algorithm, which is used by a backing bean (MyBean.java) to compute a hash value that is displayed by another web page (MyResult.jsp). These pages and the backing bean are glued with a JSF configuration file (faces-config.xml).

The MyForm.jsp Page

This JSF form uses the <x:inputFileUpload> tag of MyFaces, along with other standard JSF tags that render labels, messages, a drop-down list that contains message-digest algorithms, and a command button that uses a JSF expression (#{myBean.processMyFile}) to specify the action method that processes the uploaded file:

<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@ taglib uri="http://myfaces.apache.org/extensions" prefix="x"%>

<f:view>

<h:form id="MyForm" enctype="multipart/form-data" >

    <h:messages globalOnly="true" styleClass="message"/>

    <h:panelGrid columns="3" border="0" cellspacing="5">

        <h:outputLabel for="myFileId" value="File: "/>
        <x:inputFileUpload id="myFileId"
            value="#{myBean.myFile}"
            storage="file"
            required="true"/>
        <h:message for="myFileId"/>

        <h:outputLabel for="myParamId" value="Param: "/>
        <h:selectOneMenu id="myParamId"
                value="#{myBean.myParam}"
                required="true">
            <f:selectItem itemLabel="" itemValue=""/>
            <f:selectItem itemLabel="MD5" itemValue="MD5"/>
            <f:selectItem itemLabel="SHA-1" itemValue="SHA-1"/>
            <f:selectItem itemLabel="SHA-256" itemValue="SHA-256"/>
            <f:selectItem itemLabel="SHA-384" itemValue="SHA-384"/>
            <f:selectItem itemLabel="SHA-512" itemValue="SHA-512"/>
        </h:selectOneMenu>
        <h:message for="myParamId"/>

        <h:outputText value=" "/>
        <h:commandButton value="Submit"
            action="#{myBean.processMyFile}"/>
        <h:outputText value=" "/>

    </h:panelGrid>

</h:form>

</f:view>

The MyBean Class

The backing bean has three properties: myFile, myParam, and myResult. The role of the myFile property was explained in the first half of the article. It lets you get the content of the uploaded file along with its name, size, and content type. The value of the myParam property is the message-digest algorithm. The myResult property will hold the hash value after the execution of the processMyFile() method. The MyBean class provides get and set methods for all of its properties:

package com.devsphere.articles.jsfupload;

import org.apache.myfaces.custom.fileupload.UploadedFile;
...
public class MyBean {
    private UploadedFile myFile;
    private String myParam;
    private String myResult;

    public UploadedFile getMyFile() {
        return myFile;
    }

    public void setMyFile(UploadedFile myFile) {
        this.myFile = myFile;
    }

    public String getMyParam() {
        return myParam;
    }

    public void setMyParam(String myParam) {
        this.myParam = myParam;
    }

    public String getMyResult() {
        return myResult;
    }

    public void setMyResult(String myResult) {
        this.myResult = myResult;
    }
    ...
}

The processMyFile() method gets the content of the uploaded file through an input stream obtained with myFile.getInputStream(). The hash value is computed with the help of java.security.MessageDigest, and then it is converted into a string that can be accessed via the myResult property:

package com.devsphere.articles.jsfupload;
...
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import java.io.*;

public class MyBean {
    ...
    public String processMyFile() {
        try {
            MessageDigest md
                = MessageDigest.getInstance(myParam);
            InputStream in = new BufferedInputStream(
                myFile.getInputStream());
            try {
                byte[] buffer = new byte[64 * 1024];
                int count;
                while ((count = in.read(buffer)) > 0)
                    md.update(buffer, 0, count);
            } finally {
                in.close();
            }
            byte hash[] = md.digest();
            StringBuffer buf = new StringBuffer();
            for (int i = 0; i < hash.length; i++) {
                int b = hash[i] & 0xFF;
                int c = (b >> 4) & 0xF;
                c = c < 10 ? '0' + c : 'A' + c - 10;
                buf.append((char) c);
                c = b & 0xF;
                c = c < 10 ? '0' + c : 'A' + c - 10;
                buf.append((char) c);
            }
            myResult = buf.toString();
            return "OK";
        } catch (Exception x) {
            FacesMessage message = new FacesMessage(
                FacesMessage.SEVERITY_FATAL,
                x.getClass().getName(), x.getMessage());
            FacesContext.getCurrentInstance().addMessage(
                null, message);
            return null;
        }
    }

}

The faces-config.xml File

The JSF configuration file defines the backing bean in the request scope and specifies a navigation rule:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE faces-config PUBLIC
    "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN"
    "http://java.sun.com/dtd/web-facesconfig_1_1.dtd">

<faces-config>

    <managed-bean>
        <managed-bean-name>myBean</managed-bean-name>
        <managed-bean-class>
            com.devsphere.articles.jsfupload.MyBean
        </managed-bean-class>
        <managed-bean-scope>request</managed-bean-scope>
    </managed-bean>

    <navigation-rule>
        <from-view-id>/MyForm.jsp</from-view-id>
        <navigation-case>
            <from-outcome>OK</from-outcome>
            <to-view-id>/MyResult.jsp</to-view-id>
        </navigation-case>
    </navigation-rule>

</faces-config>

The MyResult.jsp Page

This web page displays some information about the uploaded file and the hash value:

<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>

<f:view>

    <h:panelGrid columns="2" border="0" cellspacing="5">

        <h:outputText value="FileName:"/>
        <h:outputText value="#{myBean.myFile.name}"/>

        <h:outputText value="FileSize:"/>
        <h:outputText value="#{myBean.myFile.size}"/>

        <h:outputText value="Param:"/>
        <h:outputText value="#{myBean.myParam}"/>

        <h:outputText value="Result:"/>
        <h:outputText value="#{myBean.myResult}"/>

    </h:panelGrid>

</f:view>

The displayed file name may actually contain the full path in the client's file system, as provided by the web browser:

The output produced by the result page
Figure 2. The output produced by the result page

Summary

There are many cases when users need to upload files through their browsers, but there is no ideal way for handling these files on the server side. Keeping the files' content in memory is acceptable only for small files, while storing the uploaded content in temporary files complicates the situation. MyFaces lets you choose the solution that is good for your application, but this framework has some minor problems. It doesn't let you delete the temporary files when you don't need them anymore, the file names are sometimes file paths, and there are no warnings when users attempt to upload files that are too large. These can be fixed, since the source code is available, and this article indicates where you can improve the code of MyFaces. For many applications, however, you might find MyFaces usable without any changes. The samples of this article have been tested with JSF 1.1.01, MyFaces 1.0.9, and Commons File Upload 1.0.

Resources

Andrei Cioroianu is the founder of Devsphere and an author of many Java articles published by ONJava, JavaWorld, and Java Developer's Journal.

JavaServer Faces

Related Reading

JavaServer Faces
By Hans Bergsten

Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.