-
Introduction
There’s already an excellent JS client out there you can find it here, a good explanation on how to use it can also be find here, in fact, the idea behind this post is just to create a GWT wrapper on top of this amazing library. So let’s get our hands dirty.
You will need:
- An application server, get the best one here
- A JMS broker, get HornetQ or ActiveMQ from the links above
- GWT 2.2
- This post’s code can be found at : google-code
-
Configuring the brokers
<transportConnector name="websocket" uri="ws://0.0.0.0:61614"/><acceptor name="stomp-ws-acceptor"> <factory-class>org.hornetq.core.remoting.impl.netty.NettyAcceptorFactory</factory-class> <param key="protocol" value="stomp_ws"/> <param key="port" value="61614"/> </acceptor>
-
Implementation
package com.furiousbob.jms.client; import com.google.gwt.core.client.JavaScriptObject; /** * * @author Vinicius Carvalho * * An overlay on top of the StompJS message object. Messages can be created by using the * static method * */ public class Message extends JavaScriptObject { protected Message(){} public final native String getBody()/*-{ return this.body; }-*/; public final native Header getHeaders()/*-{ return this.headers; }-*/; public static final native Message create(String json)/*-{ return eval('(' + json + ')'); }-*/; } package com.furiousbob.jms.client; import com.google.gwt.core.client.JavaScriptObject; /** * * @author Vinicius Carvalho * * */ public class Header extends JavaScriptObject { protected Header(){} public final native String getDestination()/*-{ return this.destination; }-*/; public final native Integer getExpires()/*-{ return this.expires; }-*/; public final native String getSubscription()/*-{ return this.subscription; }-*/; public final native String getId()/*-{ return this["message-id"]; }-*/; public final native Integer getPriority()/*-{ return this.priority; }-*/; public final native Long getTimestamp()/*-{ return this.timestamp; }-*/; }
package com.furiousbob.jms.client; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.ScriptElement; import com.google.gwt.resources.client.ClientBundle; import com.google.gwt.resources.client.TextResource; public class StompJS { protected interface Resources extends ClientBundle { @Source("com/furiousbob/jms/stomp.js") TextResource stompjs(); } private static final Resources RESOURCES = GWT.create(Resources.class); private static boolean installed = false; public static synchronized void install() { if (!installed) { ScriptElement e = Document.get().createScriptElement(); e.setText(RESOURCES.stompjs().getText()); Document.get().getBody().appendChild(e); installed = true; } } private StompJS() { } }
public void onModuleLoad() { StompJS.install(); }
package com.furiousbob.jms.client; /** * * @author Vinicius Carvalho * */ public interface ConnectionCallback { /** * Called on connection to the JMS broker */ void onConnect(); /** * Called on an error or disconnection from the broker * @param cause - the reason behind the error */ void onError(String cause); /** * Called on a disconnection initiated from the client */ void onDisconnect(); } package com.furiousbob.jms.client;
In order to receive the message you should also implement an interface: MessageListener (just like you would with regular JMS), that interface is going to be called when a message is received:
package com.furiousbob.jms.client; /** * * @author Vinicius Carvalho * Marker interface that receives messages from the broker */ public interface MessageListener { public void onMessage(Message message); }
Now to the main class that is going to handle all the plumbing:
Now before you continue… This is a working in progress, there’s a lots to improve, no exceptions, no way to provide the login/pwd so far.
I’ll do my best to update the project files and make this at least an usable project. But the whole intention here was just to prove a concept more than providing an 3PP library.
package com.furiousbob.jms.client; /** * * @author Vinicius Carvalho * Wraps the Stomp JS client. * * This class connects to a remote broker and maintains a list of subscriptions. * Each subscription is associated if a MessageListener. Only one instance of this class * should exist per server, and subscriptions to different channels (Queue/Topic) should be * created as required. * */ public class StompClient { String url; ConnectionCallback callback; public StompClient(String url){ this(url,null); } public StompClient(String url, ConnectionCallback callback){ this.url = url; this.callback = callback; init(); } private native final void init()/*-{ $wnd.subscriptions = new Array(); $wnd.stompClient = $wnd.Stomp.client(this.@com.furiousbob.jms.client.StompClient::url); }-*/; /** * Connects to the JMS broker and invokes the callback interface if one was provided */ public native final void connect() /*-{ var that = this; var onsuccess = function(){ that.@com.furiousbob.jms.client.StompClient::onConnect()(); } var onfail = function(cause){ that.@com.furiousbob.jms.client.StompClient::onError(Ljava/lang/String;)(cause); } $wnd.stompClient.connect('guest','guest',onsuccess,onfail); }-*/; /** * Disconnects from the server and removes any subscriptions that are still active */ public native final void disconnect() /*-{ var that = this; if($wnd.subscriptions.length > 0){ for(var i=0;i<$wnd.subscriptions.length;i++){ $wnd.stompClient.unsubscribe($wnd.subscriptions[i]); } } var ondisconnect = function(){ that.@com.furiousbob.jms.client.StompClient::onDisconnect()(); } $wnd.stompClient.disconnect(ondisconnect); }-*/; /** * Subscribes the given listener to a certain destination. An identifier from the subscription is returned * that id should be used to unsubscribe from the channel * @param channel - The name of the Queue/Topic * @param listener - Implementation of your message Listener * @return Subscription Identifier */ public native final String subscribe(String channel, MessageListener listener)/*-{ var onmessage = function(message){ listener.@com.furiousbob.jms.client.MessageListener::onMessage(Lcom/furiousbob/jms/client/Message;)(message); } var id = $wnd.stompClient.subscribe(channel,onmessage); $wnd.subscriptions.push(id); return id; }-*/; /** * Unsubscribe from the channel. * @param subscriptionId The id of the subscription to be unsubscribed */ public native final void unsubscribe(String subscriptionId)/*-{ var idx = $wnd.subscriptions.indexOf(subscriptionId); $wnd.subscriptions.splice(idx,1); $wnd.stompClient.unsubscribe(subscriptionId); }-*/; /** * Sends a message with the headers to a destination * @param destination - The id of the Channel * @param message - The Message with the headers */ public native final void send(String destination, Message message)/*-{ $wnd.stompClient.send(destination, message.headers, message.body); }-*/; /** * Sends a text to a destination. * @param destination - The id of the Channel * @param body - The message contents as a String */ public native final void send(String destination, String body) /*-{ $wnd.stompClient.send(destination,{},body); }-*/; void onConnect(){ if(callback != null){ callback.onConnect(); } } void onError(String cause){ if(callback != null){ callback.onError(cause); } } void onDisconnect(){ if(callback != null){ callback.onDisconnect(); } } }
-
Using
<inherits name="com.furiousbob.jms.StompJMS"></inherits>ConnectionCallback cbk = new ConnectionCallback() { @Override public void onError(String message) { Window.alert(message); } @Override public void onDisconnect() { Window.alert("Disconnected from server"); } @Override public void onConnect() { Window.alert("Connected"); } }; final StompClient client = new StompClient("ws://localhost:61614/stomp",cbk); client.connect(); final MessageListener listener = new MessageListener() { @Override public void onMessage(Message message) { //Do something cool with your message } }; client.subscribe("jms.queue.ExampleQueue", listener);
Sending messages is even simpler:
client.send("jms.queue.ExampleQueue", "Hello World"); //or client.send("jms.queue.ExampleQueue", Message.create("{'body':'This is a test', 'headers':{'priority':1}}"));
-
Conclusions
This was more a Proof Of Concept than a real project use. I’m still experimenting those things. Websocket support is really under a threat right now, as Firefox dropped the support on the final relase of Firefox 4.
The best alternative for a push model is to use Errai, it does not use websockets, and hence you have a better support on browsers. But again, the idea of this post is just to prove an idea
I guess that is it, enjoy the code, and don’t forget to comment, post bugs on the project site, I hope to get a stable version of it in a few weeks.
Happy Coding
