Friday, January 29, 2010

WebSphere + GWT + Comet

IBM supports Reverse AJAX (AKA Comet) in a form of WebSphere Feature Pack for Web 2.0 for WebSphere Application Server 6.0+. It is shipped by default with the latest fix packs for WAS 6.1 and in all 7.0 versions. Actually this Feature Pack has a lot of nice Web 2.0 features, but at the moment we are interested in Web Messaging Service only.

To install and enable FP you need to follow the documentation, it won't take long. As the result you will get the ability to push data to your client's web browser by publishing it to the special JMS topics. As an alternative, you may also use a more specialized API for doing this, but the Publisher object is stored in the Servlet context, therefore it can be somewhat tricky to access it from an arbitrary part of your application, which is not true with JMS.

I assume that you have successfully installed and tested your Web Messaging, and now want to upgrade your QuoteStreamer sample application to GWT. For example, you have a widget and want its methods to be called when some data arrives from the backend.

It is possible to use JSNI to achieve exactly this goal. The idea is simple: when you need to push some object to the client (for example, out of your Message Driven Bean), you first serialize it using GWT RPC mechanism, then put the resulting string to the appropriate JMS Topic. This string is wrapped to JSON envelope, sent to the client via WFP and unwrapped there via Dojo. Your JavaScript listener should be triggered at this moment. But as we use GWT for all our JavaScript programming, we should implement that listener as JSNI snippet:
private native void initCallbackAndSubscribe (String someParam) /*-{
if ($wnd.dojox) {
$wnd.dojox.cometd.subscribe("/test/" + someParam, this,
function (comet) {
module.@com.test.MyCoolWidget::onReverseAjax(Ljava/lang/String;)(comet.data)
});
}
}-*/;

// This is a normal Java code
public void
onReverseAjax(String msg) throws SerializationException {
SerializationStreamReader r = ((SerializationStreamFactory) svc).createStreamReader(msg);
Object data = r.readObject();
// Do something with that data arrived
}
As you see, the data is deserialized later in your normal Java code called by this listener. That's it. If you have initialized everything right (in your index.jsp or some similar place), this scheme should work fine. Now I will show you how to send data using GWT RPC. It should be simple via RPC.encodeResponseForSuccess method, but there is one problem - it requires SerializationPolicy object for the security reasons. This mechanism restricts serialized objects to the limited set described in *.gwt.rpc files, generated during compilation. Another problem is that there can be several such files. I haven't found a good way to get the SerializationPolicy other than by concatenating these files altogether:
public SerializationPolicy getSerializationPolicy() throws Exception {
String result = "";
for (File file : new File(PATH_TO_YOUR_GENERATED_STUFF).listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.endsWith(".rpc");
}
})) {
result += (FileUtils.readFileToString(file));
}
return SerializationPolicyLoader
.loadFromStream(new ByteArrayInputStream(result
.getBytes("UTF-8")), null);
}

public void publishObject(String topic, Object obj, Method method) throws Exception {
String r = RPC.encodeResponseForSuccess(method, obj, getSerializationPolicy());
publishStringViaJms(topic, r.toString());
}
I forgot to mention that RPC.encodeResponseForSuccess requires a Method instance, but it is used only to extract the return value type, so any method returning the object of the obj.getClass() type will do.

In general, there are some good things about such WebSphere + GWT + Comet integration, the major one is that it uses WFP, which is a pretty powerful Comet server, compared to some custom-built solutions. For example (at least on WAS 7.0), it uses connection multiplexing, nio and all that cool stuff you expect from the modern web server. Also, it naturally integrates into your ESB, so you can use it in your Process Server applications with ease. Some limitations are obvious too, for example it won't work in GWT Hosted Mode with Tomcat. Also it seems to me that this should be implemented somehow simpler, but I was unable to find it. So if you have any other ideas, please give me a clue.

P.S.: Actually, there are some alternatives to this approach. For example you can use gwt-comet-streamhub to enable Reverse AJAX for your GWT applications. But it provides its own Comet server called StreamHub which is definitely not a part of the IBM WebSphere product line :)

P.P.S.: The described method should work with other Comet-enabled J2EE servers like Jetty with minor modifications.

P.P.P.S.: There is left one more issue with security. As for now, it is not really clear for me how to implement role-based security for Comet... I can think of using several update servlets at the same time, and it should allow me to use webapp security. But this issue definitely needs further investigation.

10 comments:

  1. An alternative would be using Errai-Bus: http://jboss.org/errai

    ReplyDelete
  2. Hello Heiko,

    Thanks for the link, Errai-Bus looks truly promising, but as I see from the documentation:

    ...This approach may be extremely disruptive to some appserver deployments and depending on some security policies may not work at all. To that end, we are working on implementing specific abstractions to support continuations in the Servlet 3.0 draft specification, as well as implementing adapters for vendor-specific implementations like JBoss AIO and Jetty Continuations...

    ...it won't work with IBM WebSphere Comet implementation at the moment, and I have to use it right now.

    ReplyDelete
  3. GWT 2.0 uses Jetty not tomcat anymore... so perhaps getting GWT+Comet working via Development Mode (aka Hosted Mode).

    ReplyDelete
  4. That's a good point! The actual server-specific code resides in publishStringViaJms method not shown here (in WebSphere it just puts the message to JMS Topic, don't know about how it's done in Jetty).

    ReplyDelete
  5. An alternative would also be using GWTEventService (http://gwteventservice.googlecode.com/). :-) By the way, where do you have the cool comet picture from? :-)

    ReplyDelete
  6. Great post. Might be a good idea to look at Atmosphere -> http://atmosphere.dev.java.net , which could be used as a portable comet layer.

    ReplyDelete
  7. Hello Jeanfrancois,

    this indeed is a very nice project! I looked at their GWP plugin example (http://code.google.com/p/atmosphere-gwt-comet/source/browse/examples/gwt-comet-test/) and it seems to be doing exactly the same things I have to do manually :)

    I haven't looked at the server-side part yet, and the only two (minor) problems I see with this GWT plugin are: lack of documentation and too many redundant classes (kind of empty Broadcasters and Serializers), though I'm not sure if you really need them to get this running.

    Thanks for this one :)

    ReplyDelete
  8. This image of the astronaut witnessing the earth's destruction is mine. It is titled "An Earth Shattering Experience," and it is published here without permission. While I am flattered that you like the image I created enough to publish it, I do require that proper credit be given.

    Either add the tag line:

    © 2008 Dean Reeves II

    AND the link:

    http://deanreevesii.deviantart.com/art/An-Earth-Shattering-Experience-146786821?q=gallery%3Adeanreevesii%2F23937479&qo=0

    or remove it. Whichever you prefer. Failure to do so will result in further action to protect my copyright.

    Thank you.

    ReplyDelete
  9. Dean, thanks for the reminder. I beg my pardon for violating your copyright and removed the image from this post. I should be more careful next time.

    ReplyDelete
  10. I appreciate your prompt response. I don't like being hard nosed about these things, but whoever began this image running around the internet cropped the property stamp off, leaving no trail of ownership back to me. I am flattered to see my image being used online, I only wish that it had proper credit attached.

    Anyhow, thanks again.

    -Dean

    ReplyDelete