Code I/O

A topnotch WordPress.com site

5 Minutes on Java: GWT consumer + Server push tutorial

11 Comments

The previous 2 posts on server push demonstrated the idea of push information to the consumers with Java services and Flex client. However, many might argue that a generic consumer should work on any device and it is better to develop and support such framework.  In an environment where rapid prototyping is the key driver, with a responsibility to drive development frameworks and technologies; the constraints can be many.

On that pretext, I tried GWT framework to see if we can do things quickly and effectively as done with Flash, and I find it to be promising in many ways.

  • It generates JavaScript which is compact and optimized and can be obfuscated.
  • Can run on various browsers and platforms.
  • Development cost can be reduced as most enterprise developers are experienced in Java.
  • At the same time, can create proof-of-concepts quickly and efficiently.

However, one challenge I faced was getting the right blend of components to work with GWT.  Firstly, I tried ActiveMQ AJAX, which was good to begin with, however, when working with JQuery libraries, JavaScript really messes up the namespaces.  Secondly, was trying hard to see if Comet can save the day, but only in vain.  It became more messier as libraries were incompatible with some of the third-party components required in the backend.

After a lot of thoughts and architectural considerations, I finally decided to reuse most of the work done on the back-end (which is critical); which happens to be ActiveMQ (FUSE); Yet had to give up the idea of Flash moving forward; this  became a key driver for future; which means that rapid prototyping must be supported as a fundamental requirement, at the same time keeping the technology stack for building prototypes and learning curve as lean as possible.

Applications, be it RESTful or Presence based, it must inter-operate, change and grow with changing requirements.  So how do we put all of these together?

  • Jersey/Restlet fits the stack perfectly for exposing RESTful API
  • ActiveMQ becomes a preferred choice for messaging; which is scalable and reliable as well.

Bring them together with GWT for HTML(5) client, and stage is set for developing applications for the future.  There are challenges with data-binding that you’ll miss if you’re used to Adobe Flex a lot.  In my opinion that could be solved in few years.

Having said enough so far, let’s see the sample code to make this work with GWT; I used GWT Eventing to implement message handling on the client.

Download it from gwteventservice google code

Make sure you have the following inherits in *.gwt.xml file.

<inherits name='com.google.gwt.user.User' />
<inherits name='com.google.gwt.xml.XML' />
<inherits name='de.novanic.eventservice.GWTEventService' />

Add the following files to your gwt client package.
File: MessengerRemoteService

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;

import de.novanic.eventservice.client.event.domain.Domain;
import de.novanic.eventservice.client.event.domain.DomainFactory;

@RemoteServiceRelativePath("MessengerRemoteService")
public interface MessengerRemoteService extends RemoteService {
	public static final Domain SERVER_MESSAGE_DOMAIN = DomainFactory.getDomain("server_message_domain");

	/**
	 * Utility class for simplifying access to the instance of async service.
	 */

	public void start();

	public static class Util {
		private static MessengerRemoteServiceAsync instance;
		public static MessengerRemoteServiceAsync getInstance(){
			if (instance == null) {
				instance = GWT.create(MessengerRemoteService.class);
			}
			return instance;
		}
	}
}

File: MessengerRemoteService

import com.google.gwt.user.client.rpc.AsyncCallback;

public interface MessengerRemoteServiceAsync {
	void start(AsyncCallback<Void> callback);
}

File: MessageEvent

import de.novanic.eventservice.client.event.Event;

public class MessageEvent implements Event {
	/**
	 *
	 */
	private static final long serialVersionUID = 1L;
	protected String message = null;

	public String getMessage() {
		return message;
	}

	public void setMessage(String message) {
		this.message = message;
	}

	public MessageEvent(String message){
		super();

		this.message = message;
	}

	public MessageEvent(){}
}

Finally, add the code to your entry point that will load the EventListener. This code will be the main event receiver, hence should be able use this to receive events from the server and let the visualization component render it based on the views.

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.xml.client.DOMException;
import com.google.gwt.xml.client.Document;
import com.google.gwt.xml.client.Element;
import com.google.gwt.xml.client.NodeList;
import com.google.gwt.xml.client.XMLParser;

import de.novanic.eventservice.client.event.Event;
import de.novanic.eventservice.client.event.RemoteEventService;
import de.novanic.eventservice.client.event.RemoteEventServiceFactory;
import de.novanic.eventservice.client.event.listener.RemoteEventListener;

public class ConnectorSample {
	public void connect(){
		RemoteEventService remoteEventService = RemoteEventServiceFactory.getInstance().getRemoteEventService();
		remoteEventService.addListener(MessengerRemoteService.SERVER_MESSAGE_DOMAIN,
			new RemoteEventListener() {
				@Override
				public void apply(Event anEvent) {
					if(anEvent instanceof MessageEvent){
						String message = ((MessageEvent)anEvent).getMessage();

						try {
							NodeList nodes = null;

							try {
								Document messageDocument = XMLParser.parse(message);
								nodes = messageDocument.getElementsByTagName("some tag name");
							}catch(DOMException domex){
								throw new Exception("XMLParseException while parsing message");
							}

							if(nodes != null && nodes.getLength() > 0){
								for(int index = 0; index < nodes.getLength(); index++){
									Element element = (Element) nodes.item(index);

									// Update DataModel
									// Updae View

								}
							}
						}
						catch(Exception e){
							GWT.log(e.getMessage());
						}
					}
				}
			}
		);

		MessengerRemoteService.Util.getInstance().start(new VoidAsyncCallback());
	}

	private class VoidAsyncCallback implements AsyncCallback<Void>
    {
        public void onFailure(Throwable aThrowable) {}

        public void onSuccess(Void aResult) {}
    }
}

Add the following file to your gwt server package
File: MessengerRemoteService

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

import org.apache.activemq.command.ActiveMQTextMessage;
import org.onesun.sfs.client.MessengerRemoteService;
import org.onesun.sfs.shared.event.MessageEvent;

import de.novanic.eventservice.client.event.Event;
import de.novanic.eventservice.service.RemoteEventServiceServlet;

public class MessengerRemoteServiceImpl extends RemoteEventServiceServlet
	implements MessengerRemoteService, MessageListener
{
	/**
	 *
	 */
	private static final long serialVersionUID = 1L;

	public void init(){
	}

	@Override
	public void start() {
	}

	private void sendToClient(Event event){
		addEvent(MessengerRemoteService.SERVER_MESSAGE_DOMAIN, event);
	}

	@Override
	public void onMessage(Message message) {
		String messageText = null;
		try {
			messageText = ((TextMessage)message).getText();
		} catch (JMSException e) {
			e.printStackTrace();
		}

		if(messageText != null && messageText.length() > 0){
			if(message instanceof ActiveMQTextMessage) {
				sendToClient(new MessageEvent(messageText));
			}
		}
	}
}

Now connect the code with configurations for the Web App to work well.

  <servlet>
    <servlet-name>EventService</servlet-name>
    <servlet-class>
      de.novanic.eventservice.service.EventServiceImpl
    </servlet-class>
  </servlet>

  <servlet>
  	<servlet-name>MessengerRemoteService</servlet-name>
  	<servlet-class>PACKAGE_NAME.MessengerRemoteServiceImpl</servlet-class>
  </servlet>

  <servlet-mapping>
  	<servlet-name>MessengerRemoteService</servlet-name>
  	<url-pattern>/feedvu/MessengerRemoteService</url-pattern>
  </servlet-mapping>

  <servlet-mapping>
    <servlet-name>EventService</servlet-name>
    <url-pattern>/feedvu/gwteventservice</url-pattern>
  </servlet-mapping>

Update the applicationcontext.xml for spring injections

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:util="http://www.springframework.org/schema/util"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:jms="http://www.springframework.org/schema/jms"
	xmlns:p="http://www.springframework.org/schema/p"
	xmlns:s="http://www.springframework.org/s"
	xmlns:context="http://www.springframework.org/schema/context"

	xsi:schemaLocation="http://www.springframework.org/schema/beans
 		http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
 		http://www.springframework.org/schema/context
 		http://www.springframework.org/schema/context/spring-context-2.5.xsd
 		http://www.springframework.org/schema/jms
 		http://www.springframework.org/schema/jms/spring-jms-2.5.xsd
 		http://www.springframework.org/schema/util
 		http://www.springframework.org/schema/util/spring-util-2.0.xsd
 		http://camel.apache.org/schema/spring
 		http://camel.apache.org/schema/spring/camel-spring.xsd">

	<camelContext xmlns="http://camel.apache.org/schema/spring" />

	<!-- ActiveMQ JMS -->
	<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"
		p:brokerURL="tcp://localhost:61616" />

	<jms:listener-container container-type="default" destination-type="topic" connection-factory="connectionFactory" acknowledge="auto">
		<jms:listener destination="feed-message-topic" ref="messengerRemoteService" method="onMessage" />
	</jms:listener-container>

	<bean id="messengerRemoteService" init-method="init" class="PACKAGE_NAME.MessengerRemoteServiceImpl">
	</bean>
</beans>

Finally startup the services and send messages to the topic using FUSE admin, the GWT client should render the messages as they are pushed on the Bus.

Summary and some thoughts:

Flash will still remain a way to quickly develop proof-of-concepts.  It provides a way to seamlessly bind data and visualization.  It is good to turn vapor-ware thoughts into tangible concepts; which is a good proposition in a life-cycle of prototyping.

On the other hand, if I were to build concepts that need to work on non-flash devices; then have to look into GWT like frameworks (since coming from Java world).  There are challenges with data-binding to visualization, rendering of changing data can be challenging.  HTML5 features of client side storage can help to build tactics on the client to resolve certain limitations.

In either case, depending on the time available, features of the application, and the case to demonstrate on diverse platforms, you can decide one over the other.

Server Push is essential for messaging; and clients must be built with the future in mind.  I’m sure you’ll find the samples for both frameworks simple and reusable.

Advertisements

11 thoughts on “5 Minutes on Java: GWT consumer + Server push tutorial

  1. This is very nice, I will definitely store it as a perfect reference.

  2. I can’t run this at Eclipse?

    “Update the applicationcontext.xml for spring injections”
    “applicationcontext.xml” equal to project.gwt.xml?
    Place the code inside the or outside?

    • applicationContext.xml and project.gwt.xml are different.

      Add the folder (which contains applicationContext.xml) to classpath in your eclipse project preferences.
      Open Run/Debug configuration -> Go to Classpath tab -> Select User Entries -> Click on Advanced -> Add Folders -> Choose the folder where applicationContext.xml resides.

  3. Thanks for your help, sorry about that I have no experience on GWT. There are two exceptions occur during execute.

    java.lang.ClassNotFoundException: de.novanic.eventservice.service.EventServiceImpl

    I was already solve this by copy the file from
    C:eclipse,libgwteventservice-1.1.1gwteventservice-1.1.1.jar
    to the
    C:UsersXXXDesktopprojectwarWEB-INFlib

    I use the same method on the following exception, but it is not work
    java.lang.ClassNotFoundException: org.onesun.sfs.server.MessengerRemoteServiceImpl

    C:eclipse,libjms1.1libjms.jar
    This is the jms1.1 downloaded from the internet.

  4. Here is the full console statement.

    Initializing AppEngine server
    Logging to JettyLogger(null) via com.google.apphosting.utils.jetty.JettyLogger
    Successfully processed C:UserskannifDesktopchatroomwarWEB-INF/appengine-web.xml
    Successfully processed C:UserskannifDesktopchatroomwarWEB-INF/web.xml
    [WARN] EXCEPTION
    java.lang.ClassNotFoundException: org.onesun.sfs.server.MessengerRemoteServiceImpl
    at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
    at com.google.appengine.tools.development.IsolatedAppClassLoader.loadClass(IsolatedAppClassLoader.java:151)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
    at org.mortbay.util.Loader.loadClass(Loader.java:91)
    at org.mortbay.util.Loader.loadClass(Loader.java:71)
    at org.mortbay.jetty.servlet.Holder.doStart(Holder.java:73)
    at org.mortbay.jetty.servlet.ServletHolder.doStart(ServletHolder.java:242)
    at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50)
    at org.mortbay.jetty.servlet.ServletHandler.initialize(ServletHandler.java:685)
    at org.mortbay.jetty.servlet.Context.startContext(Context.java:140)
    at org.mortbay.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1250)
    at org.mortbay.jetty.handler.ContextHandler.doStart(ContextHandler.java:517)
    at org.mortbay.jetty.webapp.WebAppContext.doStart(WebAppContext.java:467)
    at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50)
    at org.mortbay.jetty.handler.HandlerWrapper.doStart(HandlerWrapper.java:130)
    at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50)
    at org.mortbay.jetty.handler.HandlerWrapper.doStart(HandlerWrapper.java:130)
    at org.mortbay.jetty.Server.doStart(Server.java:224)
    at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50)
    at com.google.appengine.tools.development.JettyContainerService.startContainer(JettyContainerService.java:185)
    at com.google.appengine.tools.development.AbstractContainerService.startup(AbstractContainerService.java:149)
    at com.google.appengine.tools.development.DevAppServerImpl.start(DevAppServerImpl.java:219)
    at com.google.appengine.tools.development.gwt.AppEngineLauncher.start(AppEngineLauncher.java:119)
    at com.google.gwt.dev.DevMode.doStartUpServer(DevMode.java:431)
    at com.google.gwt.dev.DevModeBase.startUp(DevModeBase.java:1053)
    at com.google.gwt.dev.DevModeBase.run(DevModeBase.java:795)
    at com.google.gwt.dev.DevMode.main(DevMode.java:282)
    [ERROR] javax.servlet.ServletContext log: unavailable
    javax.servlet.UnavailableException: org.onesun.sfs.server.MessengerRemoteServiceImpl
    at org.mortbay.jetty.servlet.Holder.doStart(Holder.java:79)
    at org.mortbay.jetty.servlet.ServletHolder.doStart(ServletHolder.java:242)
    at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50)
    at org.mortbay.jetty.servlet.ServletHandler.initialize(ServletHandler.java:685)
    at org.mortbay.jetty.servlet.Context.startContext(Context.java:140)
    at org.mortbay.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1250)
    at org.mortbay.jetty.handler.ContextHandler.doStart(ContextHandler.java:517)
    at org.mortbay.jetty.webapp.WebAppContext.doStart(WebAppContext.java:467)
    at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50)
    at org.mortbay.jetty.handler.HandlerWrapper.doStart(HandlerWrapper.java:130)
    at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50)
    at org.mortbay.jetty.handler.HandlerWrapper.doStart(HandlerWrapper.java:130)
    at org.mortbay.jetty.Server.doStart(Server.java:224)
    at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50)
    at com.google.appengine.tools.development.JettyContainerService.startContainer(JettyContainerService.java:185)
    at com.google.appengine.tools.development.AbstractContainerService.startup(AbstractContainerService.java:149)
    at com.google.appengine.tools.development.DevAppServerImpl.start(DevAppServerImpl.java:219)
    at com.google.appengine.tools.development.gwt.AppEngineLauncher.start(AppEngineLauncher.java:119)
    at com.google.gwt.dev.DevMode.doStartUpServer(DevMode.java:431)
    at com.google.gwt.dev.DevModeBase.startUp(DevModeBase.java:1053)
    at com.google.gwt.dev.DevModeBase.run(DevModeBase.java:795)
    at com.google.gwt.dev.DevMode.main(DevMode.java:282)

    [WARN] failed MessengerRemoteService: java.lang.NullPointerException
    [WARN] Failed startup of context com.google.apphosting.utils.jetty.DevAppEngineWebAppContext@1ce3fc5{/,C:UserskannifDesktopchatroomwar}
    java.lang.NullPointerException
    at java.lang.Class.isAssignableFrom(Native Method)
    at org.mortbay.jetty.servlet.ServletHolder.doStart(ServletHolder.java:256)
    at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50)
    at org.mortbay.jetty.servlet.ServletHandler.initialize(ServletHandler.java:685)
    at org.mortbay.jetty.servlet.Context.startContext(Context.java:140)
    at org.mortbay.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1250)
    at org.mortbay.jetty.handler.ContextHandler.doStart(ContextHandler.java:517)
    at org.mortbay.jetty.webapp.WebAppContext.doStart(WebAppContext.java:467)
    at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50)
    at org.mortbay.jetty.handler.HandlerWrapper.doStart(HandlerWrapper.java:130)
    at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50)
    at org.mortbay.jetty.handler.HandlerWrapper.doStart(HandlerWrapper.java:130)
    at org.mortbay.jetty.Server.doStart(Server.java:224)
    at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50)
    at com.google.appengine.tools.development.JettyContainerService.startContainer(JettyContainerService.java:185)
    at com.google.appengine.tools.development.AbstractContainerService.startup(AbstractContainerService.java:149)
    at com.google.appengine.tools.development.DevAppServerImpl.start(DevAppServerImpl.java:219)
    at com.google.appengine.tools.development.gwt.AppEngineLauncher.start(AppEngineLauncher.java:119)
    at com.google.gwt.dev.DevMode.doStartUpServer(DevMode.java:431)
    at com.google.gwt.dev.DevModeBase.startUp(DevModeBase.java:1053)
    at com.google.gwt.dev.DevModeBase.run(DevModeBase.java:795)
    at com.google.gwt.dev.DevMode.main(DevMode.java:282)
    The server is running at http://localhost:8888/

  5. I think my problem is :
    I don’t understand what file should I prepare and where should they place.

    I was downloaded
    gwteventservice-1.0.2-developer.zip
    from http://code.google.com/p/gwteventservice/downloads/list
    already.

  6. Hi Joe,

    Note down the package where MessengerRemoteServiceImpl.java is in your source code system.

    update the web.xml and applicationContext.xml files to reflect the package changes

    You’ll resolve the issue.

  7. Hi Udy,

    In the server side I have problems with the imports:

    import javax.jms.JMSException;
    import javax.jms.Message;
    import javax.jms.MessageListener;
    import javax.jms.TextMessage;

    import org.apache.activemq.command.ActiveMQTextMessage;
    import org.onesun.sfs.client.MessengerRemoteService;
    import org.onesun.sfs.shared.event.MessageEvent;

    How can I solve it? Do I have to import more libraries?
    Thank you

  8. Really enjoyed this example but some questions remain (for the gwt/gwtEvent/activeMQ beginner):

    Do I have to configure sth. in GWTEventService or ActiveMQ or is it enough to include the JARs?

    Where do I find the applicationContext.xml?

    Thx in advance!
    Dirk

  9. its not yet on git ..please put..

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s