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.

CometD 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.

CometD Java Server Configuration

CometD Java Server 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 a server will wait for a message before responding to a long poll with an empty response.
interval 0 The time, in milliseconds, that specifies how long the client must wait between the end of one long poll requests and the start of the next.
maxInterval 10000 The max period of time, in milliseconds, that the server will wait for a new long poll from a client before that client is considered invalid 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.

CometD Java Server 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 message);

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

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.

CometD Java Server Services

CometD Java Server API: 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.

CometD Java Server Services Integration

CometD Java Server 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 Services 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> 
    </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.

CometD 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.

CometD Java Client 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();

CometD Java Client 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.

CometD Java Client 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.

CometD Java Client Disconnection

Java CometD Client API: Disconnecting

Like the JavaScript counterpart, disconnecting is straightforward:

BayeuxClient.disconnect();