Blog

Friday, 8 November 2013

Atmosphere configuration with JBoss AS 7.1.1

Atmosphere Framework the most popular framework for building asynchronous web application. It is used in RichFaces, Primefaces and few others leading Java web frameworks and can be used on every servlet-based webserver.

Recently, we decided to implement asynchronous event broadcasting between people logged in to our new application Agido based on fantastic AngularJS framework.
We want to make Agido truly interactive application, and allow people to work together concurrently. So, user's events must be propagated to every other interested team member which is working on the same project.
We have 2 choices:
  1. migrate to new JBoss Wildfly 8.0, and use new pure Java EE 7 API for WebSocket (JSR-356)
  2. give chance to Atmosphere framework on good, old JBoss AS 7.1.1

Our first shot was Wildfly. We implemented early version using JSR-356, but Wildfly was in early Alpha stage, and Undertow bugs were hard to overcome at this time, so we decided to use Atmosphere!
So, what we do want to archive?
  1. Asynchronous transport between client (AngularJS) <-> server (JBoss 7.1.1 with standard JEE6 API)
  2. On server side, events should be propagated trough JMS.
  3. Handling different types of transport protocol, since old browsers don't support cutting-edge technologies, main choices were: WebSockets, HTML5 Server-Sent Events, and for legacy browsers - long-polling (which is interesting concept by the way, http://en.wikipedia.org/wiki/Push_technology#Long_polling)/

Let's start with the server-side.

First step is of course maven:

Two artifacts should be added to our pom.xml, first is core atmosphere module, second one is jms extension.

    org.atmosphere
    atmosphere-runtime-native
    2.0.3
    compile


    org.atmosphere
    atmosphere-jms
    2.0.0
    compile

Configure JBoss AS

Set native="true" flag in standalone.xml file (or domain.xml if you are using domain setup), in subsection "jboss:domain:web:1.1", example entry for developer machine below:

   
   
       
       
   

I assume, that JMS is properly configured.

Create Handlers

@ManagedService(path = "/push/events/{projectId}", broadcaster = JMSBroadcaster.class, broadcasterCache = UUIDBroadcasterCache.class)
@SuppressWarnings("UnusedDeclaration")
public class AtmosphereHandler {

    private final Logger logger = Logger.getLogger(AtmosphereHandler.class);

    @Disconnect
    public void onDisconnect(AtmosphereResourceEvent event)
    {
        if (event.isCancelled()) {
            logger.infov("Browser {0} unexpectedly disconnected.", event.getResource().uuid());
        } else if (event.isClosedByClient()) {
            logger.infov("Browser {0} closed the connection by client action.", event.getResource().uuid());
        }
    }

    @Message(encoders = {EventEncoder.class}, decoders = {EventDecoder.class})
    public Event onMessage(AtmosphereResource atmosphereResource, Event event)
    {
        if (event != null) {
            logger.infov("Received event from client: {0}", event.toString());
            return event;
        } else {
            return new Event();
        }
    }

    @Ready
    public void onReady(final AtmosphereResource resource)
    {
        logger.infov("Browser {0} connected.", resource.uuid());
    }

    @Resume
    public void onResume(final AtmosphereResource resource)
    {
        logger.infov("Browser {0} resumed.", resource.uuid());
    }
}
Annotation @ManagedService tell Atmosphere that, it should use this class for handling all push events for paths like "/push/events/*"
Parameter broadcaster specify which broadcaster implementation will be used for handling events. org.atmosphere.plugin.jms.JMSBroadcaster is the default simple implementation for JMS supported message distribution (from atmosphere-jms artifact). Of course you can write your own implementation. You must remember to annotate it with @BroadcasterService, so Atmosphere will detect it automatically, and should extends AbstractBroadcasterProxy
EventEncoder and EventDecored are simple classes implementing org.atmosphere.config.managed.Decoder and Encoder interfaces.

Add atmosphere servlet configuration in web.xml file

    
    
        AtmosphereServlet
        AtmosphereServlet
        org.atmosphere.cpr.AtmosphereServlet

        
        
            org.atmosphere.cpr.broadcasterCacheClass
            org.atmosphere.cache.UUIDBroadcasterCache
               

        
        
            org.atmosphere.plugin.jms.JMSBroadcaster.JNDINamespace
            java:/
        
        
            org.atmosphere.plugin.jms.JMSBroadcaster.JNDIConnectionFactoryName
            AgidoJMSConnectionFactory
        
        
            org.atmosphere.plugin.jms.JMSBroadcaster.JNDITopic
            topic/agidoEventTopic
        
        

        0
        true
    
    
        AtmosphereServlet
        /push/*
    

Thats all, your server is properly configured!


In this setup, all incomming messages will be handled by separate Broadcaster using JMS selector, which will be the specific path. So newly connected client to path: "/push/events/2" will be handled by Broadcaster instance connected to JMS using "/push/events/2" selector.

Ok, now we know how to handle received message. But how do we send anything to client?
That is simple. In this example we send new event to all clients connected to "/push/events/2" path:
Event event = new Event("Hello client!");
final Broadcaster broadcaster = getBroadcaster(selector);
broadcaster.broadcast(new EventEncoder().encode(event));
What we should do, when we want to send specific event to all connected clients? We can use MetaBroadcaster class.
MetaBroadcaster.getDefault().broadcastTo("/push/events/*", new EventEncoder().encode(event));

Client side

There is a simple wrapper for atmosphere.js library, that you can inside AngularJS https://github.com/bertramdev/angular-atmosphere.

Using server configuration from this post, we can use all transports, except of Websockets! For more details about using client side Atmosphere library, check out useful Atmosphere wiki pages: https://github.com/Atmosphere/atmosphere/wiki