Thursday, March 19, 2009

Messaging With GWT (AJAX)

"Writing web apps today is a tedious and error-prone process. Developers can spend 90% of their time working around browser quirks. In addition, building, reusing, and maintaining large JavaScript code bases and AJAX components can be difficult and fragile. Google Web Toolkit (GWT) eases this burden by allowing developers to quickly build and maintain complex yet highly performant JavaScript front-end applications in the Java programming language." --Quoted from "http://code.google.com/webtoolkit/".

Since version 6.x, Netbeans included plug-ins for GWT/AJAX web application development. This makes writing GWT/AJAX applications a pleasant experience. With just a few clicks, the required GWT/AJAX web application skeleton files are automatically created. A complete war file is also generated when the (GWT/AJAX) web application is successfully built. The Netbeans/GWT tutorial to create a GWT web application is fairly simple to follow:

http://www.netbeans.org/kb/60/web/quickstart-webapps-gwt.html

The GWT quick start guide is at the link here:

http://code.google.com/webtoolkit/

This article provides a simple (GWT/AJAX) code example to send/receive text messages from a browser. A JMS TextMessage sent to a specific (JMS) destination can be received by the AJAX script running in the browser. The example can be modified to receive GWT supported serializable objects. The example looks like (Figure 1) after launched. The text entered in the lower text area is sent to a queue ("myqueue") when the Send button is clicked. The text is then (concurrently) received from the same queue and displayed in the upper text area.










(Figure 1)

The example assumes that you know about 2% of GWT and 0.1% of AJAX. The 2% means you know how to create and write a Hello-World GWT application and how "GWT RPC" works. No AJAX knowledge is required. You do need to know Java and JMS.

The code example is created with Netbeans IDE 6.5 with GWT plug-ins. The example is divided into the following components. AJAX and HTML files generated from GWT compiler and Netbeans IDE are not included here.
  • Creating services (interfaces)
  • Implementing services (interfaces)
  • Making the service calls
1. Creating services.

The interface defined in GWTService allows AJAX clients to send/receive messages on the specified destination name.

GWTService.java

package com.em.gwt.client;
import com.google.gwt.user.client.rpc.RemoteService;

//RPC interface for client to send/receive messages from destination name "destName".

public interface GWTService extends RemoteService {

public String receive (String queueName) throws IllegalStateException;

public void send (String queueName, String msg);
}

GWTServiceAsync.java

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

public interface GWTServiceAsync {

public void receive (String queueName, AsyncCallback callback);
public abstract void send(String queueName, String msg, AsyncCallback asyncCallback);
}


2. Implementing services (interfaces).

The service is implemented with Open Message Queue. So you need to include imq.jar, jms.jar in the IDE project library.

GWTServiceImpl.java

/*
* GWTServiceImpl.java
*
* Created on March 17, 2009, 2:02 PM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/

package com.em.gwt.server;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.em.gwt.client.GWTService;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.servlet.ServletException;

/**
*
* An example GWT service implementation to receive messages from
* specified JMS destinations.
*/
public class GWTServiceImpl extends RemoteServiceServlet implements
GWTService {

private ConnectionFactory factory = null;
private Connection connection = null;

/**
* This is an example implementation. No connection pooling.
*/
@Override
public void init() throws ServletException {

try {
//get conn facory
factory = new com.sun.messaging.ConnectionFactory();

//create jms connection
connection = factory.createConnection();
//start the connection
connection.start();

log ("Connection started.");
} catch (Exception e) {
log (e.getMessage(), e);
throw new ServletException (e);
}

}

/**
* Receive a message from the specified queue destination name.
*
* The performance can be improved if the session/consumer
* are cached.
*
* @param qname the queue name.
* @return The body of the text message.
*
* @throws java.lang.IllegalStateException if any internal error occurred.
*/
public String receive (String qname) throws IllegalStateException {

String msg = null;
Session session = null;

try {
//create a session
session = this.connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//get the queue reference
Queue queue = session.createQueue(qname);
//create a consumer
MessageConsumer consumer = session.createConsumer(queue);

//receive with 10 seconds timeout
Message m = consumer.receive(10000);

//get the body of the message
if (m != null && m instanceof TextMessage) {
msg = ((TextMessage)m).getText();
}

} catch (Exception e) {
msg = e.getMessage();
throw new IllegalStateException (e);
} finally {
close(session);
}

return msg;
}

public void send (String qname, String msg) {

Session session = null;

try {
//create a session
session = this.connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//get the queue reference
Queue queue = session.createQueue(qname);
//create a consumer
MessageProducer producer = session.createProducer(queue);

TextMessage tm = session.createTextMessage();
tm.setText(msg);

producer.send(tm);

} catch (Exception e) {
throw new IllegalStateException (e);
} finally {
close(session);
}

return;
}

/**
* close the specified session.
* @param s the session to be closed.
*/
private void close (Session s) {
try {
s.close();
} catch (Exception e) {
log (e.getMessage(), e);
//throw new IllegalStateException (e);
}
}

/**
* close connection when servlet is destroyed.
*/
@Override
public void destroy() {

try {
this.connection.close();
} catch (Exception e) {
log (e.getMessage(), e);
}
}
}


3. Making service calls.

The entry point of the GWT client application. Each message received is updated in the TextArea.

maasEntryPoint.java

package com.em.gwt.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.ServiceDefTarget;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import java.util.Date;

public class maasEntryPoint implements EntryPoint {

private VerticalPanel vp = new VerticalPanel();

private TextArea recvTextArea = new TextArea();

private TextArea sendTextArea = new TextArea();

private Button btnSend = new Button("Send");

private Label recvLabel = new Label ("message received:");

private Label sendLabel = new Label ("message sent:");

private AsyncCallbackRecvImpl recvCallBack = new AsyncCallbackRecvImpl();
private AsyncCallbackSendImpl sendCallBack = new AsyncCallbackSendImpl();

private GWTServiceAsync service = null;

private static final String MYQUEUE = "myqueue";

/** Creates a new instance of maasEntryPoint */
public maasEntryPoint() {
}

/**
* The entry point method, called automatically by loading a module
* that declares an implementing class as an entry-point
*/
public void onModuleLoad() {

vp.add(recvLabel);

recvTextArea.setCharacterWidth(70);

vp.add(recvTextArea);

vp.add(sendTextArea);

btnSend.addClickListener(new SendBtnListener());

vp.add(btnSend);

vp.add(sendLabel);

RootPanel.get().add (vp);

service = getService();

receive();
}

private void receive() {
service.receive(MYQUEUE, recvCallBack);
}

public static GWTServiceAsync getService(){
// Create the client proxy. Note that although you are creating the
// service interface proper, you cast the result to the asynchronous
// version of
// the interface. The cast is always safe because the generated proxy
// implements the asynchronous interface automatically.
GWTServiceAsync service = (GWTServiceAsync) GWT.create(GWTService.class);
// Specify the URL at which our service implementation is running.
// Note that the target URL must reside on the same domain and port from
// which the host page was served.
//
ServiceDefTarget endpoint = (ServiceDefTarget) service;
String moduleRelativeURL = GWT.getModuleBaseURL() + "gwtservice";
endpoint.setServiceEntryPoint(moduleRelativeURL);
return service;
}

class AsyncCallbackRecvImpl implements AsyncCallback {

public void onFailure(Throwable caught) {
recvTextArea.setText("receive failed: " + caught.getMessage());

caught.printStackTrace();
}

public void onSuccess(Object result) {

if (result != null) {
recvTextArea.setText("Received msg = " + (String)result);
}

receive();
}

}

class AsyncCallbackSendImpl implements AsyncCallback {

public void onFailure(Throwable caught) {

sendLabel.setText(new Date() + ": send failed.");

caught.printStackTrace();
}

public void onSuccess(Object result) {

sendLabel.setText(new Date() + ", sent message = " + sendTextArea.getText());

}

}

class SendBtnListener implements ClickListener {

public void onClick(Widget sender) {
String msg = sendTextArea.getText();
if (msg == null || msg.isEmpty()) {
Window.alert("No message entered in send text area.");
} else {
service.send(MYQUEUE, msg, sendCallBack);
}
}

}

}