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.
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:

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.
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.
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.
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).
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>
|
MyBean ClassThe 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 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>
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:

Figure 2. The output produced by the result page
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.
Andrei Cioroianu is the founder of Devsphere and an author of many Java articles published by ONJava, JavaWorld, and Java Developer's Journal.
Return to ONJava.com.
Copyright © 2007 O'Reilly Media, Inc.