Grails has the basic infrastructure in place to support AJAX-based file upload progress bars on your pages. In this article I will show you how to leverage the ExtJS Javascript library along with the Apache Commons File Upload library to achieve a nice user-friendly file upload progress bar in your Grails project. You can download a working Grails demo application at the end of this article in the Resources section.
Below is a screenshot of the demo application in action.
Background
File upload handling is, as everything else in Grails stacked on top of the Spring Framework. The Spring Framework in turn has delegated the job of managing streaming uploads to the Apache Commons File Upload library.
Commons File Upload takes care of very important server-side memory-management by allowing you to "stream" your uploads to disk instead of them being kept and handled entirely in memory. Read more about this at the Commons File Upload site.
In this article we will need to complete the following steps in order to achieve a working client-side progressbar:
- Implement a custom ProgressListener
- Subclass Spring's CommonsMultipartResolver
- Inject our custom ProgressListener and CommonsMultipartResolver classes into Spring via "conf/spring/resources.xml"
- Create an action in a controller which can be polled for upload progress info from client-side javascript
- Write appropriate client-side Javascript to poll the server for progress info
Prerequisites:
- At the time of writing, Grails 1.0.1 ships with version 1.1.x of Apache commons-fileupload. Visit http://commons.apache.org/fileupload/ and get at least version 1.2
- For the purposes of this tutorial I will be using ExtJS version 2.x (http://extjs.com/download), which comes with a nifty ready-built javascript progressbar.
Implement a custom ProgressListener
The commons file upload API allows us to register a class which implements the org.apache.commons.fileupload.ProgressListener interface. Once registered, this will allow us to receive progress updates as multipart data is being streamed to the server. Below is a code snippet from AjaxProgressListener.groovy (see Resources at bottom of article)
6:class AjaxProgressListener implements ProgressListener {
7:
8:
.
.
22: void update(long pBytesRead, long pContentLength, int pItems){
23:
24: session.setAttribute("progressMap", ["bytesRead": pBytesRead, "length" : pContentLength, "items" : pItems])
25: println("DEBUG UPLOAD: ${pBytesRead} : ${pContentLength} : ${pItems}")
26:
27: if (pBytesRead == pContentLength){
28: session.setAttribute("progressStatus", STATUS_DONE)
29: }
30:
31: }
32:}
Subclassing Spring's MultipartResolver
Grails injects a "multipartResolver" property into our applicationContext. This property is actually an instance of Spring's CommonsMultipartResolver. CommonsMultipartResolver actually wraps the commons FileUpload class. However it does not expose the FileUpload functionality allowing us to register a progress listener.
In order to get our custom ProgressListener registered, we have to subclass CommonsMultipartResolver, and expose getter/setter methods for ProgressListener registration see lines 18 to 26 and our overridden factory method on line 47) in the
1:package com.adeptiva.commons.upload
2:
3:import org.springframework.web.multipart.commons.CommonsMultipartResolver
.
.
11:import javax.servlet.http.HttpServletRequest
12:
13:class AjaxMultipartResolver extends CommonsMultipartResolver{
14:
15: private ProgressListener pListener
16: private HttpServletRequest request
17:
18: public void setProgressListener(ProgressListener p){
19: this.pListener = p
20: }
21:
22: public ProgressListener getProgressListener(){
23:
24: return this.pListener
25:
26: }
27:
28:
.
.
46:
47: protected FileUpload newFileUpload(FileItemFactory fileItemFactory) {
48:
49: FileUpload fu = super.newFileUpload(fileItemFactory)
50:
51: if (this.pListener != null){
52: this.pListener.session = this.request.session
53: fu.setProgressListener(this.pListener)
54: }
55:
56: this.pListener?.updateStatus(AjaxProgressListener.STATUS_UPLOADING)
57:
58: return fu
59: }
60:
61:}
Injecting our AjaxMultipartResolver into Grails/Spring
Now that we have our own implementation of the Spring CommonsMultipartResolver, it's time to take advantage of dependency injection to ensure that Grails uses our AjaxMultipartResolver. We modify our "conf/spring/resources.xml" as follows:
1:<bean id="multipartResolver" class="com.adeptiva.commons.upload.AjaxMultipartResolver">
2: <property name="maxInMemorySize">
3:
4: <value>10240</value>
5: </property>
6: <property name="maxUploadSize">
7:
8: <value>1024000000</value>
9: </property>
10: <property name="progressListener">
11: <ref bean="progressListener"/>
12: </property>
13: </bean>
14:
15:<bean id="progressListener" class="com.adeptiva.commons.upload.AjaxProgressListener"/>
16:
Progress Information
The action which retrieves progress information is rather simple, and beyond doing some minor error checking is only responsible for constructing a JSON response (using builders of course).
1: def uploadInfo = {
2:
3: def progressMap = session.getAttribute("progressMap")
4: def progressStatus = session.getAttribute("progressStatus")
.
.
22: render(builder:'json'){
23: bytesRead(progressMap['bytesRead'] )
24: totalSize(progressMap['length'] )
25: status(progressStatus)
26: }
27: }
Our .gsp and Javascript
Between the .gsp and javascript files there remains only a few items to take care of:
- Perform a form submission containing our file input via Javascript. This prevents the browser from navigating away from the current page after upload is complete.
- Start an asynchronous task which polls the server for upload progress, updating our progressbar on each return.
Resources:
You can download the full project demonstrating the above from HERE.
References:
File Uploading section from the Grails User Guide.
Apache Commons File Upload website
FIN.
