CometD 1.x Reference

CometD 1.x Reference

CometD JavaScript

CometD JavaScript Implementation

The JavaScript implementation of the Bayeux specification and API has been totally rewritten starting from CometD version 1.0.beta8, and further refactored since 1.0.beta9.
What is available now is a portable JavaScript implementation with bindings for the major JavaScript toolkits, currently Dojo and jQuery.

What this means is that the CometD Bayeux JavaScript implementation is written in pure JavaScript with no dependencies on the toolkits, and that the toolkit bindings add the syntactic sugar that makes the Bayeux APIs feel like they are native to the toolkit.
For example, it is possible to refer to the standard cometd object using the following notation:

// Dojo style
var cometd = dojox.cometd;

// jQuery style
var cometd = $.cometd;

If you followed the Primer, you may have noticed that the skeleton project now requires to reference both the portable implementation, under org/cometd.js, and one binding - for example Dojo's - under dojox/cometd.js. For jQuery, the binding is under jquery/jquery.cometd.js.

The usage of the Bayeux APIs from the JavaScript toolkits is almost identical, and in the following we will not refer to a particular toolkit.
Small differences only surface when passing callback functions to the Bayeux API, where Dojo users may like to use dojo.hitch(), while jQuery users may like an anonymous function approach.

The following sections will go in detail about the JavaScript Bayeux APIs and their implementation secrets.

Configuration

JavaScript Cometd API: Configuration and Initialization

After you have setup your skeleton project following the Primer, you may want to fully understand how to customize and configure the parameters that govern the behavior of the Cometd implementation.

The whole API is available through a single object prototype named org.cometd.Cometd.
The Dojo toolkit has one instance of this object available under the name dojox.cometd, while for jQuery it is available under the name $.cometd.

This default cometd object has been instantiated and configured with the default values and it has not started any Bayeux communication yet.
Before it can start any Bayeux communication it needs a mandatory parameter: the URL of the Bayeux server.

There are 2 ways of passing this parameter:

// First style: URL string
cometd.configure('http://localhost:8080/cometd');

// Second style: configuration object
cometd.configure({
    url: 'http://localhost:8080/cometd'
});

The first way is a shorthand for the second way.
However, the second way allows to pass other configuration parameters, currently:

Parameter Name Required Default Value Parameter Description
url yes The URL of the Bayeux server this client will connect to
logLevel no info The log level. Possible values are: "warn", "info", "debug". Output to window.console if available
maxConnection no 2 The max number of connections used to connect to the Bayeux server.
Only change this value if you know exactly what is the client's connection limit and what "request queued behind long poll" means
backoffIncrement no 1000 The number of milliseconds of which the backoff time is incremented every time a connection with the Bayeux server fails.
A reconnection will be attempted after the backoff time elapses
maxBackoff no 60000 The max number of milliseconds of the backoff time after which the backoff time is not incremented anymore
reverseIncomingExtensions no true Controls whether the incoming extensions will be called in reverse order with respect to the registration order
maxNetworkDelay no 10000 The max number of milliseconds to wait before considering a request to the Bayeux server failed.
requestHeaders no {} An object containing the request headers to be sent for every bayeux request (for example: {"My-Custom-Header":"MyValue"})
appendMessageTypeToURL no true Whether or not the Bayeux message type (handshake, connect, disconnect) is appended to URL of the Bayeux server (see above).
autoBatch no false Whether multiple publishes that gets queued up will be sent as a batch on the first occasion, without requiring explicit batching.

 
After you have configured the cometd object, it has not started the Bayeux communication yet. To start the Bayeux communication, you need to call handshake(), see the next section.

Previous users of the JavaScript Cometd implementation were used to call a method called init(). This method still exists, and it is a shorthand for calling configure() followed by handshake().
Follow the advices in the next section as they apply as well to init().

Handshake

JavaScript Cometd API: Handshake

The call to handshake() (or to init()) is the one that initiates the Bayeux communication with the Bayeux server.

The Bayeux handshake performs 2 tasks:

  • the client and the server negotiate the type of long poll transport to use
  • once the long poll transport is negotiated successfully, the server informs the client with the detailed timings of the long poll requests

As with several methods of the JavaScript Cometd API, it is an asynchronous method: it returns immediately, well before the Bayeux handshake steps have completed.

Note
Calling handshake() does not mean that you have completed the handshake with the server when handshake() returns.

The handshake may fail for several reasons:

  • you mistyped the server URL
  • the long poll transport could not be negotiated successfully
  • the server denied the handshake (for example, the authentication credentials were wrong)
  • the server crashed
  • there was a network failure

Therefore it is not a good idea to write this code:

// Configure and handshake
cometd.init('http://localhost:8080/cometd');

// Publish to a channel
cometd.publish('/foo', { foo: 'bar' });

It is not a good idea, because there is no guarantee that the call to publish() (which we cover in a later section) can actually succeed in contacting the Bayeux server.
Since the API is asynchronous, you have no way of knowing synchronously (i.e. by having handshake() return an error code or by throwing an exception) that the handshake failed.
Even if the handshake succeeds, you may still be "disconnected" from the Bayeux server, for example because the server crashed just after the successful handshake.

Fortunately there is a way to be notified about the details of the Bayeux protocol message exchange: by adding listeners to special channels (called meta channels).
This is explained in the section about subscriptions.

Subscription

JavaScript Cometd API: Subscribing and Unsubscribing

Channels

The Bayeux specification defines the concept of a channel: it is like a messaging topic where interested parties can subscribe to receive information published onto the channel.
There are 3 types of channels:

  • meta channels
  • service channels
  • normal channels

A channel looks like a directory path such as /meta/connect (a meta channel; all meta channels starts with the prefix /meta/), or /service/chat (a service channel; all service channels starts with the prefix /service/) or /foo/bar (a normal channel).

Meta Channels

Meta channels are created by the Bayeux protocol itself.
It is not possible to subscribe to meta channels: the server will reply with an error message. However, it is possible to listen to meta channels (see below the difference between subscribing and listening).
It makes no sense to publish messages to meta channels: only the Bayeux protocol implementation creates and sends messages on meta channels.
Meta channels are useful on the client to listen for error messages like handshake errors (for example because the client did not provide the correct credentials) or network errors (for example to know when the connection with the server has broken or when it has been re-established).

Service Channels

Service channels are used in the case of request/response style of communication between client and server (as opposed to the publish/subscribe style of communication or normal channels).
While subscribing to service channels yields no errors, this is a no-operation for the server: the server ignores the subscription request.
It is possible to publish to service channels, with the semantic of a communication between a specific client (the one that's publishing the message on the service channel) and the server.
Service channels are useful to implement, for example, private chat messages: in a chat with userA, userB and userC, userA can publish a private message to userC (without userB knowing about) using service channels.

Normal Channels

Normal channels have the semantic of a messaging topic and are used in the case of publish/subscribe style of communication.
Usually, it is possible to subscribe to normal channels and to publish to normal channels; this can only be forbidden using a security policy on the Bayeux server.
Normal channels are useful to implement broadcasting of messages to all subscribed clients, for example in case of a stock price change.

Subscribers versus Listeners

The JavaScript Cometd API has 2 APIs to work with channel subscriptions:

  • addListener() and the correspondent removeListener()
  • subscribe() and the correspondent unsubscribe()

The addListener() method:

  • must be used to listen to meta channel messages
  • may be used to listen to service channel messages (you may also use subscribe())
  • should not be used to listen normal channel messages (use subscribe() instead)
  • does not involve any communication with the Bayeux server, and as such can be called before calling handshake()
  • is synchronous: when it returns, you are guaranteed that the listener has been added

The subscribe() method:

  • must not be used to listen to meta channels messages (otherwise the server will return an error)
  • may be used to listen to service channel messages (you may also use addListener())
  • should be used to listen to normal channel messages
  • involves a communication with the Bayeux server and as such cannot be called before calling handshake()
  • is asynchronous: it returns immediately, well before the Bayeux server has received the subscription request

Note
Calling subscribe() does not mean that you have completed the subscription with the server when subscribe() returns.

Both addListener() and subscribe() return a subscription object that must be passed to, respectively, removeListener() and unsubscribe():

// Some initialization code
var subscription1 = cometd.addListener('/meta/connect', function() { ... });
var subscription2 = cometd.subscribe('/foo/bar/', function() { ... });

// Some de-initialization code
cometd.unsubscribe(subscription2);
cometd.removeListener(subscription1);

A common pattern of utilization is to handle the subscription code in an idempotent method, like this:

var _subscription;

// The idempotent method
function _refresh()
{
    _appUnsubscribe();
    _appSubscribe();
}

function _appUnsubscribe()
{
    if (_subscription) 
        cometd.unsubscribe(_subscription);
    _subscription = null;
}

function _appSubscribe()
{
    _subscription = cometd.subscribe('/foo/bar', function() { ... });
}

The same of course applies also for addListener()/removeListener().

The point is that you have to be careful in your application: you have to remove your subscriptions, in order to avoid to leak functions, or to execute functions more than once (since you could erroneously bind the same callback twice).
Refer also to the Primer for a discussion about using idempotent methods.

How do subscribe() and unsubscribe() behave in case the Bayeux server is not reachable (due to network failures or because the server crashed) ?
In subscribe() the local listener is first added to the list of subscribers for that channel, then the server communication is attempted. If the communication fails, the server will not know that it has to send messages to this client and therefore on the client the local listener (although present) will never be invoked.
In unsubscribe(), the local listener is first removed from the list of subscribers for that channel, then the server communication is attempted. If the communication fails, the server will still send the message to the client but there will be no local listener to dispatch to.

Listeners/Subscribers Exception Handling

If a listener or subscriber function throws an exception (for example, calls a method on an undefined object, etc.), then the error message is logged (at level "debug").
However, there is a way to intercept these errors by defining the global listener exception handler, that is invoked every time a listener or subscriber throws an exception:

cometd.onListenerException = function(exception, subscriptionHandle, isListener, message)
{
    // Uh-oh, something went wrong, disable this listener/subscriber
    // Object "this" points to the CometD object
    if (isListener)
        this.removeListener(subscriptionHandle);
    else
        this.unsubscribe(subscriptionHandle);
}

It is be possible to send messages to the server from the listener exception handler.
If the listener exception handler itself throws an exception, this exception is logged at level "info" and the CometD implementation will not break.
Note that a similar mechanism exists for extensions, see here.

Wildcard Subscriptions

It is possible to subscribe to several channels at once using wildcards, like this:

cometd.subscribe("/chatrooms/*", function(message) { ... });

A single asterisk has the meaning of matching a single channel segment, so in the example above it will match channels /chatrooms/12 and /chatrooms/15, but not /chatrooms/12/upload.
To match multiple channel segments, use the double asterisk:

cometd.subscribe("/events/**", function(message) { ... });

With the double asterisk, the channels /events/stock/FOO and /events/forex/EUR will match, as well as /events/feed and /events/feed/2009/08/03.

The wildcard mechanism works also for listeners, so it is possible to listen to all meta channels like this:

cometd.addListener("/meta/*", function(message) { ... });

By default, subscriptions to the global wildcards /* and /** result in an error, but this behavior can be changed by specifying a custom security policy on the Bayeux server.

The wildcards can only be specified as last segment of the channel, so these are invalid subscriptions: /**/foo or /foo/*/bar.

Meta Channel List

These are the meta channels available in the JavaScript Cometd implementation:

  • /meta/handshake
  • /meta/connect
  • /meta/disconnect
  • /meta/subscribe
  • /meta/unsubscribe
  • /meta/publish
  • /meta/unsuccessful

Each meta channel is notified when the correspondent Bayeux message is handled by the JavaScript Cometd implementation.
The /meta/unsuccessful channel is notified in case of any failure.

By far the most interesting meta channel to subscribe to is /meta/connect, because it gives the status of the current connection with the Bayeux server. In combination with /meta/disconnect, it can be used, for example, to display a green "connected" icon or a red "disconnected" icon on the page, depending on the connection status with the Bayeux server.

This is a common pattern using the /meta/connect and /meta/disconnect channels:

var _connected = false;

cometd.addListener('/meta/connect', function(message)
{
    // if (cometd.getStatus() === 'disconnecting' || cometd.getStatus() === 'disconnected')
    if (cometd.isDisconnected()) // Available since 1.1.2
    {
        return;
    }
    var wasConnected = _connected;
    _connected = message.successful;
    if (!wasConnected && _connected)
    {
        // Reconnected
    }
    else if (wasConnected && !_connected)
    {
        // Disconnected
    }
});

cometd.addListener('/meta/disconnect', function(message)
{
    if (message.successful)
    {
        _connected = false;
    }
}

One small caveat with the /meta/connect channel is that /meta/connect is also used for polling the server.
Therefore, if a disconnect is issued during an active poll, the active poll is returned by the server and this triggers the /meta/connect listener.
The initial check on the status verifies that is not the case before executing the connection logic.

Another interesting usage of meta channels is when there is an authentication step during the handshake.
In this case the registration to the /meta/handshake channel can give details about, for example, authentication failures.

Publish

JavaScript Cometd API: Publishing

The publish() method allow you to publish data onto a certain channel:

cometd.publish('/mychannel', { mydata: { foo: 'bar' } });

You cannot (and it makes no sense) to publish to a meta channel, and you can publish to a channel even if you are not subscribed to that channel.
However, you have to handshake before being able to publish.

As with other JavaScript Cometd API, publish() involves a communication with the server and it is asynchronous: it returns immediately, well before the Bayeux server has received the message.

Note
Calling publish() does not mean that you have published the message when publish() returns.

If you have to publish several messages to different channels, you may want to use message batching.

Disconnection

JavaScript Cometd API: Disconnecting

The JavaScript Cometd implementation performs automatic reconnect in case of network or Bayeux server failures.
The reconnect parameters are described in the configuration section.

Short Network Failures

In case of temporary network failures, the client is notified through the /meta/connect channel (see this section about meta channels) with messages that have the successful field set to false (see also the archetypes in the primer as an example).
However, the Bayeux server may be able to keep the client's state, and when the network resumes the Bayeux server may behave as if nothing happened.
The client in this case just re-establishes the long poll, but any message published by the client during the network failure is not automatically re-sent (though it is possible to be notified, through the /meta/publish channel, of the failed publishes).

Long Network Failures or Server Failures

If the network failure is long enough, the Bayeux server times out the lost client, and deletes the state associated with it. The same happens when the Bayeux server crashes (except of course that the state of all clients is lost).
In this case, the reconnection mechanism on the client performs the following steps:

  • a long poll is re-attempted, but the server rejects it with a 402::Unknown client error message
  • a handshake is attempted, and the server normally accepts it and allocates a new client
  • upon the successful re-handshake, a long poll is re-established

If you register with meta channels, be aware of these steps, since a reconnection may involve more than one message exchange with the server.

Disconnecting

Calling the JavaScript Cometd API disconnect() result in a message being sent to the Bayeux server, so that it can cleanup any state associated with that client.
As with all methods that involve a communication with the Bayeux server, it is an asynchronous method: it returns immediately, well before the Bayeux server has received the disconnect request.
If the server cannot be reached (because it is down or because of network failures), the JavaScript Cometd implementation will stop any reconnection attempt and cleanup any local state.
It is normally safe to ignore if the disconnect() call has been successful or not: the client is in any case disconnected, its local state cleaned up, and if the server has not been reached it will eventually time out the client and cleanup any server-side state for that client.

Tip
If you are debugging your application with Firebug, and you shutdown the server, you'll see in the Firebug console the attempts to reconnect.
To stop those attempts, simply type in the Firebug command line: dojox.cometd.disconnect() (for Dojo) or $.cometd.disconnect() (for jQuery).

Message Batching

JavaScript Cometd API: Message Batching

It is often needed by an application to send several messages to possibly different channels.

One, naive, way of doing it is the following:

cometd.handshake();

// Warning: non-optimal code
cometd.publish('/channel1', { product: 'foo' });
cometd.publish('/channel2', { notificationType: 'all' });
cometd.publish('/channel3', { update: false });

You may think that the 3 publishes will leave the client one after the other, but that's actually not the case.
Remember that publish() is asynchronous (so it returns immediately), so the 3 publish() calls in sequence may return well before a single byte hits the network.

What happens is that the first publish() will be executed immediately, and the other 2 will be put in a queue, waiting for the first publish() to complete.
A publish() is complete when the server received it, the server sent back the meta response, and the client received the meta response for that publish.
When the first publish is completed, the second publish is executed and waited to complete. After that, finally the third publish() is executed.

Since CometD 1.1.2, a new configuration parameter called autoBatch can be set to true, allowing the implementation to automatically batch messages that have been queued up.
So, in the example above, the first publish() will be executed immediately, and when it completes, the implementation will batch the second and third publish() into one request to the server.
The autoBatch feature may be interesting for those systems where events received asynchronously and unpredictably, either at a fast rate or in bursts, end up in generating a publish() to the server: in such cases, using the batching API is not effective (as each event would generate only one publish()).
A bursts of events on the client will generate a bursts of publish() to the server, but this mechanism batches them automatically, making the communication more efficient.

This queueing mechanism is needed to avoid to queue a publish() behind a long poll. If not for this mechanism, the browser would receive 3 publish requests but only has 2 connections available, and one is already occupied by the long poll request. So the browser may decide to round robin the publish requests, so that the first publish goes on the second connection (remember that the first connection is already busy with the long poll request), which is free and it is actually sent over the network, schedule the second publish to the first connection (after the long poll returns), and schedule the third publish again to the second connection, after the first publish returns.
The result is that if you have a long poll timeout of 5 minutes, the second publish request may arrive to the server 5 minutes later than the first and the third publish request.

You can optimize the 3 publish using batching, which is a way to group messages together so that a single Bayeux message actually carries the 3 publish messages.

cometd.handshake();

cometd.batch(function()
{
    cometd.publish('/channel1', { product: 'foo' });
    cometd.publish('/channel2', { notificationType: 'all' });
    cometd.publish('/channel3', { update: false });
});

// Alternatively, but not recommended
cometd.startBatch()
cometd.publish('/channel1', { product: 'foo' });
cometd.publish('/channel2', { notificationType: 'all' });
cometd.publish('/channel3', { update: false });
cometd.endBatch()

Note how the 3 publish() calls are now within a function passed to batch().
Alternatively, but less recommended, you can surround the 3 publish() calls between startBatch() and endBatch().

Warning
Remember to call endBatch() after having called startBatch().
If you don't, for example because an exception is thrown in the middle of the batch, your messages will continue to queue up, and your application will not work as expected.

Function batch() already does the correct batching for you (also in case of errors), so it's the recommended way to do message batching.

When a batch is started, subsequent API calls are not sent to the server, but instead queued up, until the batch is ended.
The end of the batch packs up all the queued messages into one single Bayeux message and send it over the network to the Bayeux server.

Message batching allow an efficient utilization of the network, as instead of making 3 requests/responses cycles, batching makes only one request/response cycle.

Batches can be made up of different API calls:

var _subscription;

cometd.batch(function()
{
    cometd.unsubscribe(_subscription);
    _subscription = cometd.subscribe('/foo', function(message) { ... });
    cometd.publish('/bar', { ... });
});

Batched messages will be processed by the Bayeux server in the order they are sent.

If you still want to risk and use the startBatch() and endBatch() calls, remember that they must be done from the same context of execution; message batching has not been designed to span multiple user interactions.
So, for example, it would be wrong to start a batch in, say, functionA (triggered by user interaction), and ending the batch in functionB (also triggered by user interaction and not called by functionA).
Similarly, it would be wrong to start a batch in functionA and then schedule (using setTimeout()) the execution of functionB to end the batch.

Extensions

JavaScript CometD API: Extensions

The JavaScript CometD implementation has the capability to add/remove extensions.
An extension is a function that is being called to give the chance to modify the message just before it is being sent (an outgoing extension) or just after it is being received (an incoming extension).

An extension normally adds fields to the message being sent or received in the ext object defined by the Bayeux specification (see here).

An extension is not a way to add business fields to a message, but rather a way to process all messages, including the meta messages used by the Bayeux protocol.

Extensions are normally setup on both the client and the server, since fields added by the client normally needs a special processing by the server; it may be possible that an extension is only client-side or only server-side, though.
Most of the times, however, they are needed in both client and server, and when the extension does not behave as expected, it's normally because the extension on one of the two sides is missing.

The JavaScript CometD Extensions are described in the next sections, and follow the same pattern used by the portable JavaScript CometD implementation: a portable implementation of the extension with bindings for the specific JavaScript toolkit, currently Dojo and jQuery.

Writing the Extension

An extension is a JavaScript object with 4 optional methods:

  • outgoing(message), called just before a message is being sent
  • incoming(message), called just after a message is received
  • registered(name, cometd), called when the extension is registered
  • unregistered(), called when the extension is unregistered

All 4 methods are optional, or there can be only one, or maybe two, three or all of them. If they are present, they will be invoked at the proper time.

Writing an extension that logs and counts the long polls is quite easy: we need a reference to the cometd object, that has the logging methods, and we need only the outgoing extension method:

var LoggerExt = function()
{
    var _cometd;
    var _counter;

    this.registered = function(name, cometd)
    {
        // Store the cometd object reference
        _cometd = cometd;
    };

    this.outgoing(message)
    {
        if (message.channel == '/meta/connect')
        {
            // Log the long poll
            _cometd._info('bayeux connect');

            // Count the long polls
            if (!message.ext) message.ext = {};
            if (!message.ext.logger) message.ext.logger = {};
            if (!message.ext.logger.counter) message.ext.logger.counter = 0;
            message.ext.logger.counter = ++_counter;
        }
    };
};

Note that also meta messages are passed to the extension methods; you normally have to filter the messages that the extension method receives by looking at the channel or at some other message value.
Note that the message can be modified by adding fields, normally in the ext field.

Note
Be careful to not overwrite the ext field that it may have been set by other extensions: check if it's present first.
It is also a good practice to group your extension fields so that there is no clash with other extensions (in the example above the only field - counter - is "grouped" in the message.ext.logger object).

The outgoing() and incoming() methods can avoid to return something, or return the message itself (or another message object). This means that the message has been processed by the extension and can therefore be processed by other extensions, if present, or processed by the implementation (either be sent to the server - for outgoing extensions - or be notified to listeners - for incoming extensions).
If null is returned by the extension method, this means that the processing should stop: the message will not be processed by other extensions and will not be further processed (therefore it will neither be sent to the server nor be notified to listeners).

Registering the Extension

The JavaScript CometD API defines 3 methods to manage extensions:

  • registerExtension(name, extension), to register an extension with the given name
  • unregisterExtension(name), to unregister the extension previously registered with the given name
  • getExtension(name), to obtain a reference to the extension previously registered with the given name

Following the example above, we can register the extension like this:

cometd.registerExtension('loggerExt', new LoggerExt());

From now on, the meta connect messages will be modified to carry the counter from the example extension above.

Unregistering the extension is similar:

cometd.unregisterExtension('loggerExt');

It is not possible to register 2 extensions under the same name.

You can register more than one extension, and they will be applied following the registration order: outgoing extensions methods will be called in registration order and, by default, incoming registration methods will be called in reverse registration order. See also the reverseIncomingExtensions configuration parameter in the configuration section.
For example, if you register extA and extB, then for outgoing messages the methods called are: extA.outgoing() and then extB.outgoing(), while for incoming messages the methods called are extB.incoming() and then extA.incoming().

Extension Exception Handling

While it is normally good practice to catch exceptions within extension functions, sometimes this is tedious to code, or there is no control about the quality of the extension (e.g. it's a third party extension).
The JavaScript CometD API provides a way to define the global extension exception handler that is invoked every time an extension throws an exception (for example, calling a method on an undefined object, etc.):

cometd.onExtensionException = function(exception, extensionName, outgoing, message)
{
    // Uh-oh, something went wrong, disable this extension
    // Object "this" points to the CometD object
    this.unregisterExtension(extensionName);

    // If the message is going to the server, add the error to the message
    if (outgoing)
    {
        // Assume we have created the message structure below
        var badExtension = message.ext.badExtensions[extensionName];
        badExtension.exception = exception;
    }
}

Be very careful to use the CometD object to publish messages within the extension exception handler, or you may end up in an infinite loop (the publish message is processed by the extensions, which may fail and call again the extension exception handler).
If the extension exception handler itself throws an exception, this exception is logged at level "info" and the CometD implementation will not break.
Note that a similar mechanism exists for listeners and subscribers, see here.

The following sections will explain in detail the usage of the provided extensions.

Acknowledge

Acknowledge Extension

The acknowledged messages extension provides reliable ordered messaging to the Bayeux protocol.
This extension requires both a client-side extension and a server-side extension. The server-side extension is available in Java.

Enabling the Server-side Extension

To enable support for acknowledged messages, the extension must be added to the org.cometd.Bayeux instance during initialization:

bayeux.addExtension(new org.cometd.server.ext.AcknowledgedMessagesExtension());

The AcknowledgedMessageExtension is a per-server extension that monitors handshakes from new clients, looking for clients that also support the acknowledged message extension and then adds the AcknowledgedMessagesClientExtension to each client during the handshake.

Once added to a client, the AcknowledgedMessagesClientExtension prevents messages being delivered on any request other than a long poll request.
This prevents the possibility of out of order delivery.
The extension also maintains a list of non-acknowledged messages and intercepts the traffic on the /meta/connect channel to insert and check acknowledge IDs.

Enabling the Client-side Extension

The client side extension binding for Dojo is provided by dojox/cometd/ack.js and it is sufficient to use Dojo's dojo.require mechanism:

dojo.require("dojox.cometd.ack");

The client side extension binding for jQuery is provided by the file jquery.cometd-ack.js. This file must be included in the HTML page via the <script> tag:

<script type="text/javascript" src="AckExtension.js"></script>
<script type="text/javascript" src="jquery.cometd-ack.js"></script>

In both Dojo and jQuery extension bindings, the extension is registered on the default cometd object under the name "ack".

Furthermore, the extension may be programmatically disabled/enabled before initialization by setting the ackEnabled boolean field on the cometd object:

// Disables the ack extension during handshake
cometd.ackEnabled = false;
cometd.init(cometdURL);

Acknowledge Extension Details

In order to enable message acknowledgement, both client and server must indicate that they support message acknowledgement.
This is negotiated during handshake. On handshake, the client sends {"ext":{"ack": "true"}} to indicate that it supports message acknowledgement.
If the server also supports message acknowledgment, it likewise replies with {"ext":{"ack": "true"}}.

The extension does not insert ack IDs to every message, as this would impose a significant burden on the server for messages sent to multiple clients (which would need to be reserialized to json for each client). Instead the ack ID is inserted in the ext field of the /meta/connect messages that are associated with message delivery. Each /meta/connect request contains the ack ID of the last received ack response: "ext":{"ack": 42}. Similarly, each /meta/connect response contains an ext ack ID that uniquely identifies the batch of responses sent.

If a /meta/connect message is received with an ack ID lower that any unacknowledged messages held by the extension, then these messages are requeued prior to any more recently queued messages and the /meta/connect response sent with a new ack ID.

Demo

There is an example of acknowledged messages in the Dojo chat demo that comes bundled with the Cometd distribution.
The example can also be run by building the Cometd project.

To run the demo, follow these steps:

$ cd cometd-demo
$ mvn jetty:run

Then point your browser to http://localhost:8080/dojo-examples/chat/ and make sure to check "Enable reliable messaging".

Use two different browsers instances to begin a chat session, then briefly disconnect one browser from the network (you can do this by setting the "work offline" feature).
While one browser is disconnected, type some chat in the other browser and this will be received when the disconnected browser is reconnected to the network.

Note that if the disconnected browser is disconnected for in excess of maxInterval (default 10s), then the client will be timed out and the unacknowledged queue discarded.

Timestamp

Timestamp Extension

The timestamp extension add a timestamp to the message object, for every message sent by the client and/or server.
It is a non standard extension because it does not add the additional fields to the ext field, but to the message object itself.
This extension requires both a client-side extension and a server-side extension. The server-side extension is available in Java.

Enabling the Server-side Extension

To enable support for timestamped messages, the extension must be added to the org.cometd.Bayeux instance during initialization:

bayeux.addExtension(new org.cometd.server.ext.TimestampExtension());

Enabling the Client-side Extension

The client side extension binding for Dojo is provided by dojox/cometd/ack.js and it is sufficient to use Dojo's dojo.require mechanism:

dojo.require("dojox.cometd.timestamp");

The client side extension binding for jQuery is provided by the file jquery.cometd-timestamp.js. This file must be included in the HTML page via the <script> tag:

<script type="text/javascript" src="jquery.cometd-timestamp.js"></script>

In both Dojo and jQuery extension bindings, the extension is registered on the default cometd object under the name "timestamp".

Timesync

Timesync Extension

The timesync extension uses the messages exchanged between a client and a server to calculate the offset between the client's clock and the server's clock.
This is independent from the timestamp extension, which uses the local clock for all timestamps.
This extension requires both a client-side extension and a server-side extension. The server-side extension is available in Java.

Enabling the Server-side Extension

To enable support for acknowledged messages, the extension must be added to the org.cometd.Bayeux instance during initialization:

bayeux.addExtension(new org.cometd.server.ext.TimesyncExtension());

Enabling the Client-side Extension

The client side extension binding for Dojo is provided by dojox/cometd/timesync.js and it is sufficient to use Dojo's dojo.require mechanism:

dojo.require("dojox.cometd.timesync");

The client side extension binding for jQuery is provided by the file jquery.cometd-timesync.js. This file must be included in the HTML page via the <script> tag:

<script type="text/javascript" src="jquery.cometd-timesync.js"></script>

In both Dojo and jQuery extension bindings, the extension is registered on the default cometd object under the name "timesync".

Timesync Extension Details

The timesync extension allows the client and server to exchange time information on every handshake and connect message so that the client may calculate an approximate offset from it's own clock epoch to that of the server.
The algorithm used is very similar to the NTP algorithm.

With each handshake or connect, the extension sends timestamps within the ext field like:

{ext:{timesync:{tc:12345567890,l:23,o:4567},...},...}

where:

  • tc is the client timestamp in ms since 1970 of when the message was sent
  • l is the network lag that the client has calculated
  • o is the clock offset that the client has calculated

The accuracy of the offset and lag may be calculated with tc-now-l-o, which should be zero if the calculated offset and lag are perfectly accurate.

A Bayeux server that supports timesync, should respond only if the measured accuracy value is greater than accuracy target.
The response will be an ext field like:

{ext:{timesync:{tc:12345567890,ts:1234567900,p:123,a:3},...},...}

where:

  • tc is the client timestamp of when the message was sent
  • ts is the server timestamp of when the message was received
  • p is the poll duration in ms - ie the time the server took before sending the response
  • a is the measured accuracy of the calculated offset and lag sent by the client

On receipt of the response, the client is able to use current time to determine the total trip time, from which p is subtracted to determine an approximate two way network traversal time. Thus:

  • lag = (now-tc-p)/2
  • offset = ts-tc-lag

In order to smooth over any transient fluctuations, the extension keeps a sliding average of the offsets received.
By default this is over 10 messages, but this can be changed by passing a configuration object during the creation of the extension:

// Unregister the default timesync extension
cometd.unregisterExtension('timesync');

// Re-register with different configuration
cometd.registerExtension('timesync', new org.cometd.TimeSyncExtension({ maxSamples: 20 }));

The client-side timesync extension also exposes several methods to deal with the result of the time synchronization:

  • getNetworkLag(), to obtain the calculated network latency between client and server
  • getTimeOffset(), to obtain the offset between the client's clock and the server's clock in ms
  • getServerTime(), to obtain the server's time
  • setTimeout(), to schedule a function to be executed at a certain server time

Reload

Reload Extension

The reload extension allows a page to be loaded (or the same page to be reloaded) without having to re-handshake in the new (or reloaded) page, therefore resuming the existing CometD connection.
This extension requires only the client-side extension.

Enabling the Client-side Extension

The client side extension binding for Dojo is provided by dojox/cometd/reload.js and it is sufficient to use Dojo's dojo.require mechanism:

dojo.require("dojox.cometd.reload");

The client side extension binding for jQuery is provided by the file jquery.cometd-reload.js. This file must be included in the HTML page via the <script> tag, along with the jQuery cookie plugin and the reload extension implementation:

<script type="text/javascript" src="jquery.cookie.js"></script>
<script type="text/javascript" src="ReloadExtension.js"></script>
<script type="text/javascript" src="jquery.cometd-reload.js"></script>

In both Dojo and jQuery extension bindings, the extension is registered on the default cometd object under the name "reload".

Reload Extension Details

The reload extension allows a page to be reloaded and an existing CometD connection resumed by the reloaded page.

To activate the reload extension, the page must call cometd.reload() just before the page is reloaded (for example, after a link is clicked or on the page unload event).
This saves a short lived cookie with the connection details.
The new page still needs to call subscribe to the channels it is interested in to register the callbacks, since the client state has been completely reset due to the reload.

An example simple usage is:

<html>
    <head>
        <script type="text/javascript" src="dojo/dojo.js"></script>
        <script type="text/javascript">
            dojo.require("dojox.cometd");
            dojo.require("dojox.cometd.reload");

            dojox.cometd.init({ url: "/context/cometd", logLevel: "info" });
            dojox.cometd.subscribe("/some/channel", function() { ... });
            dojox.cometd.subscribe("/some/other/channel", function() { ... });
            
            // On page unload, call dojox.cometd.reload()
            dojo.addOnUnload(dojox.cometd, "reload");
        </script>
    </head>
    <body>
    ...
    </body>
</html>

Transports

JavaScript Cometd Transports

The Bayeux specification defines two mandatory transports:

  • long-polling
  • callback-polling

The JavaScript Cometd implementation implements exactly these two transports.

For most recent browsers (such as Firefox 3.5) it is possible to use the long-polling transport also for cross-domain bayeux communication, see below the cross-domain mode.

The long-polling Transport

The long-polling transport is the default transport.
This transport is used when the communication with the Bayeux server happens on the same domain, and in the cross-domain mode (see below).
The data is sent to the server by means of a POST request with Content-Type text/json via a plain XMLHttpRequest call.

The callback-polling Transport

The callback-polling transport is the transport that is used when the communication with the Bayeux server happens on a different domain (when the cross-domain mode is not supported, see below for the cross-domain mode section).

It is well known that XMLHttpRequest calls have restrictions when the invocation is directed to a domain different from the one the script has been downloaded (but see below the cross-domain mode for an alternative solution).
To overcome XMLHttpRequest restrictions, this transport uses the JSONP script injection: instead of using XMLHttpRequest it injects a <script> element whose src attribute points to the Bayeux server.
The browser will notice the script element injection and performs a GET request to the specified source URL.
The Bayeux server is aware that this is a JSONP request and replies with a JavaScript function that is then executed by the browser (and that calls back into the JavaScript Cometd implementation).

There are three main drawbacks in using this transport:

  • The transport is chattier.
    This is due to the fact that the browser executes the injected scripts sequentially, and until a script has been completely "downloaded", it cannot be executed.
    For example, imagine a communication that involves a script injection for the long poll, and a script injection for a message publish. The browser injects the long poll script, a request is made to the Bayeux server, but the Bayeux server holds the request waiting for server-side events (so the "script" is not "downloaded"). Then the browser injects the publish script, the request is made to the Bayeux server, which replies (so the "script is "downloaded"). However, the browser does not execute the second script, because it has not executed the first yet (since its "download" is not finished). In these conditions, the publish would be executed only after the long poll returns. To avoid this situation the Bayeux server, in case of callback-polling transport, resumes the client's long poll for every message that arrives from that client, and that's why the transport is chattier: the long poll returns more often.
  • The message size is limited.
    This is necessary to support IE7, that has a 2083 character limit for GET requests.
  • The reaction to failures is slower.
    This is due to the fact that if the script injection points to a URL that returns an error (for example the Bayeux server is down), this is silently ignored by the browser.

The cross-domain Mode

Firefox 3.5 introduced the capability for XMLHttpRequest calls to be performed towards a different domain (see here).

As of version 1.0.0.rc0, this is supported also in the JavaScript Cometd implementation, with no configuration necessary on the client (if the browser supports XMLHttpRequest cross-domain calls, they will be used) and with a bit of configuration for the server. Refer to this document for the server configuration.

To use the cross-domain mode, you need:

  • a cross-domain compliant browser (for example Firefox 3.5)
  • a compliant server (for example Jetty configured with the CrossOriginFilter)

With this setup, even when the communication with the Bayeux server is cross-domain, the long-polling transport will be used, avoiding the drawbacks of the callback-polling transport.

CometD Java

CometD Java Implementation

The CometD Java implementation is based on the popular Jetty Http Server and Servlet Container, for both the client and the server.

The CometD Java implementation, though based on Jetty, is portable on other Servlet 2.5 compliant servlet containers (because it uses the portable Jetty Continuation API).

The war file resulting from the development of your CometD web application can be deployed to other Servlet 2.5 compliant servlet containers, but it will scale less because it will be less integrated with the servlet container.
When deployed to a Servlet 3.0 compliant servlet container, the CometD implementation will make use of the asynchronous features offered by the servlet container and will be fully portable and scalable (the scalability will be as good as the servlet container's implementation).

The CometD Java Implementation offers a client library and a server library, documented in details in the following sections.

Server APIs

Cometd Java Server Implementation

In order to run the Cometd server implementation, you need to deploy a Java web application to a servlet container.

The web application needs to configure the Cometd servlet (see here for configuration details) to interpret the Bayeux protocol.

Most of the times it is also needed to write one or more user-defined services that can act upon receiving messages on Bayeux channels.

Configuration

Java Server Cometd Servlet Configuration

Basic Configuration

The Cometd servlet must be setup in web.xml.
If you followed the primer, then Maven has configured the web.xml file for you, but here we will detail its configuration.
This is a sample web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.server.continuation.ContinuationCometdServlet</servlet-class>
        <init-param>
            <param-name>timeout</param-name>
            <param-value>60000</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

</web-app>

The org.cometd.server.continuation.ContinuationCometdServlet must be defined and mapped in web.xml, or otherwise the server will not be able to interpret the Bayeux protocol.
It is normally mapped on /cometd/*, but you can change the mapping url-pattern at your wish.

Here is the list of configuration init parameters accepted by the ContinuationCometdServlet:

Parameter Name Default Value Parameter Description
timeout 30000 The time, in milliseconds, that specifies the server-side long poll timeout
interval 0 The time, in milliseconds, that specifies how much the client must wait between long poll requests
maxInterval 10000 The max period of time, in milliseconds, after which a client that did not issue a long poll is considered disappeared and is removed
logLevel 0 The log level; 0 = warn, 1 = info, 2 = debug
multiFrameInterval -1 The period of time, in milliseconds, that specifies the client normal polling period in case the server detects more than one tab/frame connected from the same browser. A non-positive value means that the second tab/frame will be disconnected.
requestAvailable false Whether or not the current HttpServletRequest should be returned by Bayeux.getCurrentRequest()
filters The path of a JSON file, relative to the WEB-INF directory of the war, that specifies DataFilters to be installed
jsonDebug false Whether or not the full JSON input should be kept for debugging purposes
channelIdCacheLimit 0 The limit of the ChannelId cache. Set to -1 to disable caching, set to 0 for no limits, set to any positive value to clear the cache once the limit has been reached

 

Advanced Configuration

If you are using Jetty 7, you may want to configure also the CrossOriginFilter.
This filter implements the Cross-Origin Resource Sharing specification, and allows recent browsers that implements it (as of November 2009, Firefox 3.5.x, Chrome 3.x and Safari 4.x) to perform cross-domain JavaScript requests (see also the transport section).
Here is an example of web.xml configuration for the CrossOriginFilter:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.server.continuation.ContinuationCometdServlet</servlet-class>
        <init-param>
            <param-name>timeout</param-name>
            <param-value>60000</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

    <filter>
        <filter-name>cross-origin</filter-name>
        <filter-class>org.eclipse.jetty.servlets.CrossOriginFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>cross-origin</filter-name>
        <url-pattern>/cometd/*</url-pattern>
    </filter-mapping>

</web-app>

Refer to this document for the filter configuration.

Authorization

Java Server Cometd API: Authorization

The Bayeux object can be configured with a org.cometd.SecurityPolicy object, which allows to control various steps of the Bayeux protocol such as handshake, subscription, publish, etc.
By default, the Bayeux object does not have a SecurityPolicy installed, which means that any operation is authorized.

The org.cometd.SecurityPolicy has a default implementation in org.cometd.server.AbstractBayeux$DefaultPolicy, that is useful as a base class in case of customization of the SecurityPolicy (see how authentication works for an example).

The org.cometd.SecurityPolicy methods are:

boolean canHandshake(Message message);

boolean canCreate(Client client, String channel, Message message);

boolean canSubscribe(Client client, String channel, Message messsage);

boolean canPublish(Client client, String channel, Message messsage);

The methods are self-speaking and control, respectively, if an handshake, a channel creation, a subscription to a channel and a publish to a channel are to be authorized.

The default implementation org.cometd.server.AbstractBayeux.DefaultPolicy:

  • allows any handshake
  • allows creation of channel only from clients that handshook and only if the channel is not a meta channel
  • allows subscription from clients that handshook, but not if the channel is a meta channels or the global channel wildcards /** and /*
  • allows publish from clients that handshook to any channel or from clients that want to handshake to the handshake meta channel only

To understand how to install your custom SecurityPolicy on the Bayeux object, see how it is done in the authentication howto.

Services

Cometd Services

A Cometd service is a Java class that allow a developer to specify the code to run when Bayeux messages are received on Bayeux channels.

Cometd Service Implementation

A Cometd service is a Java class that extends the Cometd class org.cometd.server.BayeuxService, that specifies the Bayeux channels the service is interested to, and that adheres to the contract required by the BayeuxService class:

public class EchoService extends BayeuxService                       (1)
{
    public EchoService(Bayeux bayeux)                                (2)
    {
        super(bayeux, "echo");                                       (3)
        subscribe("/echo", "processEcho");                           (4)
    }

    public void processEcho(Client remote, Map<String, Object> data) (5)
    {
        remote.deliver(getClient(), "/echo", data, null);            (6)
    }
}

This is a simple echo service that returns the message sent by the remote client on channel "/echo" to the remote client itself.

Note the following:

  • In (1) we extend from org.cometd.server.BayeuxService
  • In (2) we create a constructor that takes a org.cometd.Bayeux object
  • In (3) we call the superclass constructor, passing the Bayeux object and an arbitrary name of the service, in this case "echo"
  • In (4) we subscribe to channel "/echo", and we specify the name of a method that must be called when a message arrives to that channel
  • In (5) we define a method with the same name specified in (4), and with an appropriate signature (see below)
  • In (6) we use the org.cometd.Client API to echo the message back to that particular client

The contract that the BayeuxService class requires for callback methods is that the methods must have one of the following signatures:

// Obtains the remote client object and the message object
public void processEcho(Client remote, Message message)

// Obtains the remote client object and the message's data object
// (additional message information, such as the channel or the id is lost)
public void processEcho(Client remote, Map<String, Object> data)

// Obtains the remote client object, the channel name, the message object and the message id
public void processEcho(Client remote, String channelName, Message message, String messageId)

// Obtains the remote client object, the channel name, the message's data object and the message id
public void processEcho(Client remote, String channelName, Map<String, Object> data, String messageId)

Note that the channel name specified in the subscribe() method can be a wildcard, for example:

public class BaseballTeamService extends BayeuxService
{
    public BaseballTeamService(Bayeux bayeux)
    {
        super(bayeux, "baseballTeam");
        subscribe("/baseball/team/*", "processBaseballTeam");
    }

    public void processBaseballTeam(Client remote, String channelName, Map<String, Object> data, String messageId)
    {
        // Upon receiving a message on channel /baseball/team/*, forward to channel /events/baseball/team/*
        getBayeux().getChannel("/events" + channelName, true).publish(getClient(), data, null);
    }
}

Note also how in the first example we used Client.deliver() to send a message to a particular remote client, while in the second we used Channel.publish() to send a message to anyone who subscribed to channel "/events/baseball/team/*".

Once you have written your Bayeux services it is time to setup them in your web application, plain style or Spring style.

Integration

Bayeux Services Integration

There are several ways to integrate your Bayeux services into your web application.

All of these ways are complicated by the fact that the Bayeux object is created by a servlet, and there is no easy way to detect, in general, when the Bayeux object has been created.

Integration via Configuration Servlet

The simplest way to initialize your web application with your Bayeux services is to use a configuration servlet.
This configuration servlet will have no mapping, because its only scope is to initialize (or "wire" together) your services for your web application to work properly.

Following you can find a sample web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.server.continuation.ContinuationCometdServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>configuration</servlet-name>
        <servlet-class>com.acme.cometd.ConfigurationServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
    </servlet>

</web-app>

Note how we specified <load-on-startup> to be 1 for the Cometd servlet (so that the Bayeux object gets created and put in the ServletContext), and to be 2 for the configuration servlet, so that it will be initialized only after the Cometd servlet has been initialized and hence the Bayeux object be available.

This is the code for the ConfigurationServlet:

public class ConfigurationServlet extends GenericServlet
{
    public void init() throws ServletException
    {
        // Grab the Bayeux object
        Bayeux bayeux = (Bayeux)getServletContext().getAttribute(Bayeux.ATTRIBUTE);
        new EchoService(bayeux);
        // Create other services here

        // This is also the place where you can configure the Bayeux object
        // by adding extensions or specifying a SecurityPolicy
    }

    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException
    {
        throw new ServletException();
    }
}

See here about the EchoService

Integration via Configuration Listener

Instead of using a configuration servlet, it is possible to use a configuration listener, by writing a class that implements ServletContextAttributeListener.

Following you can find the web.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.server.continuation.ContinuationCometdServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

    <listener>
        <listener-class>com.acme.cometd.BayeuxInitializer</listener-class>
    </listener>

</web-app>

This is the code for the BayeuxInitializer:

public class BayeuxInitializer implements ServletContextAttributeListener
{
    public void attributeAdded(ServletContextAttributeEvent event)
    {
        if (Bayeux.ATTRIBUTE.equals(event.getName()))
        {
            // Grab the Bayeux object
            Bayeux bayeux = (Bayeux)event.getValue();
            new EchoService(bayeux);
            // Create other services here

            // This is also the place where you can configure the Bayeux object
            // by adding extensions or specifying a SecurityPolicy
        }
    }

    public void attributeRemoved(ServletContextAttributeEvent event)
    {
    }

    public void attributeReplaced(ServletContextAttributeEvent event)
    {
    }
}

Spring Integration

Bayeux Service Integration with Spring

Integration of CometD services with Spring is particularly interesting, since most of the times your Bayeux services will require other beans to perform their service.
Not all Bayeux services are as simple as the EchoService, and having Spring's dependency injection (as well as other facilities) integrated greatly simplifies development.

Below you can find 3 proposed strategies to integrate with Spring.

Late Spring Initialization

This strategy delays Spring initialization until the CometD servlet is initialized.
This strategy is suited for those cases where Spring beans can be initialized late because no other services or frameworks need Spring to be initialized upfront.

This strategy requires a bit of coding to glue Spring and Bayeux services together.
The reason to require glue code is twofold:

  • the Bayeux object is not created by Spring (but by the CometD servlet), so any bean that depends on the Bayeux object must be initialized only after the CometD servlet
  • the order of initialization of servlets and listeners in web.xml is not defined by the Servlet Specification (the order will only be fully specified in servlet 3.0)

So, in order to guarantee a portable initialization, a bit of code is required.

Below you can find the configuration files and the code needed to integrate with Spring.

First, the web.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.server.continuation.ContinuationCometdServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

    <listener>
        <listener-class>com.acme.cometd.spring.LateSpringBayeuxInitializer</listener-class>
    </listener>

</web-app>

Note how we use a listener to initialize Spring. This listener completely replaces Spring's org.springframework.web.context.ContextLoaderListener, but it is based on the same classes used by it to perform context initialization at web application startup.
This means that the LateSpringBayeuxInitializer looks up by default Spring beans located in /WEB-INF/applicationContext.xml, or in locations specified by the servlet context init param contextConfigLocation, as per usual Spring configuration.

Second, Spring's applicationContext.xml file, defining the EchoService we defined earlier:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <bean id="echoService" class="com.acme.cometd.EchoService">
        <constructor-arg><ref bean="bayeux" /></constructor-arg>
    </bean>

</beans>

Note how there is a reference to the "bayeux" bean, which is not defined in any Spring XML file, but setup by LateSpringBayeuxInitializer, see below.

Lastly, the glue code to integrate Spring, the LateSpringBayeuxInitializer class referenced in web.xml above:

public class LateSpringBayeuxInitializer implements ServletContextListener, ServletContextAttributeListener
{
    private volatile ContextLoader loader;

    public void contextInitialized(ServletContextEvent event)
    {
    }

    public void contextDestroyed(ServletContextEvent event)
    {
        ContextLoader loader = this.loader;
        if (loader != null)
            loader.closeWebApplicationContext(event.getServletContext());
    }

    public void attributeAdded(ServletContextAttributeEvent event)
    {
        if (Bayeux.ATTRIBUTE.equals(event.getName()))
        {
            Bayeux bayeux = (Bayeux) event.getValue();

            StaticListableBeanFactory factory = new StaticListableBeanFactory();
            factory.addBean("bayeux", bayeux);
            GenericApplicationContext bayeuxApplicationContext = new GenericApplicationContext(new DefaultListableBeanFactory(factory));
            bayeuxApplicationContext.refresh();

            loader = new BayeuxContextLoader(bayeuxApplicationContext);
            ApplicationContext applicationContext = loader.initWebApplicationContext(event.getServletContext());

            customizeBayeux(bayeux, applicationContext);
        }
    }

    public void attributeRemoved(ServletContextAttributeEvent event)
    {
    }

    public void attributeReplaced(ServletContextAttributeEvent event)
    {
    }

    protected void customizeBayeux(Bayeux bayeux, ApplicationContext applicationContext)
    {
    }

    private static class BayeuxContextLoader extends ContextLoader
    {
        private final ApplicationContext parentApplicationContext;

        public BayeuxContextLoader(ApplicationContext parentApplicationContext)
        {
            this.parentApplicationContext = parentApplicationContext;
        }

        @Override
        protected ApplicationContext loadParentContext(ServletContext servletContext) throws BeansException
        {
            return parentApplicationContext;
        }
    }
}

Note how you can override the customizeBayeux() method and further customize the Bayeux object by, for example, looking up a org.cometd.SecurityPolicy object from Spring's application context and set it on the Bayeux object. Or, in the same way, add your own Spring-configured Bayeux extensions to the Bayeux object.

Lazy Spring Initialization

This strategy allows for normal Spring initialization, but requires that Bayeux services are marked as lazy (and recursively all other beans/services that depend on Bayeux services), again to allow the CometD servlet to initialize properly.
This strategy is suited for those cases where other frameworks (for example, Struts2) require Spring to be initialized upfront.

This strategy requires a bit of coding to be able to access the Bayeux object from Spring configuration files.

First, the web.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.server.continuation.ContinuationCometdServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <listener>
        <listener-class>com.acme.cometd.spring.LazySpringBayeuxInitializer</listener-class>
    </listener>

</web-app>

Note how we use the normal Spring listener (ContextLoaderListener) to initialize Spring, but we also add another listener, LazySpringBayeuxInitializer.
The LazySpringBayeuxInitializer is the glue code that we need to make the Bayeux object accessible from Spring configuration files:

public class LazySpringBayeuxInitializer implements ServletContextAttributeListener
{
    public void attributeAdded(ServletContextAttributeEvent event)
    {
        if (Bayeux.ATTRIBUTE.equals(event.getName()))
        {
            Bayeux bayeux = (Bayeux) event.getValue();
            BayeuxHolder.setBayeux(bayeux);
        }
    }

    public void attributeRemoved(ServletContextAttributeEvent event)
    {
    }

    public void attributeReplaced(ServletContextAttributeEvent event)
    {
    }
}

The LazySpringBayeuxInitializer class makes use of another class, BayeuxHolder, that is very simple:

public class BayeuxHolder
{
    private static volatile Bayeux bayeux;

    public static void setBayeux(Bayeux bayeux)
    {
        BayeuxHolder.bayeux = bayeux;
    }

    public static Bayeux getBayeux()
    {
        return bayeux;
    }
}

At this point, we just need to write Spring's applicationContext.xml file as follows:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <bean id="nonLazyService" class="com.acme..." />

    <bean id="bayeux" class="com.acme.cometd.spring.BayeuxHolder" factory-method="getBayeux" lazy-init="true" />

    <bean id="echoService" class="com.acme.cometd.EchoService" lazy-init="true">
        <constructor-arg><ref local="bayeux" /></constructor-arg>
        <constructor-arg><ref local="nonLazyService" /></constructor-arg>
    </bean>

</beans>

Note how the Spring configuration file can have normal beans (such as nonLazyService), and how those beans can be injected in lazy beans such as our EchoService.
Also, note how we make use of the BayeuxHolder, which is lazy-initialized, to retrieve the Bayeux object stored by LazySpringBayeuxInitializer when the CometD servlet is initialized.

It is very important to note that the EchoService, which also is lazy-initialized, can be injected only in other lazy-initialized beans or in non-singleton beans (such as beans in scope prototype); if it is injected in singleton non-lazy beans, it will not be ready (because the CometD servlet is not yet initialized), and you will get an error.

Full Spring Initialization

This strategy initializes the Bayeux object directly in the Spring configuration file, and injects it in the servlet context, where it is picked up by the CometD servlet.
This strategy relies a bit more on CometD implementation details and may require changes if the implementation details change.
It may or may not require glue code, depending on other details of the application being developed (see below).
It requires also a bit more of discipline because the Bayeux object can now be configured in 2 places, the Spring configuration file and the web.xml file, and you don't want to split the Bayeux configuration in different places.

The web.xml file is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.server.continuation.ContinuationCometdServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

</web-app>

Spring's applicationContext.xml is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <bean id="nonLazyService" class="com.acme..." />

    <bean id="bayeux" class="org.cometd.server.continuation.ContinuationBayeux">
        <property name="timeout" value="15000" />
    </bean>

    <bean class="org.springframework.web.context.support.ServletContextAttributeExporter">
        <property name="attributes">
            <map>
                <entry key="org.cometd.bayeux">
                    <ref local="bayeux" />
                </entry>
            </map>
        </property>, and therefore 
    </bean>

    <bean id="echoService" class="com.acme.cometd.EchoService" lazy-init="true">
        <constructor-arg><ref local="bayeux" /></constructor-arg>
        <constructor-arg><ref local="nonLazyService" /></constructor-arg>
    </bean>

</beans>

Note how the implementation class of the Bayeux object is now referenced in applicationContext.xml and how the Bayeux object is exported into the servlet context via Spring's ServletContextAttributeExporter.
This only creates an un-initialized Bayeux object; only after the CometD servlet has been initialized the Bayeux object is ready to be used.
Because of this, it is important that the echoService (and any other Bayeux service and, recursively, other dependent services) is lazy-initialized.

Since the echoService is lazy-initialized, there must be a way to tell Spring to actually instantiate and initialize it when the application first needs it.

If you are using another framework that rely on Spring for dependency injection, then this step is performed by the other framework. For example, if you use Struts2 with Spring support, you don't need to tell Spring to instantiate and initialize you CometD services, because Struts2 will recognize that there is the need to inject your CometD service (for example in a Struts2 action) and Struts2 will ask Spring to provide it.
Therefore, the configuration above is perfectly sufficient.

However, if you are not using other frameworks, you need to manually tell Spring to instantiate and initialize your CometD services.
The technique to do so is similar to what described for the non-Spring service integration: writing a configuration servlet or listener that will reference your lazy-initialized CometD services. Below an example:

public class ConfigurationServlet extends GenericServlet
{
    public void init() throws ServletException
    {
        // Grab Spring's ApplicationContent
        ApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());

        // Trigger CometD service initialization
        context.getBean("echoService");
    }
    
    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException
    {
        throw new ServletException();
    }
}

And of course this configuration servlet must be mapped with a higher value for the load-on-startup element in web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
         version="2.5">

    <servlet>
        <servlet-name>cometd</servlet-name>
        <servlet-class>org.cometd.server.continuation.ContinuationCometdServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>cometd</servlet-name>
        <url-pattern>/cometd/*</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>configuration</servlet-name>
        <servlet-class>com.acme.cometd.ConfigurationServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
    </servlet>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

</web-app>

org.cometd.Bayeux

Java Server Cometd API: Bayeux

The org.cometd.Bayeux interface is the central object that manages remote Cometd clients (or, better, their server-side counterpart) and channels.
One only of these objects exist for each web application, and it is created by the ContinuationCometdServlet, and put in the web application ServletContext to be accessed by other server side components.
See also the services section for how to retrieve and integrate the Bayeux object in your web application.

We have seen in the authorization section, how it is possible to use

Bayeux.setSecurityPolicy(SecurityPolicy policy)

to set the security policy for the Bayeux object, so that we can have full control on the authorization bits.

Another useful method is

Bayeux.getChannel(String channel, boolean create)

which allows to retrieve, and eventually create, a channel on which it is possible to publish messages, that will be received by all remote clients subscribed to that channel.
For example, imagine an online game where a player acts and we need to tell other players what that player did.
The acting player will communicate to the server its action, and the server will forward the action to other players:

public class GameService extends BayeuxService
{
    ...
    public void processPlayerAction(Client remote, Message message)
    {
        // Forward the action to other players
        Channel channel = getBayeux().getChannel("/dungeons/blue_cavern", false);
        channel.publish(getClient(), message.getData(), null);
    }
    ...
}

Sometimes, however, we need to send a message to a very specific client.
Imagine you're chatting with a friend, and you both logged in with your credentials at an imaginary site called www.bayeux-chat.org.
When you registered at www.bayeux-chat.org, the application stored your information in a database row with userId=100, and your friend was registered with userId=200.
When you connect to www.bayeux-chat.org to chat with your friend, you also have a Bayeux clientId=1234ABCD, while your friend has a Bayeux clientId=5678EFGH; you decide to start chatting privately with your friend.
Your chat message needs to contain an identifier of your friend (so the server will know to whom, of the thousands client connected, send your message), let's say it's the database's userId=200, but to deliver him your message, the server has to find the server-side Bayeux client object that represents your friend, and can do that using a mapping from userIds to clientId, and

Bayeux.getClient(String clientId)

in the following way:

public class ChatService extends BayeuxService
{
    ...
    public void processPrivateChat(Client remote, Map<String, Object> data)
    {
        // Extract the userId
        String friendUserId = (String)data.get("friendUserId");

        // Do not forget to check if that friendUserId is really your friend !

        // Map the database userId to a Bayeux clientId via some other service
        String friendClientId = this.userToClientMapper.get(friendUserId);
        
        Client remoteFriend = getBayeux().getClient(friendClientId);
        remoteFriend.deliver(getClient(), "/chat-room/1", data, null);
    }
    ...
}

How you map userIds to clientIds it's project dependent, but you can find some ideas in the authentication section.

Furthermore, it is possible to obtain the current HttpServletRequest from the Bayeux object via

Bayeux.getCurrentRequest()

This method returns null by default, unless the requestAvailable configuration parameter is set to true, see the configuration section.
From the HttpServletRequest it is possible to obtain the HttpSession, the ServletContext, etc. for any need you may have to interact with the servlet API.

Lastly, it is possible to add extensions to the Bayeux object, that will affect all clients, via:

Bayeux.addExtension(Extension)
Bayeux.removeExtension(Extension)

For a discussion about extensions, see here.

org.cometd.Channel

Java Server Cometd API: Channel

The org.cometd.Channel class represent a named "topic" to which clients subscribe to receive messages.

The name of a channel resembles that of a filesystem directory, for example: /level_3/dungeons/diamonds_cavern or /nyse/stocks/JAVA.
You can find more information on channel in the Bayeux channel section.

The most common operation on channel is to publish a message on it, using:

Channel.publish(Client local, Object data, String messageId)

The first argument represent the server-side local client that sends the message, and most of the time can be null, as the implementation will use a default value.
The second argument represent the data that you want to send, and the third argument is the messageId, which can be null, as the implementation will generate a new value.

public class StockService extends BayeuxService
{
    ...
    public void stockChanged(String stockName, double price)
    {
        Channel channel = getBayeux().getChannel("/nyse/stocks/" + stockName, false);
        if (channel != null)
        {
            Map<String, Object> data = new HashMap<String, Object>();
            data.put("price", price);
            channel.publish(null, message, null);
        }
    }
    ...
}

A channel may have one or more org.cometd.DataFilters that are used to modify the incoming and outgoing messages (for example by replacing XML markups such as <foo> with &lt;foo&gt;). The data filters are added and removed via:

Channel.addDataFilter(DataFilter filter);
Channel.removeDataFilter(DataFilter filter);

Channels may be set to be persistent, which means that when the last subscriber unsubscribes, the channel object will remain in memory and can be accessed via the Bayeux API.

Channel.isPersistent()
Channel.setPersistent(boolean persistent)

Channels may also be set as lazy, which means that all messages sent to that channel will be marked as lazy. A lazy message is a message that does not wake up the long poll to be delivered immediately to the client, but it will be delivered on the first occasion (either a non-lazy message sent to the channel, or the long poll returning because it timed out).

Channel.isLazy()
Channel.setLazy(boolean lazy)

Subscriptions to channels are normally not done directly on the channel, but via the Client API.

org.cometd.Client

Java Server Cometd API: Client

The org.cometd.Client class is the server-side representation of a remote Bayeux client, that is, a client that connected successfully to the server using the Bayeux protocol.

An Client is identified by a unique clientId, which can be obtained via

Client.getId()

and can be used in conjunction with Bayeux.getClient(String).

Similarly to the Bayeux API, it is possible to add extensions to a Client instance. Differently from the Bayeux case where the extension is applied to all clients, in this case the extension is applied to this particular client only.

Client.addExtension(Extension extension)
Client.removeExtension(Extension extension)

For a discussion about extensions, see here.

Of particular interest are the APIs to add/remove listeners on a Client instance.

Client.addListener(ClientListener listener)
Client.removeListener(ClientListener listener)

The org.cometd.ClientListener interface is empty, but its subinterfaces are more interesting.

org.cometd.RemoveListener is the subinterface upon which

RemoveListener.removed(String clientId, boolean timeout)

gets called when the Bayeux server detects that a client has disconnected, either orderly (i.e. the client called disconnect()) or because or network failures or client crashes.
The listener is notified after the server-side data structures have already been cleaned up so, for example, calling Bayeux.getClient(String) from RemoveListener.removed(String, boolean) would return null.
There is an example of usage of the RemoveListener in the authentication example.

org.cometd.MessageListener is the subinterface upon which

MessageListener.deliver(Client from, Client to, Message message)

gets called when the client receives a message.
While normally it is better to use a Bayeux service to implement your business logic, a MessageListener may be used to count the number of messages, as well as to measure the latency between when a message was sent and when it was received, for example in collaboration with the timesync extension.

For completeness, there are other subinterfaces of ClientListener, but they are somewhat less interesting and documented in their relative javadocs.

We have already seen how it is possible to send a message using the Client interface.
It is however possible send multiple messages in a single batch, for example:

public class BatchingService extends BayeuxService
{
    ....
    public void processBatch(Client remote, Message message)
    {
        remote.startBatch();

        Map<String, Object> externalData = new HashMap<String, Object>();
        // Fill the external data
        remote.deliver(getClient(), "/external", externalData, null);

        Map<String, Object> emailData = new HashMap<String, Object>();
        // Fill the email data
        remote.deliver(getClient(), "/email", emailData, null);

        remote.endBatch();
    }
    ...
}

Multiple Clients

Multiple CometD Clients

The HTTP protocol recommends a connection limit of 2 connections per domain.
Virtually all browsers adhere to this recommendation, thus any iframes, tabs or windows on the same browser connecting to the same host need to share the two connections.

If two iframes/tabs/windows initiate a Bayeux communication, both will start a long poll connect request and both connections will be consumed.

The CometD Server implementation implements the multiple-clients advice specified in the Bayeux specification.
The server uses "BAYEUX_BROWSER" cookie to detect multiple CometD clients from the same browser.

If multiple clients are detected, then only one long poll connection is allowed and subsequent long poll requests will not wait for messages before returning. They will return immediately with the multiple-clients field of the advice object set to true. The advice will also contain an interval field set to the value of the "multiFrameInterval" servlet init parameter (see here). This instructs the client not to send another long poll until that interval has passed.

The effect of this advice is that additional client connections will poll the server with a period of "multiFrameInterval". This avoids consume both HTTP connections at the cost of some latency for the additional iframes/tabs/windows.

It is recommended that the client application monitor the /meta/connect channel for multiple-clients field in the advice. If detected, the application may ask the user to close the additional tabs, or it could automatically close them or take some other action.

Client APIs

CometD Java Client Implementation

The CometD client implementation can be used in any JSETM or JEETM application.

It is made of one main class, org.cometd.client.BayeuxClient, that depends on Jetty's asynchronous HttpClient.

Typical usages of the CometD Java client are:

  • As the transport for a rich thick Java UI (for example, Swing) to communicate to a Bayeux Server (also via firewalls)
  • As a load generator to simulate thousands of CometD clients, like for example org.cometd.client.BayeuxLoadGenerator

A BayeuxClient also implements the org.cometd.Client interface, so all information given in the Client section regarding extensions and listeners is valid also for BayeuxClient.
Furthermore, the CometD Java Client API is very similar to the CometD JavaScript Client API, so reading that section may give you a better understanding.

The following sections will go in detail about the JavaScript Bayeux APIs and their implementation secrets.

Handshake

Java Cometd Client API: Hanshake

To initiate the communication with the Bayeux server, you need to call

BayeuxClient.start()

A typical usage is the following:

// Create (and eventually setup) Jetty's HttpClient
HttpClient httpClient = new HttpClient();
// Here setup Jetty's HttpClient, for example:
// httpClient.setMaxConnectionsPerAddress(2);
httpClient.start();

BayeuxClient client = new BayeuxClient(httpClient, "http://localhost:8080/cometd");
// Here setup the BayeuxClient, for example:
// client.addListener(new MessageListener() { ... });
client.start();

The BayeuxClient is created with Jetty's HttpClient and with the URL that points to the Bayeux server.

When BayeuxClient.start() is called, the BayeuxClient will perform the handshake with the Bayeux server and then will establish the long poll connection, asynchronously.

Note
Calling start() does not mean that you have completed the handshake with the server when start() returns.

To verify if the handshake is successful, you can add a MessageListener before calling BayeuxClient.start():

HttpClient httpClient = ...
BayeuxClient client = new BayeuxClient(httpClient, "http://localhost:8080/cometd");
client.addListener(new MessageListener() 
{ 
    public void deliver(Client from, Client to, Message message)
    {
        if (Bayeux.META_HANDSHAKE.equals(message.getChannel())
        {
            Boolean successful = message.get(Bayeux.SUCCESSFUL_FIELD);
            if (successful != null && successful)
            {
                // Here handshake is successful
            }
        }
    }
});
client.start();

Subscription

Java Cometd Client API: Subscribing and Unsubscribing

Subscription and unsubscription is done very similarly to the JavaScript counterpart explained here.

The Java API is only slightly different since it must register a MessageListener before subscribing to a channel:

public class Example 
{
    private static final String CHANNEL = "/foo";
    private final MessageListener fooListener = new FooListener();

    public void attach()
    {
        HttpClient httpClient = ...
        BayeuxClient client = new BayeuxClient(httpClient, "http://localhost:8080/cometd");
        client.start();

        client.addListener(fooListener);
        client.subscribe(CHANNEL);
    }

    private static class FooListener implements MessageListener
    {
        public void deliver(Client from, Client to, Message message)
        {
            if (CHANNEL.equals(message.getChannel())
            {
                // Here we received a message on the channel
            }
        }
    }
}

Note
Calling subscribe() does not mean that you have completed the subscription with the server when subscribe() returns.

Differently from JavaScript, a MessageListener is notified for any type of channel (hence for meta channels as well as for normal channels).
Similarly, subscribe() only works properly for normal channels.

Unsubscription is straightforward: if you unsubscribe, messages on that channel will not be delivered to message listeners.
Using the Example class above:

public class Example 
{
    ...
    public void detach()
    {
        client.removeListener(fooListener);
        client.unsubscribe(CHANNEL);
    }
}

Tip
It is recommended that you remove also the MessageListener that you registered when you subscribed.
To make this simpler, avoid using the anonymous inner class style to register a message listener, since otherwise you will not have its reference when you want to remove it.

Publish

Java Cometd Client API: Publishing

Publishing a message on a channel is achieved using the method:

BayeuxClient.publish(String channel, Object data, String messageId)

A typical example is:

HttpClient httpClient = ...
BayeuxClient client = new BayeuxClient(httpClient, "http://localhost:8080/cometd");
client.start();

Map<String, Object> data = new HashMap<String, Object>();
// Fill in the data
client.publish("/game/table/1", data, null);

Like its JavaScript counterpart, publishing data on a channel is an asynchronous operation.

Note
Calling publish() does not mean that you have published the message when publish() returns.

Message batching works in a way similar to the JavaScript message batching:

HttpClient httpClient = ...
BayeuxClient client = new BayeuxClient(httpClient, "http://localhost:8080/cometd");
client.start();

client.startBatch();
try
{
    Map<String, Object> data = new HashMap<String, Object>();
    // Fill in the data map object
    client.publish("/game/table/1", data, null);

    Map<String, Object> extra = new HashMap<String, Object>();
    // Fill in the extra map object
    client.publish("/extra/1", data, null);
}
finally
{
    client.endBatch();
}

Warning
Remember to call endBatch() after having called startBatch(), for example in a finally block.
If you don't, your messages will continue to queue up, and your application will not work as expected.

Disconnection

Java Cometd Client API: Disconnecting

Like the JavaScript counterpart, disconnecting is straightforward:

BayeuxClient.disconnect();

Oort Clustering

The Oort cluster is a cometd cluster built using both the server and client from cometd-java.

More details to come. For now, see http://svn.cometd.org/trunk/cometd-java/oort/src/main/java/org/cometd/oo...