AJAX Queue
I was thinking of doing a simple AJAX framework for myself using some concepts of GUI programming. Actually just one, the event queue. Please note I haven't done the implementation yet, this is just an idea at the moment. The advantange to performing this approach is that it allows a separation of event handling from the actual page. The development of the AJAX enabled page would be similar to that of a standard GUI application. Components:
- one JMS queue that resides on the presentation tier.
- one servlet to retrieve the queue data from the JMS queue and convert it to XML
- one servlet to retrieve a secure random number.
- a Javascript API to retrieve the queue data from the servlet and process the queue events.
- a JavaScript API to send data to the Servlet tier
- a Java API to put things into the JMS queue.
For those that don't understand the concept of "on the presentation tier," the basic idea is you would have a separate set of resources (which might be cheaper alternatives) that is used only to support whatever presentation tasks you might have. This is to allow separation of business concerns from presentation concerns. For example, a presentation tier database stores things that are too big or cumbersome to let session management handle. In the same way a temp file is used to put information rather than store things in memory and let the virtual memory manager handle the data. The presentation tier resources tend to be cheaper and less persistent, i.e., on application restart, all the temporary runtime data stored in there are deleted.In order to prevent queues from overflowing, each message in the queue should have an expiration set. The expiration should be no more than one minute depending on how long you expect clients to process the next event. The messages should also be mark as non-persisting, since we don't need them to stay if the queues happen to restart (since the application might be restarted as well anyway). QueueServlet The QueueServlet is an HttpServlet that retrieves event data from the JMS queue and transforms the data into an XML stream (possibly via JDOM or manual StringBuffers). This is the only place for the AJAX side to get responses from the server side. The servlet will implement the doPost() method and uses the following logic:
public void doPost(HttpServletRequest request, HttpServletResponse response) {
// requestId is set by the RequestIdServlet
String requestId = request.getHeader("requestId");
String sessionId = request.getSession(false).getId();
List queueData = new LinkedList(); // we don't do random access to the list
do {
// get the message from the JMS queue non blocking
Message m =
jmsQueue.getMessage("requestId = '" + requestId + "' and " +
"sessionId = '" + sessionId + "'");
if (m == null) {
break;
}
queueData.add(new AjaxEvent(m));
}
// Write the queue data as XML
writeQueueDataXml(queueData,response);
}This approach ensures that the largest possible chunk is sent for every request. This solves the issue of too many requests getting a small chunk of data.
RequestIdServlet
This servlet performs a SecureRandom call to get a random number to pass back to the browser. The purpose of the id is to allow multiple pages to the same website to be open at the same time. As the QueueServlet, it only supports the POST method.
AjaxQueue JavaScript
The AjaxQueue JavaScript API is a JS file provided by the framework. It is responsible for creating/managing the XmlHttpRequest object and dealing with infrastructure based tasks. The typical Ajax enabled page would look like this:
<script src="ajaxQueue.js" type="text/javascript"></script>
<script>
// define event handlers
function handleFooEvent(eventDomTree) {
// do some DOM manipulation
}
// register events
registerEventHandler("Foo", handleFooEvent);
registerEventHandler("Bar", handleBarEvent);
registerErrorEventHandler(handleErrorEvent);
// start up the queue
startQueue("QueueHandlerUrl", "RequestIdUrl");
</script>In earlier attempts of implementing this, I tried to use a more object oriented fashion. However, since the xmlHttpRequest.onreadystatechange function cannot have parameters passed into it, things just ended up in global variables.
The global variables in ajaxQueue.js are:
- xmlHttp - an XmlHttpRequest object built based on the browser
- eventHandlers - an associative array that maps event name to a handler
- errorEventHandler - a function reference that gets called when there is an error
- requestId - a unique number representing the requests made from the current page
- queueUrl - the URL to the QueueServlet
- queueTimeout - the time to sleep before the next queue query
xmlHttp.onreadystatechange = function () {
if (xmlHttp.readyState == 4) {
if (xmlHttp.status == 200) {
var events = xmlHttp.responseXML.getElementsByTagName("event");
for (var i = 0; i < events.length; ++i) {
var eventName = events[i].name
if (eventName == "history") {
// special event to create a virtual entry in
// the history to handle the back button
doHistoryEvent(events[i]);
}
eventHandler = eventHandlers[eventName];
if (eventHandler) {
eventHandler(events[i]);
}
}
window.setTimeout("processQueue()", queueTimeout);
} else {
errorEventHandler(xmlHttp.statusText);
}
}
}The startQueue function would look like this:
function startQueue(url, requestIdUrl, timeout) {
if (xmlHttp == null) {
// don't do anything if AJAX is not supported.
return;
}
requestId = getRequestId(requestIdUrl);
queueUrl = url;
queueTimeout = timeout
// create invisible iframe to handle history events
createHistoryIFrame();
processQueue();
}The processQueue() function that sends the request to the servlet would look like this:
function processQueue() {
xmlHttp.open("POST", queueUrl, true);
xmlHttp.setRequestHeader("requestId", requestId);
xmlHttp.send(null);
}One way to further reduce the load on the servers is to alter the queueTimeout with a more intelligent algorithm rather than a fixed amount. This would have to be application specific though.
Sending messages to the server
Requests to the server side always need to have the "requestId" so they know what to put into the queue. And there will be several pieces of information that is going to get passed into the backend for each request. So the following function is provided:
function sendMessage(url, messageData) {
if (xmlHttp == null) {
// ensure that nothing happens if AJAX is not supported.
return;
}
// create a new xmlHttpRequest for sending data
var sendXmlHttp = createXmlHttpRequest();
sendXmlHttp.open("POST", url);
sendXmlHttp.setRequestHeader("requestId", requestId);
sendXmlHttp.send(messageData);
// error handling goes here.
}The requests for the message sends are synchronous rather than asynchronous. This ensures that errors are captured properly if there are any.
Putting events into the queue
To put a message into the queue, the following code gets invoked:
AjaxQueue queue = null;
try {
queue = new AjaxQueue(servletContext, request);
queue.putEvent(e);
} finally {
if (queue != null) {
queue.close();
}
}Of course that code block is too big and there will be a lot of copy and paste if we just left it like that. So a utility class called AjaxQueueUtil with a method
AjaxQueueUtil.putEvent(ServletContext context, HttpServletRequest request, Event events...)that sends a list of events into the queue. This is provided to shield users from copying and pasting so many lines of code. Conclusion As specified earlier the advtange to performing this approach is that it allows a separation of event handling from the actual page. The development of the AJAX enabled page would be similar to that of a standard GUI application. If the browser does not support AJAX or even scripting, its okay since the pattern is non-invasive to HTML and developers can AJAXify things as needed. This pattern takes advantage of facilities within the J2EE spec and supports clustered environments and multiple windows on the same session. A large chunk of the logic is hidden within the API implementation to make things easier for the developers. However, there is no concrete implementation of this pattern yet.
