Apr 27, 2009

Getting Started With Cometd, Jetty, and jQuery

I had difficulty getting started with these tools, so I thought I'd blog my minimal setup. This is all derived from the cometd demo who's war you can find inside the Jetty 6 webapp directory, and who's code you can find inside the contrib/cometd directory of the Jetty 6 source download.

First of all you need to be able to run Jetty (and debug it). I recommend the Run Jetty Run plugin for Eclipse.

Next you need to populate your web.xml for your webapp: (it's quite possible that you don't need all these init params)

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<display-name>comet2</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>

<context-param>
<param-name>org.mortbay.jetty.servlet.ManagedAttributes</param-name>
<param-value>org.cometd.bayeux,dojox.cometd.bayeux</param-value>
</context-param>

<servlet>
<servlet-name>game</servlet-name>
<servlet-class>
org.mortbay.cometd.continuation.ContinuationCometdServlet</servlet-class>
<init-param>
<param-name>timeout</param-name>
<param-value>120000</param-value>
</init-param>
<init-param>
<param-name>interval</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>maxInterval</param-name>
<param-value>10000</param-value>
</init-param>
<init-param>
<param-name>multiFrameInterval</param-name>
<param-value>2000</param-value>
</init-param>
<init-param>
<param-name>logLevel</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>directDeliver</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>refsThreshold</param-name>
<param-value>10</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>game</servlet-name>
<url-pattern>/game/*</url-pattern>
</servlet-mapping>

<servlet>
<servlet-name>gameServlet</servlet-name>
<servlet-class>server.GameServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>

</web-app>
The GameServlet is pretty much boiler plate:
package server;

import java.io.IOException;

import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;

import org.cometd.Bayeux;
import org.mortbay.cometd.ext.AcknowledgedMessagesExtension;
import org.mortbay.cometd.ext.TimesyncExtension;

public class GameServlet extends GenericServlet {
@Override
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
((HttpServletResponse)res).sendError(503);
}

@Override
public void init() throws ServletException {
super.init();
Bayeux bayeux=(Bayeux)getServletContext().getAttribute(Bayeux.DOJOX_COMETD_BAYEUX);
new GameService(bayeux);
bayeux.addExtension(new TimesyncExtension());
bayeux.addExtension(new AcknowledgedMessagesExtension());
}
}
The GameService pairs paths to method names. You can call the send() method to send messages to clients. Mine looks like this:
package server;

import java.util.Map;

import org.cometd.Bayeux;
import org.cometd.Client;
import org.mortbay.cometd.BayeuxService;

public class GameService extends BayeuxService {
private GameState state = new GameState();

public GameService(Bayeux bayeux) {
super(bayeux, "game");
subscribe("/game/join", "join");
subscribe("/game/event", "event");
}

public void join(final Client joiner, final String channelName, Map<String, Object> data, final String messageId) {
send(joiner, "/game/state", state.getData(), null)
}

public void event(final Client joiner, final String channelName, Map<String, Object> data, final String messageId) {
// TODO
}
}
Finally, and perhaps most importantly (as this was the part that confused me the most), here's the JavaScript to talk to the comet service:
var _connected = false;

function _metaConnect(message)
{
var wasConnected = _connected;
_connected = message.successful;
if (_connected && !wasConnected)
{
$.cometd.startBatch();
$.cometd.subscribe('/game/state', this, receive);
$.cometd.publish('/game/join', {});
$.cometd.endBatch();
}
}

function join()
{
$.cometd.addListener('/meta/connect', this, _metaConnect);

var cometURL = document.location.protocol + '//' + document.location.hostname + ':' + document.location.port + '/c2/game';
$.cometd.init(cometURL);
}

function send(data) {
$.cometd.publish('/game/event', data);
}

function receive(message)
{
// handle message.data content
}

join();
Note that /c2 is the context name of my webapp in Jetty.

One last point, I used the same includes in my index.html as the cometd chat example did:
<script type="text/javascript" src="jquery/json2.js"></script>
<script type="text/javascript" src="jquery/jquery.js"></script>
<script type="text/javascript" src="jquery/jquery.cometd.js"></script>
Let me know if you get up to something with comet.

Apr 25, 2009

Jetty and Cometd Documentation Woes

I've been trying to get going with Comet using Jetty and cometd-jquery. It's been an uphill battle. First off there's practically zero documentation for Cometd. I've been reverse engineering the chat example - a very poor source of documentation. I've finally gotten to a point where my JavaScript initiates a Bayeux connection. Great.

Now I want to debug my app that Jetty is hosting. That's been a very frustrating experience. Jetty 6 forks a JVM for each webapp, so none my standard approaches to debugging are working for me. Jetty documentation is again very weak and it's hard to differentiate which versions of Jetty documentation refers to.

Can anyone help me out?

Update: IntelliJ debugs Jetty 6 out of the box (with the Jetty Integration plugin). The IntelliJ run configuration uses -Xdebug -Xrunjdwp:transport... to start in debug. I guess these settings may work for Eclipse: -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=4000,server=y,suspend=y.

Update: Run Jetty Run for Eclipse works out of the box too.

Apr 24, 2009

Leadership Philosophy

McKnight quoted in Leading Lean Software Development:

"Mistakes will be made. But if a person is essentially right, the mistakes he or she makes are not as serious in the long run as the mistakes management will make it if undertakes to tell those in authority exactly how they must do their jobs"