Asynchronous Update in Echo2
I've been sitting on my second in a series of Echo2 tutorials a little too long. (What can I say? Busy, busy.) The core of the tutorial is all about asynchronous updates -- updating the browser from the server as if by magic, without a click or a mouseover. There's a bit of a trick to doing this in Echo2 and understanding it depends on understanding how the framework handles asynchronous communication with the browser. (I apologize in advance that this isn't as verbose as a tutorial, but I thought it might be useful to someone in this form nonetheless.)
First you have to understand that this async update from the server isn't actually a server push but rather a client pull. On the client side, if asynchronous updates are turned on in an Echo2 app (more on how they are turned on later), then the browser polls the server (by default every .5 seconds) for updates. During updates, the Client Engine locks the UI, i.e. you can't click, drag or do anything while an update is taking place (so don't make your tasks long running). On the server side, async updates are turned on by creating a task queue. When the Client Engine contacts the server, those tasks in the queue are processed, one by one, and if any modifications are made to the UI model in on the server, those changes are propagated to the client. The reason for the queue is that Echo2 abides by the pre 1.3 J2EE spec that says that you cannot spawn threads that are not handled by the container. Therefore all modifications of UI components have to take place in a servlet thread. (In fact, if you try to modify any UI components from another thread, Echo2 will throw and exception.) The queue simply allows you to line up tasks that then are executed in the appropriate thread.
The act of creating such a queue causes the server to update the client so that the Client Engine periodically polls the server. If you destroy all of the queues, the client stops polling. (Of course a client-server communication has to take place in order for the client to be notified of this.)
But how do you put tasks in the queue? One naive approach would be to spawn a thread that periodically puts a task in the queue. The problem with this approach is that the queue is only cleared when the client polls the server. If you have a thread putting tasks in the queue and the client navigates away from your page, you could have a thread filling up the queue for quite some time. You also have to put lifecycle methods in place to clean up your thread and any associated resources. The cleaner solution is to override the hasQueuedTasks method of the ApplicationInstance class. This method is called every time an async poll comes in from the client.
Here is my simple solution to managing the async push in Echo2. Note that there are a few things missing here, such as clearing or pausing all scheduled tasks, catching exceptions, etc.. You'll have to decide some of those details on your own. Note that I've used a thread-safe set with a thread-safe iterator -- very handy:
package ajaxsample.app.echo2;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import nextapp.echo2.app.ApplicationInstance;
import nextapp.echo2.app.TaskQueueHandle;
import nextapp.echo2.app.Window;
import nextapp.echo2.webcontainer.ContainerContext;
public class AjaxSampleApplication extends ApplicationInstance {
private Window mainWindow;
private TaskQueueHandle tqh;
private final static int ASYNC_PERIOD = 5000; // 5 seconds
private Set<UpdateTask> tasks;
@Override
public Window init() {
tasks = new CopyOnWriteArraySet<UpdateTask>();
tqh = createTaskQueue();
// have the client poll every 5 seconds
setUpdateInterval(tqh, ASYNC_PERIOD);
mainWindow = new Window();
mainWindow.setContent(new DefaultForm());
mainWindow.setTitle("Ajax Sample");
return mainWindow;
}
/**
* Set the polling interval from the client for the task queue.
*
* @param tqh The task queue to modify
* @param interval polling interval in milliseconds for client engine
*/
private void setUpdateInterval(TaskQueueHandle tqh, int interval) {
ContainerContext containerContext = (ContainerContext) getContextProperty(ContainerContext.CONTEXT_PROPERTY_NAME);
containerContext.setTaskQueueCallbackInterval(tqh, (int) interval);
}
/**
* Add an UpdateTask to the default taskQueue schedule
* @param r
*/
public void schedulePeriodic(UpdateTask r) {
tasks.add(r);
}
/**
* Remove a task from the default task schedule.
*
* @param r
* @return was the task present in the default task schedule
*/
public boolean unschedulePeriodic(UpdateTask r) {
return tasks.remove(r);
}
/**
* Insert tasks from out default schedule into the default queue.
*
* @return do we have queued tasks?
* @see nextapp.echo2.app.ApplicationInstance#hasQueuedTasks()
*/
@Override
public boolean hasQueuedTasks() {
Iterator<UpdateTask> it = (Iterator<UpdateTask>) tasks.iterator();
while (it.hasNext()) {
UpdateTask task = it.next();
if (task.ready()) {
enqueueTask(tqh, task);
}
}
return super.hasQueuedTasks();
}
/**
* Best practice. Returns the current application instance cast to the
* correct type.
*
* @return the active <code>AjaxSampleApplication</code>
*/
public static AjaxSampleApplication getApp() {
return (AjaxSampleApplication) getActive();
}
}
How would you make use of this queueing mechanism? Here's an example that updates a label every 5 seconds:
package ajaxsample.app.echo2;
public interface UpdateTask extends Runnable {
boolean ready();
}
[...]
private static final long UPDATE_INTERVAL = 5000;
private Label countLabel = new Label();
private int count = 0;
[...]
// create an update task for the counter
UpdateTask task = new UpdateTask() {
private long lastRun = 0; // in forever
public boolean ready() {
// more time has passed than our interval
return UPDATE_INTERVAL <= (System.currentTimeMillis() - lastRun);
}
public void run() {
// update the runtime
lastRun = System.currentTimeMillis();
count++;
countLabel.setText("Counter = " + count);
}
};
// now schedule it
AjaxSampleApplication.getApp().schedulePeriodic(task);
That's quite a bit of code that doesn't really fit the component GUI model. This is one area where I think ZK gets it right where Echo2 gets it wrong: the whole async update business should be abstracted into a timer component (see the timer in the ZK demo). If someone needs a special async update processing that cannot be accomodated through a timer, they can always hack the hasQueuedTasks method.
