Jean-Francois Arcand works for Ning.com. Previously he has worked for Sun Microsystems where he created Grizzly (NIO Framework) , Atmosphere and was a significant contributor to the GlassFish Application Server. Jean-Francois is a DZone MVB and is not an employee of DZone and has posted 23 posts at DZone. You can read more from them at their website. View Full User Profile

Dead Simple Async REST Application: Getting Started with the Atmosphere Framework

06.11.2009
| 11000 views |
  • submit to reddit

In this part, I describe a simple asynchronous REST application using behaviors.js, prototype.js and the Atmosphere Framework. As usual, you can deploy the app anywhere! If you interested to look for a complex Atmosphere application, take a look at the Twitter revisited one.

To understand the basic of Atmosphere, I recommend you take a quick look at part I and II. This time I will explains a simple application, which uses long polling for suspending connection. First, let's see what the war file structure looks like:

./index.html
./WEB-INF
./WEB-INF/context.xml
./WEB-INF/classes
./WEB-INF/classes/org
./WEB-INF/classes/org/atmosphere
./WEB-INF/classes/org/atmosphere/samples
./WEB-INF/classes/org/atmosphere/samples/rest
./WEB-INF/classes/org/atmosphere/samples/rest/counter
./WEB-INF/classes/org/atmosphere/samples/rest/counter/RestLongPolling.class
./WEB-INF/lib
./WEB-INF/lib/jersey-core-1.1.1-ea-SNAPSHOT.jar
./WEB-INF/lib/atmosphere-core-0.2.jar
./WEB-INF/lib/asm-3.1.jar ./WEB-INF/lib/atmosphere-portable-runtime-0.2.jar
./WEB-INF/lib/jersey-server-1.1.1-ea-SNAPSHOT.jar ./WEB-INF/lib/jsr311-api-1.1.jar ./WEB-INF/web.xml ./javascripts ./javascripts/prototype.js ./javascripts/counter.js
./javascripts/behaviour.js ./META-INF ./META-INF/context.xml ./META-INF/atmosphere.xml ./stylesheets ./stylesheets/styles-site.css

The two important files are the RestLongPolling, which contains the server side code, and the counter.js, which contains the client side code. The client simply looks like:

   var counter = {
       'poll' : function() {
           new Ajax.Request('dispatch/counter', {
               method : 'GET',
               onSuccess : counter.update
          });
       },
       'increment' : function() {
           var count = $('count').innerHTML * 1;
          new Ajax.Request('dispatch/counter/' + count, {
              method : 'POST'
          });
      },
      'update' : function() {
         var count = $('count').innerHTML * 1;
          $('count').innerHTML = count + 1;
          counter.poll();
      }
  }
  
  var rules = {
      '#increment': function(element) {
          element.onclick = function() {
              counter.increment();
          };
      }
  };
  
  Behaviour.register(rules);
  Behaviour.addLoadEvent(counter.poll);

On the screen, it looks like:

click.png

As soon as you click, the counter send a request, appending the count value to the URL. Every browser connected will be updated as soon a click event happens. The idea here is to really show something simple. Now, on the server side, we only have (I'm not kidding!):

   package org.atmosphere.samples.rest.counter;
   
   import com.sun.jersey.spi.resource.Singleton;
   import java.util.concurrent.atomic.AtomicInteger;
   import javax.ws.rs.GET;
   import javax.ws.rs.POST;
   import javax.ws.rs.Path;
   import javax.ws.rs.PathParam;
   import org.atmosphere.core.annotation.ResumeOnBroadcast;
  import org.atmosphere.core.annotation.Suspend;
  
 @Path("{counter}")
@Singleton
 public class RestLongPolling{
     private final AtomicInteger counter = new AtomicInteger();
      
    @GET
    @Suspend
    public String suspend(){
          return "<!-- Atmosphere is your future-->";
      }   
      
      @POST
      @Path("{counter}")
      @ResumeOnBroadcast
      public String increment(@PathParam("counter") String count){
         counter.incrementAndGet();
         return counter.toString(); 
     }   
 }

So, when the Browser load the page, the counter.poll will invoke, on the server side the suspend() method. Since that method is annotated with the @Suspend annotation, the connection will be suspended after the method execution, waiting for event. You can see the Browser icon spinning in that demo as the GET has not yet returned. As soon as a click happens (line 8), the POST will invoke the increment(..) (line 26) method, and since the method is annotated with the @ResumeOnBroadcast, then the returned value will be broadcasted to all suspended connections, then written on the wire, and then the connection will be resumed. On the Browser side, the spinning will stop for a few milliseconds, as the Browser will re-issue a GET, which will again be suspended. Note that for that simple demo, I'm not updating the client side using the returned value of the increment(..) method to keep it extremely simple.

Now technically this application use the Atmosphere module called core, which build on top of the Comet Portable Runtime (CPR), hence allowing the application to be deployed anywhere. The CPR always auto detect the container is running on top of, like:

First with Glassfish v3:

10-Jun-2009 8:02:05 PM org.atmosphere.cpr.AtmosphereServlet loadAtmosphereDotXml
INFO: Sucessfully loaded org.atmosphere.handler.ReflectorServletProcessor@e1ca74 mapped to context-path /dispatch
10-Jun-2009 8:02:05 PM org.atmosphere.cpr.BroadcasterConfig 
INFO: DefaultBroadcaster configured using a Thread Pool of size: 2
10-Jun-2009 8:02:05 PM org.atmosphere.cpr.AtmosphereServlet autoDetectContainer
INFO: Atmosphere Framework running under container javax.servlet version 3.0
...and then Tomcat 6 INFO: DefaultBroadcaster configured using a Thread Pool of size: 2 10-Jun-2009 8:03:46 PM org.atmosphere.cpr.AtmosphereServlet autoDetectContainer INFO: Forcing the use of the container's native Comet API implementation instead of Servlet 3.0 10-Jun-2009 8:03:46 PM org.atmosphere.cpr.AtmosphereServlet autoDetectContainer INFO: Atmosphere Framework running under container Tomcat version 6.0.x

The two log above are showing that if the framework detect Servlet 3.0 Async Support implementation, it will use it. If not, then it will use the native Comet implementation.

Finally, two descriptors are needed to make the application works. The first one, the atmosphere.xml, just tells Atmosphere to use Jersey's Servlet to process request. Under the hood, all annotation processing and REST support comes for free from Jersey:

<atmosphere-handlers>
    <atmosphere-handler context-root="/dispatch" class-name="org.atmosphere.handler.ReflectorServletProcessor">
        <property name="servletClass" value="com.sun.jersey.spi.container.servlet.ServletContainer"/>
    </atmosphere-handler>
</atmosphere-handlers>

Next, the web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:j2ee="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    version="3.0"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee    http://java.sun.com/xml/ns/j2ee/web-app_3_0.xsd">
    <description>Atmosphere DeadSimple</description>
    <display-name>Atmosphere DeadSimple</display-name>
    <servlet>
        <description>AtmosphereServlet</description>
        <servlet-name>AtmosphereServlet</servlet-name>
        <servlet-class>org.atmosphere.cpr.AtmosphereServlet</servlet-class>
        <async-supported>true</async-supported>
        <b><init-param>
            <param-name>com.sun.jersey.config.property.packages</param-name>
            <param-value>org.atmosphere.samples.rest.counter</param-value>
        </init-param></b>
        <init-param>
            <param-name>com.sun.jersey.spi.container.ResourceFilters</param-name>
            <param-value>org.atmosphere.core.AtmosphereFilter</param-value>
        </init-param>
        <load-on-startup>0</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>AtmosphereServlet</servlet-name>
        <url-pattern>/dispatch/*</url-pattern>
    </servlet-mapping>
</web-app>

The only important line above is the init-param that tells Jersey were to lookup our resources, e.g. under org.atmosphere.samples.rest.counter. That's it. You can download the sample from here (and many more here).

For any questions, go to our main site and use our Nabble forum (no subscription needed) or follow us on Twitter and tweet your questions there!

From http://weblogs.java.net/blog/jfarcand

Published at DZone with permission of Jean-Francois Arcand, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags:

Comments

Deepak Srinivasan replied on Fri, 2009/07/17 - 3:54pm

Hi Jean-Francois,

do you have a netbeans project with the sample that I can download? when I look at the download for the source, i see the bits separate.

 

regards

/dlexer

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.