пятница, 7 августа 2009 г.

Using DWR to download binary files

As a background I would say that DWR is a library written in Java and Javascript and is designed to facilitate the work with Ajax. This great library is designed to work primarily with the javascript on the side of the browser. By default,unfortunately, it is not able to download binary data from a server (although DWR can already upload to server out of the box). Of course, DWR simply is not designed primarily for downloading files from server.
However, it could be very convenient in some situations. For example, you do not want to add a new servlet to the project, and you want to use servlet DWR well suited for this. Or you want to use its convenient interface to access the methods of Java, in which the data for downloading could be formed, as in my case.
Anyway, after a few agonizing moments I have found a way to implement such a mechanism.
So what is required?
First, of course, we need to write java code that will build the required binary data and write it into output of server response. According to DWR Rules, a java-method will generate these data, which we will call from browser. The method will not return binary data. It will return a simple string marker, but more on that later.

So this is a code of our test class:


package test;

import java.io.IOException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRichTextString;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import uk.ltd.getahead.dwr.WebContextFactory;

public class ExcelGenerator {

public String getExcelTest() throws IOException {
final HSSFWorkbook wb = new HSSFWorkbook();
final HSSFSheet sheet = wb.createSheet("sheet_1");
HSSFRow tempRow = sheet.createRow((short) 0);
HSSFCell tempCell = tempRow.createCell((short) 0);
HSSFRichTextString tempCellValue = new HSSFRichTextString(
"Hello World!");
tempCell.setCellValue(tempCellValue);
HttpServletResponse response = WebContextFactory.get().getResponse();
response.setContentType("application/vnd.ms-excel");
response.setHeader("pragma", "public");
response.setHeader("pragma", "no-cache");
response.setHeader("Cache-Control", "cache");
response.setHeader("Cache-Control", "must-revalidate");
response.setHeader("Content-Disposition",
"download;filename=\"helloworld.xls\"");
ServletOutputStream servletOutputStream = response.getOutputStream();
wb.write(servletOutputStream);
servletOutputStream.flush();
servletOutputStream.close();
return "END_RESPONSE";
}

}


This code uses the Apache POI to generate a MS Excel workbook, which is our binary data. String END_RESPONSE is an indication of the fact that you do not need to write anything more to the output and it is used in the future.
Initially, DWR will attempt to return the string to the browser, but it is not desirable for us. In addition, after a call to our method of generating DWR will attempt to re-open the output to its own output, which would lead to an exceptional situation.
In order to avoid it, we will use the Plug-in Points for DWR. In the project file web.xml, in the section describing the servlet DWR, we add the initialization parameter in order to change org.directwebremoting.dwrp.PlainCallMarshaller used in response process:


xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
TestApp

dwr-invoker

org.directwebremoting.servlet.DwrServlet


org.directwebremoting.dwrp.PlainCallMarshaller
test.HelloPlainCallMarshaller




activeReverseAjaxEnabled
true



initApplicationScopeCreatorsAtStartup
true



maxWaitAfterWrite
-1



allowScriptTagRemoting
true


1


dwr-invoker
/dwr/*





Here are highlighted lines in web.xml file that are important to us now. That lines mean that the behaviour of the class org.directwebremoting.dwrp.PlainCallMarshaller will be assigned to the class test.HelloPlainCallMarshaller. Therefore the class test.HelloPlainCallMarshaller would participate in the chain of calls, if the method getExcelTest(or another) was called through DWR. This class extends the class PlainCallMarshaller and its method marshallOuntbout is performed after the method getExcelTest.


package test;

import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.directwebremoting.extend.Replies;
public class HelloPlainCallMarshaller extends org.directwebremoting.dwrp.PlainCallMarshaller{
public void marshallOutbound(Replies replies, HttpServletRequest request, HttpServletResponse response) throws IOException{
if(replies.getReplyCount() != 1 || !replies.getReply(0).getReply().toString().
equals("END_RESPONSE"))
super.marshallOutbound(replies, request, response);
}
}

The sole purpose of our method marshallOuntbout is to check that, whether we want to interrupt the chain of further challenges DWR or continue it. We need it because otherwise we would get an exception that the response output stream has already been opened. The method getExcelTest returns END_RESPONSE and our method marshallOutbound knows this and terminates further execution.
Just do not forget to add the class ExcelGenerator configuration DWR as usual, for example, through the file dwr.xml!

Client settings
Now that we have configured the server part of our application, we still have to deal with how to call a method from a browser. The problem is that we will not get this file if we used a standard call to method through DWR, because DWR uses AJAX to access the server. The solution is that when we invoke a method we can extract all the parameters that DWR sends in the request and then paste them into a dynamically created form. Then after submitting of the form, we have a chance to get our Excel file!
We can write a method that would be taken as an argument a call of the method to the server, but instead of that would implement AJAX call, made the above operations.

var DWRUtils = new Object();
DWRUtils.directPostDWR = function(genCall){
var form = document.createElement("form");
form.method = "POST";
document.body.appendChild(form);
dwr.engine.beginBatch();

genCall();

var map = dwr.engine._batch.map;
form.action = dwr.engine._defaultPath + dwr.engine._ModePlainCall +
map["c0-scriptName"]+ "." + map["c0-methodName"] + ".dwr";
map["batchId"] = dwr.engine._nextBatchId;
for(var param in map){
var input = document.createElement('input');
input.type = 'hidden';
input.name = param;
input.value = map[param];
form.appendChild(input);
}
form.submit();
if(typeof form.removeNode != "undefined")form.removeNode(true); // IE
else document.body.removeChild(form); // FF
dwr.engine._batch = null;
}

Now we can download like this!

DWRUtils.directPostDWR(function(){
ExcelGenerator.getExcelTest();
}
);

четверг, 30 июля 2009 г.

Dojo. How to leaf over data, dojox grid.

Of course, Dojo - a great visual web framework that allows to do different things on your web page and at the same time does not require to spend tons of time to write this. Dojo has a lot, but ... certainly not all. For example, I looked once an opportunity to leaf over a long list of data in a component dojox.Grid like this:

<< < 1 2 3 4 5 6 > >>

Thus, I decided to write a component to list the dojo grid, i.e. dojox.Grid. Additionally, I set myself the following task list:
  1. Loading data from server with portions that appear at this time on the grid.
  2. Messages on the need for waiting for the user.
  3. The ability to use links and buttons as the variants of interface.
  4. The possibility to use the usual URL-query either DWR (Direct Web Remoting Framework) for receiving data from the server.
Now, I introduce for your attention, what I created - a component dojox.grid.GridPaginator, performing all of what I just said
Click to download
I hope it will help you.