[Java] File uploads with progress bar in Spring framework
File uploads with Spring framework are straight forward, however what happens if you would like to show a progress bar while uploading large files? Spring framework default multipart implementation depends on Apache Commons Fileupload library and after looking at the user guide there is a way of attaching progress listener so it is possible to achieve progress bar functionality by modifying the default implementation. The second question is how to get the progress bar interaction on your website. In most examples online there seems to be another AJAX request polling the server for an update which means the upload progress needs to be stored somewhere – probably in the HTTP session or asĀ a thread attached variable?
To start with this implementation is not tested in production therefore I do not take any blame whatsoever. Also I do not claim that this is a good solution I wrote this while playing with spring approximately for two hours. Apart from the progress bar it would be nice to implement resume functionality if the upload fails. Again I am not sure if this is a good practice, maybe it should be left to the protocols dedicated for doing it.
So to get the thing running we need to modify the default implementation, store the upload progress in session, write some JavaScript code to query the progress and implement a filter that would return the upload progress if a specific URL is requested.
Let’s start with modifying the default implementation of multipart resolver. In this case we only need to override a single method to attach the progress listener. To be more precise I am talking about the parseRequest method.
package com.wordpress.geekdev101.fileupload;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.ProgressListener;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
public class ProgressCapableMultipartResolver
extends CommonsMultipartResolver
{
public final static String PROGRESS_PREFIX = "ProgressCapableMultipartResolver:";
private void initProgressProcessor(HttpServletRequest request) {
request.getSession().setAttribute(
PROGRESS_PREFIX + request.getRequestURI(),
new ProgressDescriptor());
}
private void clearProgressProcessor(HttpServletRequest request) {
request.getSession().removeAttribute(PROGRESS_PREFIX
+ request.getRequestURI());
}
private void processProgress(long bytesRead, long bytesTotal,
HttpServletRequest request) {
request.getSession().setAttribute(
PROGRESS_PREFIX + request.getRequestURI(),
new ProgressDescriptor(bytesRead, bytesTotal));
}
protected MultipartParsingResult parseRequest(
final HttpServletRequest request)
throws MultipartException
{
String encoding = determineEncoding(request);
ServletFileUpload fileUpload = (ServletFileUpload)prepareFileUpload(encoding);
initProgressProcessor(request);
fileUpload.setProgressListener(new ProgressListener() {
@Override
public void update(
long pBytesRead, long pContentLength, int pItems) {
processProgress(pBytesRead, pContentLength, request);
}
});
MultipartParsingResult result = null;
try {
result = parseFileItems(
fileUpload.parseRequest(request),
encoding);
} catch (FileUploadBase.SizeLimitExceededException ex) {
throw new MaxUploadSizeExceededException(
fileUpload.getSizeMax(), ex);
} catch (FileUploadException ex) {
throw new MultipartException("Could not parse " +
"multipart servlet request", ex);
}
clearProgressProcessor(request);
return result;
}
}
package com.wordpress.geekdev101.fileupload;
public class ProgressDescriptor
{
public long bytesRead;
public long bytesTotal;
public ProgressDescriptor() {
bytesRead = bytesTotal = 0;
}
public ProgressDescriptor(long bytesRead,
long bytesTotal)
{
this.bytesRead = bytesRead;
this.bytesTotal = bytesTotal;
}
public long getBytesRead() {
return bytesRead;
}
public long getBytesTotal() {
return bytesTotal;
}
@Override
public String toString() {
return bytesRead + "/" + bytesTotal;
}
}
Let say the request for progress will always be a URI with “.progress” appended to the request which makes it easy to define our filter in web.xml i.e. by specifying the filter mapping as “*.progress”. Once the filter gets the request we modify the URI to the original state i.e. without the progress tail and look it up in the session.
package com.wordpress.geekdev101.fileupload;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.filter.OncePerRequestFilter;
public class UploadProgressFilter
extends OncePerRequestFilter
{
private final String PROGRESS_TAIL = ".progress";
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String requestUri = request.getRequestURI();
String originalUrl = requestUri.substring(0,
requestUri.length() - PROGRESS_TAIL.length());
String attributeName = ProgressCapableMultipartResolver.PROGRESS_PREFIX
+ originalUrl;
Object progress = request.getSession().getAttribute(attributeName);
if (progress != null) {
ProgressDescriptor descriptor = (ProgressDescriptor)progress;
response.getOutputStream().write(
descriptor.toString().getBytes() );
}
}
}
To avoid writing a lot of JavaScript I am reusing the existing components available on-line. In this case jQuery and jQuery Forms making my codeĀ very simple. Before submit we start polling the server for information. Note there are two options how the progress can be queried either polling or http binding. It is probably better to use binding in this case, but you could argue. For example, assuming that there always be a progress in a second interval. I am using polling because it is easier to implement and is this is just a silly example.
var time;
var elem = $('#progress');
var form = $('form').ajaxForm({
beforeSubmit: function() {
var url = form[0].action;
var pos = url.indexOf(";");
if (pos != -1) url = url.substring(0, pos);
time = setInterval(function() {
$.get(url + ".progress", function(data) {
if (!data) return;
data = data.split("/");
elem.width( Math.round(data[0] / data[1] * 100) * 2 );
});
}, 500);
},
success: function() {
clearInterval(time);
elem.width(100 * 2);
}
});
About this entry
You’re currently reading “[Java] File uploads with progress bar in Spring framework,” an entry on Geekdev101
- Published:
- October 25, 2009 / 4:18 pm
- Category:
- Development, How-To
- Tags:
- Java, JavaScript, jQuery, Spring
2 Comments
Jump to comment form | comment rss [?] | trackback uri [?]