Today everything in the web is about AJAX. If you want to have a fashionable site with user-friendly interface, you have to use it. HTML hasn't been designed to be a user interface, so we have to provide some tricks and hacks to make it look like real GUI. A visual response is one of the things that HTML lacks, especially for the file uploading. Googling around i was lead to the article "AJAX file upload progress for Java using commons fileupload and prototype" by carson The last comment about the new version of file upload listener looked very promising and i decided to check it out.
Server side code
Main idea: Use commons-fileupload-1.2 ProgressListener class to monitor the upload of files dynamically.
The first thing you need is Apache Commons FileUpload, go there and grab the new binaries for version 1.2. Add it to your project. You will also need to update commons-io library to version 1.3
If you're using Maven, just add or change dependencies if they already exist in your pom.xml:
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>1.3</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.2</version>
</dependency>
Create a servlet that will handle file uploads:
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
public class TestUpload extends javax.servlet.http.HttpServlet implements javax.servlet.Servlet {
public TestUpload() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
Add it to servlet mapping (web.xml), don't forget to change package name:
<servlet>
<description>Servlet for test file uploads</description>
<display-name>TestUpload</display-name>
<servlet-name>TestUpload</servlet-name>
<!-- TODO: add here fully qualified TestUpload class name! -->
<servlet-class>ru.emdev.EmForge.web.TestUpload</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>TestUpload</servlet-name>
<url-pattern>/testupload</url-pattern>
</servlet-mapping>
Create a class that implements org.apache.commons.fileupload.ProgressListener
public class UploadListener implements org.apache.commons.fileupload.ProgressListener {
private volatile long bytesRead;
private volatile long contentLength;
private volatile long item;
public UploadListener() {
bytesRead = 0;
contentLength = 0;
item = 0;
}
public void update(long i_bytesRead, long i_contentLength, int i_item) {
bytesRead = i_bytesRead;
contentLength = i_contentLength;
item = i_item;
}
public long getBytesRead() {
return bytesRead;
}
public long getContentLength() {
return contentLength;
}
public long getItem() {
return item;
}
}
Add this piece of code into doPost method of the TestUpload servlet:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
// create file upload factory and upload servlet
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
// set file upload progress listener
UploadListener listener = new UploadListener();
HttpSession session = request.getSession();
session.setAttribute("LISTENER", listener);
// upload servlet allows to set upload listener
upload.setProgressListener(listener);
List items = null;
FileItem fileItem = null;
String filename = null;
try {
// iterate over all uploaded files
items = upload.parseRequest(request);
for (Iterator i = items.iterator(); i.hasNext();) {
fileItem = (FileItem) i.next();
if (!fileItem.isFormField()) {
if(fileItem.getSize()>0) {
// code that handle uploaded fileItem
// don't forget to delete uploaded files after you done with them! Use fileItem.delete();
}
}
}
// indicate that the upload was successfull
response.getWriter().write("upload successful");
} catch (FileUploadException e) {
response.getWriter().write(e.getMessage());
} catch (Exception e) {
response.getWriter().write(e.getMessage());
} finally {
session.removeAttribute("LISTENER");
}
}
This part will handle the file upload process. When request for file comes to doUpload function, we create and attach UploadListener to ServletFileUpload object. It allows us to monitor the upload progress.
And now the doGet method:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
PrintWriter out = response.getWriter();
HttpSession session = null;
UploadListener listener = null;
long contentLength = 0;
if ( ((session = request.getSession()) == null) ||
((listener = (UploadListener)session.getAttribute("LISTENER")) == null) ||
((contentLength = listener.getContentLength())<1)) {
out.write("");
out.close();
return;
}
response.setContentType("text/html");
long percentComplite = ((100*listener.getBytesRead()) / contentLength);
out.print(percentComplite);
out.close();
}
In this method we can poll our upload listener for the progress of upload. In our case we want to find out how much of file has been uploaded, in percents. We just get listener that has been binded to our session, poll it for the number of uploaded bytes and calculate the result.
Client part of code
Main idea: use IFRAME as a target for uploading files; as the result, the page won't be reloaded. Use AJAX to periodically poll progress of upload and draw progress bar.
Create simple HTML page.
<HTML>
<HEAD>
</HEAD>
<BODY>
</BODY>
</HTML>
Add inside <BODY></BODY> tags iframe and form below it, exactly in that order.
<iframe id="trgID" name="uploadTrg" height="0" width="0" frameborder="0" scrolling="yes"></iframe>
<form id="myFrm" enctype="multipart/form-data" method="post" target="uploadTrg" action="testupload" onsubmit="submitPostUsingAjax();">
<input type="file" name="txtFile0" id="txtFile0"></input><br>
<input type="submit" id="submitID1" name="submit" value="Upload"></input>
</form>
Here we specify the form "action" that will send file data to our servlet, and the form "target" for the iframe name. Using of form attributes method="post" and enctype="multipart/form-data" is required too. As the result, when we click on the "Upload" button, the request will be sent to "TestUpload" servlet, the response will be written to iframe, and the code in javascript function submitPostUsingAjax() will be executed.
Add hidden div for updating and the progress bar (just a table) below the form:
<div id="uploadStatusDiv" style="width:0px; height:0px; visibility:hidden;"></div>
<table id="progressBar" width="0px" style="border:1px; width:0px; background-color: red; color:black; visibility:hidden; ">
<tr><td></td></tr>
</table>
Now about monitoring part. I have used the prototype because it's a simple, clean, one-file solution for AJAX, DHTML manipulation and even more. Code also looks much more clean with it.
Download latest prototype from it's offical site. When writing this article i used version 1.5.0
Place prototype.js to the same place where your HMTL page is located and include it to the top inside <HEAD></HEAD> tags:
<script type="text/javascript" src="prototype.js">
</script>
Then add the following code:
<script type="text/javascript">
// global object that contains Ajax.PeriodicalUpdater
var updater;
// main function for upload monitoring
function submitPostUsingAjax() {
try {
// get upload status
updater = new Ajax.PeriodicalUpdater('uploadStatusDiv','testupload', {
asynchronous:true,
frequency:1,
method:'get',
onSuccess:function(request) {
if (request.responseText.length > 1) {
$('progressBar').style.visibility = 'visible';
$('progressBar').style.width = request.responseText + '%';
}
}
});
} catch(e) {
alert('submitPostUsingAjax() failed, reason: ' + e);
} finally {
}
return false;
}
// executes function after page loaded
function addLoadEvent(func) {
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function() {
oldonload();
func();
}
}
}
// this function will be executed after "target" IFRAME content changed
function handleUploadFinished() {
// stop updater manually
if (typeof updater != 'undefined') {
updater.stop();
updater = null;
}
}
// observe IFrame object for "load" event to stop AJAX updater
function observeFormSubmit() {
// add event to observe upload target
Event.observe('trgID', 'load', handleUploadFinished);
}
// add event handler only after page loaded
addLoadEvent(observeFormSubmit);
</script>
The function submitPostUsingAjax() creates Ajax.PeriodicalUpdater that will poll our servlet every second and retrives the progress of file upload in percent. The parameter "uploadStatusDiv" Ajax.PeriodicalUpdater is needed only to receive the update events.
The function addLoadEvent() is needed for attaching the listener to IFRAME object after it is created. This function was taken from Top 10 custom JavaScript functions of all time.
In the handleUploadFinished() function AJAX updater will be stopped when the upload is finished or interrupted.
After the page is loaded, IFRAME object will monitor the 'onload' event. When a user clicks on the "Upload" button, Ajax.PeriodicalUpdater will periodically poll our servlet for upload status and accordingly set the width of the progress bar. After the upload has been finished, successfully or not, IFRAME content is changed, and we will stop AJAX updater manually.
That's it, enjoy.
|