Wednesday, December 17, 2008

wicket and a slow running process in a separate thread

an AjaxLazyLoadPanel allows you to show the bulk of a page immediately and then have the remaining parts show up as they are loaded. however, if some parts of your page require slow data for example from web services, you should load the data in a separate background thread.

the reason being that creating components locks a session's pagemap for the duration. should you perform webservice calls in a web request thread, the rest of the page will be unresponsive until everything is rendered.

another issue is the fact that rfc 2616 says that browsers should use at most two persistent connections to connect to a server. if you have a bunch of AjaxLazyLoadPanels running at the same time they will prevent your clicks from getting through. for this wicket provides the concept of "channel" for it's ajax events. if you put your ALLP's all in the same channel, then you retain the other channel available to respond to your clicks.

besides doing your background data fetching in a separate thread, the request thread must obviously not block on the background thread.

the java 5.0 concurrency api provides some cool stuff for our situation, namely Future. the main issues are:
  1. Futures are not serializable
  2. how to clean up our cache and,
  3. how to poll the background thread and keep the pagemap unlocked.

AjaxLazyLoadPanel is essentially a wrapper for a behavior that is added as an anonymous inner class. also, the channel mentioned above is only settable by overriding an ajaxbehaviors getChannelName(); method.

so I just copypasted the original ALLP to be able to work on the behavior.

by adding a throttling decorator, and by just calling

target.addComponent(AjaxLazyLoadPanel.this);

in respond(ART target) I get my ALLP to come back every now and then and check up on me.

protected IAjaxCallDecorator getAjaxCallDecorator() {
return new AjaxCallThrottlingDecorator("throttle",
Duration.ONE_SECOND);
}

then if I can somehow check whether or not my background data is ready, I can solve issue #3.

to solve issue #2, I used guice-servlet's session scope. whether that makes sense depends on whether you have session specific info, like the guy's account balance, or generic stuff that applies to all your users.

and to go around the serialization issue, I put the futures in guice injected services.

I pass the guice service to a LoadableDetachableModel which has an additional method isReady() to check whether the background thread has finished retrieving the data.

I then pass my model to my own AjaxLazyLoadPanel which calls isReady() in respond() to see if the data is ready. if the data isn't ready: throttle some more. if it is ready, replace the placeholder with the actual component which can now access the slow data.

there's some nice opportunities for abstraction, such as creating a generic wrapper service which calls another service in a separate thread. an ALLP which eventually displays a Label can be packaged into a SlowDataLoadingAjaxLazyLoadLabel which can take it's parameters in its constructor without an anonymous subclass.

i put this in a wicket quickstart for anyone who wants to take a look. i welcome all comments as I was unable to find any solid point of reference for this kind of thing.

0 kommenttia: