Archive for the ‘JEE’ Category

Share on TwitterDigg This
  • Introduction

One of the many challenges web developers face when building new applications, is to get notifications from the server, in a real push way. This has been a problem since the beginning of HTML. Not being able to push messages from the server to the client led us to create some alternatives (polling, comet, pigback) you name it, there are dozens of them out there. None of those unfortunately are real server push. This led the MVC pattern to be kinda awkward on the web, and for many years we said that was a one way MVC :)

With the advent of HTML 5 and the new websockets API a promise of change is on the horizon. The only problem is that so far, only Jetty 7 supports Websockets, and you have to write a special type of Servlet to handle this type of communication. So implementing it is not as simple as you would expect.

But there’s still hope out there. The two best JMS brokers out there (HornetQ and ActiveMQ) both support websockets connections. And if you stop to think a while, it makes way more sense in an architectural style to have your messages being pushed to your client using a JMS solution. Isn’t that one of the motivations behind JMS (loose coupling, distribution etc, etc, etc).
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

Setting up the brokers to support Websocket is pretty simple, please refer to each broker docs for in depth explanation.

ActiveMQ:

Edit your conf/activemq.xml and add a connector:

<transportConnector name="websocket" uri="ws://0.0.0.0:61614"/>

HornetQ:

Edit your hornet-configuration.xml

<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

As I said before, we are only gonna create an wrapper on top of Jeff Mesnil’s great JS library for Stomp. So let’s first create an overlay type for our message object. This is pretty straight forward. I’ve created two classes for this:

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;
	}-*/;
 
 
 
 
}

GWT Overlays allows us to use JS Objects as regular java objects on our code. Most of the contents of those objects are native code anyways. No worries so far …

Since we are going to use the JS Stomp library, you’ll have to install it on your GWT code. I’m sure there are hundreds of ways of doing that, I’ve just picked the one I found the best ok?

Pack the stomp.js file inside your project in a public folder, refer to this page to have an idea. So, in our case I’ll just put it under the main package folder.

Next, we’ll have to install it on our application. So the following class will do the trick once it’s install() method gets called:

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() {
	}
}

This code will get the JS file as an resource, and them manipulate the DOM structure of your page installing it, so you could use the classes of this project. I’d recommend installing this on your onLoad method of your entry-point:

public void onModuleLoad() {
		StompJS.install();
}

Before getting to the class itself I’ve just want to show a callback interface you may use within it. This interface will allow you to get notifications from the JS client upon some events. I believe the interface is pretty simple and easy to understand:

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

Now the easy and fun part :).To use the project, register this module on your gwt.xml file:

<inherits name="com.furiousbob.jms.StompJMS"></inherits>

Don’t forget to call the install on the StompJS class. To receive messages on your code:

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);

You can skip the MessageCallback if you don’t want to be notified of events.

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}}"));

As you can see, sending is even simpler :)

There’s still room for lots of improvements but, the general idea is having a pure “Java” code in your project to send/receive JMS Messages :D

The initial code is located at google-code. I’m sorry about the project mess, I’ll clean it with the time, mavenize it ;) and improve the classes and interfaces. But at least it’s there for you if you want to start playing with it.

  • Conclusions

This is just a start, there’s lots to improve here. One aspect is the connection control, due the nature of GWT projects, when you move to a different Place (aka Page) a better control on what should be done with the connection is desired. There’s improvements on the exception handling and the class methods of course.


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