CometD 2 Java Annotated Services
CometD 2 Annotated Client-Side and Server-Side Services
Support for annotated services is available since CometD 2.1.0.
Classes annotated with @Service qualify as annotated services, both on client-side and on server-side.
Server-Side Annotated Services
Server-side services were usually written by extending the org.cometd.server.AbstractServer class, and instances of these classes have normally singleton semantic and are created and configured at web application startup.
The org.cometd.server.AbstractServer class provides (via inheritance) a few facilities that are useful when implementing the service, mainly access to the ServerSession associated with the service instance and registration of methods as a callbacks to receive messages from channels.
Services may depend on other services (for example a data source to access the database), and may require lifecycle management (i.e. the services have start()/stop() methods that needs to be invoked at appropriate times).
In services extending org.cometd.server.AbstractServer, dependency injection and lifecycle management needed to be written by hand in configuration servlet or configuration listeners.
Annotated server-side services offer full support for CometD features, and limited support for dependency injection and lifecycle management via the org.cometd.annotation.ServerAnnotationProcessor class.
Dependency Injection Support
The CometD project offers limited support for dependency injection, since normally this is accomplished by other frameworks such as Spring or Guice.
In particular, it supports only the injection of the BayeuxServer object on fields and methods (not on constructors) and the injection is performed only if the injection has not been performed yet.
The reason for this limited support is that the CometD project does not want to implement and support a generic dependency injection container, but only offer a simple integration with existing dependency injection containers and a minimal support for required CometD objects (such as the BayeuxServer instance).
| Annotated Style | Inherited Style | |
|---|---|---|
@org.cometd.annotation.Service("echoService")
public class EchoService
{
@javax.inject.Inject
private BayeuxServer bayeux;
}
|
public class EchoService extends AbstractService
{
public EchoService(BayeuxServer bayeux)
{
super(bayeux, "echoService");
}
}
|
The service class is annotated with @Service and specifies the (optional) service name of "echoService".
The BayeuxServer field is annotated with the standard JSR 330 @Inject annotation.
The @Inject annotation is supported (for example by Spring 3.x) for standard dependency injection as specified by JSR 330.
Lifecycle Management Support
The CometD project provides lifecycle management via the standard JSR 250 @PostConstruct and @PreDestroy annotations.
This support is offered for those that do not use a dependency injection container with lifecycle management such as Spring.
Channel Configuration Support
In order to initialize channels before they can be actually referenced for subscriptions, the CometD API provides the BayeuxServer.createIfAbsent(String channelId, ConfigurableServerChannel.Initializer... initializers) method, which allows to pass initializers to configure the given channel.
Furthermore, it is useful to have a configuration step for channels that happens before any subscription or listener addition, for example to configure authorizers on the channel.
In annotated services, you can use the @Configure annotation on methods:
@Service("echoService")
public class EchoService
{
@Inject
private BayeuxServer bayeux;
@Configure("/echo")
public void configure(ConfigurableServerChannel channel)
{
channel.setLazy(true);
channel.addAuthorizer(GrantAuthorizer.GRANT_PUBLISH);
}
}
Session Configuration Support
Services that extend org.cometd.server.AbstractServer have two facility methods to access the LocalSession and the ServerSession, namely getLocalSession() and getServerSession().
In annotated services, this is accomplished using the @Session annotation:
@Service("echoService")
public class EchoService
{
@Inject
private BayeuxServer bayeux;
@org.cometd.annotation.Session
private LocalSession localSession;
@org.cometd.annotation.Session
private ServerSession serverSession;
}
Fields (or methods) annotated with the @Session annotation are optional; you can just have the LocalSession field, or only the ServerSession field, or both or none, depending if you need them or not.
Session fields (or methods) cannot be injected with @Inject. This is because the LocalSession object and the ServerSession object are related, and tied to a particular service instance. Using a generic injection mechanism could have led to confusion (e.g. using the same sessions in two different services, etc.).
Listener Configuration Support
For server-side services, methods annotated with @Listener represent callbacks that are invoked during the server-side processing of the message.
Listener methods get passed a reference to the ServerSession half object that sent the message and to the ServerMessage that the server is processing.
The callback method must have the following signature, or a covariant version of it:
| Annotated Style | Inherited Style | |
|---|---|---|
@Service("echoService")
public class EchoService
{
@Inject
private BayeuxServer bayeux;
@Session
private ServerSession serverSession;
@org.cometd.annotation.Listener("/echo")
public void echo(ServerSession remote, ServerMessage.Mutable message)
{
String channel = message.getChannel();
Object data = message.getData();
remote.deliver(serverSession, channel, data, null);
}
}
|
public class EchoService extends AbstractService
{
public EchoService(BayeuxServer bayeux)
{
super(bayeux, "echoService");
addService("/echo", "echo");
}
public void echo(ServerSession remote, ServerMessage.Mutable message)
{
String channel = message.getChannel();
Object data = message.getData();
remote.deliver(getServerSession(), channel, data, null);
}
}
|
The callback method could return false to indicate that the processing of subsequent listeners should not be performed and that the message should not be published.
Subscription Configuration Support
For server-side services, methods annotated with @Subscription represent callbacks that are invoked during the local-side processing of the message.
The local-side processing is equivalent to the remote client-side processing, but it's local to the server.
The semantic is very similar to the remote client-side processing, in the sense that the message has finished the server-side processing and has been published. When it arrives to the local side the information on the publisher is not available anymore, and the message is a plain org.cometd.bayeux.Message and not a org.cometd.bayeux.server.ServerMessage, exactly how it would happen for a remote client.
This is a rarer use case (most of the times user code needs to be triggered with @Listener semantic), but nonetheless is available.
The callback method must have the following signature:
@Service("echoService")
public class EchoService
{
@Inject
private BayeuxServer bayeux;
@Session
private ServerSession serverSession;
@org.cometd.annotation.Subscription("/echo")
public void echo(Message message)
{
System.out.println("Echo service published " + message);
}
}
Annotation Processing
Annotation processing is done by the org.cometd.annotation.ServerAnnotationProcessor class.
BayeuxServer bayeux = ...; // Create the ServerAnnotationProcessor ServerAnnotationProcessor processor = new ServerAnnotationProcessor(bayeux); // Create the service instance EchoService service = new EchoService(); // Process the annotated service processor.process(service);
After the ServerAnnotationProcessor.process() method returns, the service has been processed by injecting the BayeuxServer object and the sessions objects, by calling initialization lifecycle methods, and by registering listeners and subscribers.
Symmetrically, annotation de-processing is performed by ServerAnnotationProcessor.deprocess(), which deregisters listeners and subscribers, and then calls destruction lifecycle methods (but does not de-inject the BayeuxServer object or session objects).
Client-Side Annotated Services
Client-side services have no equivalent in CometD releases prior 2.1.0.
Like their server-side counterpart, client-side services consist in classes annotated with @Service.
Client-side services have been introduced to reduce the boilerplate code that usually needs to be written:
| Annotated Style | Inherited Style | |
|---|---|---|
@Service
public class Service
{
@Session
private ClientSession bayeuxClient;
@Listener(Channel.META_CONNECT)
public void metaConnect(Message connect)
{
// Connect handling...
}
@Subscription("/foo")
public void foo(Message message)
{
// Message handling...
}
}
|
ClientSession bayeuxClient = ...;
bayeuxClient.getChannel(Channel.META_CONNECT).addListener(new ClientSessionChannel.MessageListener()
{
public void onMessage(ClientSessionChannel channel, Message message)
{
// Connect handling...
}
});
bayeuxClient.handshake();
bayeuxClient.waitFor(1000, BayeuxClient.State.CONNECTED);
bayeuxClient.getChannel("/foo").subscribe(new ClientSessionChannel.MessageListener()
{
public void onMessage(ClientSessionChannel channel, Message message)
{
// Message handling...
}
});
|
Dependency Injection and Lifecycle Management Support
The CometD project does not offer dependency injection for client-side services, but supports lifecycle management via the standard JSR 250 @PostConstruct and @PreDestroy annotations.
Client-side services usually have a shorter lifecycle than server-side services and their dependencies are usually injected directly while creating the client-side service instance.
Session Configuration Support
In client-side annotated services, the @Session annotation allows the service instance to have the ClientSession object injected in a field or method.
Like server-side annotated services, the session field (or method) cannot be injected with @Inject. This is to allow the maximum configuration flexibility between service instances and ClientSession instances.
@Service
public class Service
{
@org.cometd.annotation.Session
private ClientSession bayeuxClient;
}
Listener Configuration Support
In client-side annotated services, methods annotated with @Listener represent callbacks that are called upon receipt of messages on meta channels.
Listener callbacks must not be used to subscribe to normal channels.
| Annotated Style | Inherited Style | |
|---|---|---|
@Service
public class Service
{
@Listener(Channel.META_CONNECT)
public void metaConnect(Message connect)
{
// Connect handling...
}
}
|
bayeuxClient.getChannel(Channel.META_CONNECT).addListener(new ClientSessionChannel.MessageListener()
{
public void onMessage(ClientSessionChannel channel, Message message)
{
// Connect handling...
}
});
|
Subscription Configuration Support
In client-side annotated services, methods annotated with @Subscription represent callbacks that are called upon receipt of messages on normal channels.
| Annotated Style | Inherited Style | |
|---|---|---|
@Service
public class Service
{
@Listener("/foo/*")
public void foos(Message message)
{
// Message handling...
}
}
|
bayeuxClient.getChannel("/foo/*").subscribe(new ClientSessionChannel.MessageListener()
{
public void onMessage(ClientSessionChannel channel, Message message)
{
// Message handling...
}
});
|
Annotation Processing
Annotation processing is done by the org.cometd.annotation.ClientAnnotationProcessor class.
ClientSession bayeuxClient = ...; // Create the ClientAnnotationProcessor ClientAnnotationProcessor processor = new ClientAnnotationProcessor(bayeuxClient); // Create the service instance Service service = new Service(); // Process the annotated service processor.process(service); bayeuxClient.handshake();
Listener callbacks are configured immediately on the ClientSession object, while subscription callbacks are automatically delayed until the handshake is successfully completed.
- Printer-friendly version
- Login to post comments