In my previous post, we talked about the the business case for building a custom versioning gadget for Google Docs. In this post, we would talk about the GWT gadget which we built for the purpose.
The custom gadget looks like this
As you would notice, we have a file uploader which can upload multiple files at once (of course you have to select them individually). Then there is an ajax enabled feedback which tells you the status of the upload and once it is done.
There are also 4 fields which are custom meta data that needs to be tagged with every document. Note that this is not static tagging just like what we can do with Google Collections (the rechristened folders in Google Docs). The difference here is that, these tags are dynamic. Which means that we can have dynamic values in them. For example a document could have an expiry date of 30th May, 30th June or whatever you intend it to be.
How did we do it?
Luckily, there is an exciting project called GWTUpload which took more than half of our pains away. The idea behind this component is that there is a GWT component and then there is a backing servlet which would get the file. Depending on whether you would be able to save your file in the filesystem (which you cannot on the Google App Engine), you would be able to manipulate and work on the file. The excellent getting started guide for GWTUpload is present here. We had custom meta data too as a part of the gadget, let us see what does the code look like now
[sourcecode language=”java”]
public void onModuleLoad() {
// Attach the image viewer to the document
RootPanel.get("thumbnails").add(panelImages);
// Create a new uploader panel and attach it to the document
MultiUploader defaultUploader = new MultiUploader();
RootPanel.get("default").add(defaultUploader);
defaultUploader.addOnStartUploadHandler(onStartUploaderHandler);
// Add Date boxes
DateTimeFormat dateFormat = DateTimeFormat.getLongDateFormat();
expiryDateBox.setFormat(new DateBox.DefaultFormat(dateFormat));
RootPanel.get("expiryDate").add(expiryDateBox);
reminderDateBox.setFormat(new DateBox.DefaultFormat(dateFormat));
RootPanel.get("reminderDate").add(reminderDateBox);
// Add List Boxes
coeListBox.addItem("Application To Disimbursement",
"Application To Disimbursement");
coeListBox.addItem("Customer Service", "Customer Service");
coeListBox.addItem("Others", "Others");
RootPanel.get("coeListBox").add(coeListBox);
geographyListBox.addItem("North", "North");
geographyListBox.addItem("East", "East");
geographyListBox.addItem("West", "West");
geographyListBox.addItem("South", "South");
RootPanel.get("geographyListBox").add(geographyListBox);
// Add a finish handler which will load the image once the upload
// finishes
defaultUploader.addOnFinishUploadHandler(onFinishUploaderHandler);
}
private IUploader.OnStartUploaderHandler onStartUploaderHandler = new IUploader.OnStartUploaderHandler() {
@Override
public void onStart(IUploader uploader) {
String path = uploader.getServletPath()
+ "?expiryDate="
+ expiryDateBox.getTextBox().getText()
+ "&reminderDate="
+ reminderDateBox.getTextBox().getText()
+ "&coe="
+ coeListBox.getValue(coeListBox.getSelectedIndex())
+ "&geography="
+ geographyListBox.getValue(geographyListBox
.getSelectedIndex());
System.out.println("The path made: " + path);
uploader.setServletPath(path);
}
};
[/sourcecode]
As you would notice in the code, we have added the GWTUpload component as MultiUploader. Apart from that, we have added a few more fields as a part of the custom meta data. These are expiry date, reminder one, coe and geography. These are standard GWT date picker and list boxes.
Notice that we have defined a start uploader handler which GWTUpload would honor as soon as the upload begins.
[sourcecode language=”java”]
private IUploader.OnStartUploaderHandler onStartUploaderHandler = new IUploader.OnStartUploaderHandler() {
@Override
public void onStart(IUploader uploader) {
String path = uploader.getServletPath()
+ "?expiryDate="
+ expiryDateBox.getTextBox().getText()
+ "&reminderDate="
+ reminderDateBox.getTextBox().getText()
+ "&coe="
+ coeListBox.getValue(coeListBox.getSelectedIndex())
+ "&geography="
+ geographyListBox.getValue(geographyListBox
.getSelectedIndex());
System.out.println("The path made: " + path);
uploader.setServletPath(path);
}
};
[/sourcecode]
What we are doing in this is that we are passing all the values as a part of the request object to the server where they can be fetched along with the file.
Since for us the application is deployed on the app engine, we cannot make use of files. Luckily, again, GWTUpload has a servlet specifically for the app engine. We need to extend that and write our custom code as shown,
[sourcecode language=”java”]
public class GoogleDocsGAEUploadServlet extends AppEngineUploadAction {
…
@Override
public String executeAction(HttpServletRequest request, List sessionFiles) throws UploadActionException {
String response = "";
List<FileItem> itemsTobeUploaded = new ArrayList();
String description = getDescription(request);
System.out.println("The Description created is: " + description);
for (FileItem item : sessionFiles) {
if (item.isFormField() == false) {
itemsTobeUploaded.add(item);
}
}
for (FileItem fileItem : itemsTobeUploaded) {
try {
documentService.uploadFileToGoogleDocs(fileItem, description);
} catch (Exception e) {
// exception handling code here
}
}
// Remove files from session because we have a copy of them
removeSessionFileItems(request);
// Send your customized message to the client.
return response;
}
private String getDescription(HttpServletRequest request) {
String expiryDate = request.getParameter("expiryDate");
String reminderDate = request.getParameter("reminderDate");
String coe = request.getParameter("coe");
String geography = request.getParameter("geography");
return "EXPIRY="+expiryDate + "::" + "REMINDER-ONE=" + reminderDate + "::" + "COE=" + coe + "::" + "GEOGRAPHY=" + geography;
}
…
[/sourcecode]
The advantage of using the AppEngineUploadAction is that, instead of standard UploadAction is that it circumvents the following limitations of the app engine
- It doesn’t support writing to file-system, so this servlet stores fileItems in memcache using CacheableFileItem
- The request size is limited to 512 KB, so this servlet hardcodes the maxSize to 512
- The limit for session and cache objects is 1024 KB
- The time spent to process a request is limited, so this servlet limits the sleep delay to a maximum of 50ms
Also, we are fetching all the fields as a part of the request and forming a description out of it which we are finally passing to the document service. We would get into the internals of the document service in our next post when we talk about handling file operations on Google App Engine.
Inphina, as an expert on Google App Engine and Google Apps has helped more than 10 medium to large organizations help take advantage of the cloud by building, migrating or re-engineering their complex line of business applications to the cloud thereby making significant reductions in their capex expenditure. If you are interested in an assessment send us a mail at cloud@inphina.com
great tutorial… url of live demo ??