RESTful Web Services and Comet

   
By Carol McDonald and Rick Palkovic, August 2008  

The term Ajax was invented to describe the technologies that provide background request-response between a web server and client browser. With Ajax, web applications can retrieve data from the server in the background, essentially preloading information that is revealed in response to a user gesture and giving the impression of a more responsive web page. Although the data appears to be downloaded asynchronously, the server responds to client requests — typically through the use of the XMLHttpRequest object.

Web protocols require that servers respond to browser requests. Ajax falls short for multi-user, interactive pages because one user won't see changes other users make until the browser sends a request. In order for a server to push data to a browser asynchronously, some design workarounds are required. These design strategies are broadly described by the term Reverse Ajax.

One strategy is to use browser polling, in which the browser makes a request of the server every few seconds. Another strategy, known as piggybacking, delays a server's page update until the next request is received from the browser, at which time the server sends the pending update along with the response.

A third strategy, and the one used in the example application presented in this article, is to design with long-lived HTTP connections between the browser and server. With this strategy, a browser does not poll the server for updates; instead, the server has an open line of communication it can use to send data to the browser asynchronously. Such connections reduce the latency with which messages are passed to the server. This technique is known as Comet — a play on words that can be appreciated by users of household cleansers.

A RESTful web service conforms to Representational State transfer (REST) architectural style. The style is characterized by a simple interface that transmits domain-specific data over HTTP without an additional messaging layer such as SOAP.

Contents
 
About the Example Application
More About the Technologies
Running the Example Application
How the Application Works
Conclusion
References
 
About the Example Application

The example application discussed in this article presents a photo slideshow that can be controlled by multiple users. Actions of one user affect the pages seen by other users. The application provides a chat feature that enables users to comment on photos and let other users see the comments.

The example application incorporates the following technologies. All are available for download.

  • Comet – Techniques that enable a server to push data to client browsers through an HTTP open line of communication.
  • Dojo – An open-source DHTML toolkit written in JavaScript. The Dojo Toolkit includes many utilities that go beyond Ajax. For example, the dojox.comet module simplifies programming Comet applications.
  • Bayeux – A protocol for routing JSON-encoded events between clients and servers in a publish-subscribe model.
  • JAX-RS – Java API for XML Web Services provides a standardized API for building RESTful web services in Java. Central to the RESTful architecture is the concept of resources identified by universal resource identifiers (URIs). The API provides a set of annotations which you can add to Plain Old Java Objects (POJOs) to expose web resources identified by URIs.
  • JPA – Java Persistence API, which provides a single, standard persistence API for Java applications.
  • GlassFish – Open-source Java EE-compliant application server.
  • Grizzly – Open-source framework designed to help developers take advantage of the Java New I/O API (Java NIO API). Grizzly comes bundled with GlassFish application server, but it can be used separately.
  • MySQL – The most popular enterprise-grade open-source database server.
More About the Technologies

Comet is a term coined by Alex Russell to describe applications where the server pushes data to the client over a long-lived HTTP connection. Comet uses the persistent connection feature in HTTP/1.1. This feature keeps alive the TCP connection between the server and the browser until an explicit 'close connection' message is sent by one or the other, or until a timeout or network error occurs. The technique enables the server to send a message to the client when an event occurs without waiting for the client to send a request.

Grizzly is an HTTP framework that uses the Java NIO API to provide fast HTTP processing. Grizzly provides long-lived streaming HTTP connections that can be used by Comet, with support built on top of Grizzly's Asynchronous Request Processing (ARP). With Grizzly ARP, each Comet request doesn't monopolize a thread, and the availability of threads permits scalability.

Bayeux is a protocol for routing JSON-encoded events between clients and servers in a publish-subscribe model. Grizzly provides an implementation of Bayeux, which makes it easy to build Comet applications with dojox.comet. To build your application, configure GlassFish for Comet and configure your web application's web.xml file for the Grizzly Bayeux servlet. You can then use the dojox cometd publish and subscribe methods to send and receive Comet events. These techniques are described in more detail in the description of the application.

Figure 1: Grizzly Architecture
 
Running the Example Application

To run the example application, you need to download and install the code and necessary software tools. The sample code is available as a NetBeans project. You can build and run the sample code using the NetBeans IDE. This example uses NetBeans IDE 6.5 Beta — at the time you read this article, the final release version may be available. The example has also been shown to work with NetBeans IDE 6.1.

  1. Download and install NetBeans IDE 6.5 Beta, GlassFish v2ur2, and MySQL. Another option is to download a bundle that includes Sun Java System Application Server 9.1 Update 1 with MySQL Community Server.
     
  2. To use Comet with GlassFish, add the bold red line to the GlassFish domain.xml file in the glassfish-v2ur2\domains\domain1\config directory:
     
    Code Sample from:  domain.xml
    <http-listener acceptor-threads="1" address="0.0.0.0" 
      blocking-enabled="false" default-virtual-server="server" 
      enabled="true" family="inet" id="http-listener-1" port="8080" 
      security-enabled="false" server-name="" xpowered-by="true">
       
                             <property name="cometSupport" value="true"/>
    </http-listener>
                          
     
  3. Download the compressed example code zip archive and extract its contents. The extracted directory is named slideshow, and can be found in the directory where you extracted the example files. For example, if you extracted the contents of the zip file to the root directory C:\ on a Windows machine, then your newly created directory would be C:\slideshow.
     
  4. Start the NetBeans IDE.
     
  5. In the NetBeans IDE, start the MySQL database as follows:
    1. Select the Services tab.
    2. Expand the databases node. You will see the MySQL server database in the list of databases.
       
       
    3. Right-click the MySQL server database and choose Start from the menu.
  6. Create the slideshow database as follows:
    1. Right-click the MySQL server database and select Create Database.
    2. Enter the database name cometslideshow. A New Database Connection window opens. Click OK to accept the default settings.
  7. Create the tables in the MySQL cometslideshow database as follows:
    1. Expand the Drivers node. You see a driver for the cometslideshow database in the list of drivers.
       
       
    2. Right-click the cometslideshow driver and choose Connect from the menu.
    3. Right-click the cometslideshow driver and choose Execute Command from the menu. A SQL command window opens.
    4. Copy the contents of the slides.sql file in the slideshow directory and paste the contents into the SQL command window.
    5. Click the Run SQL icon (Ctrl+Shift+E) above the SQL command window.
  8. Open the slideshow/setup/sun-resources.xml file and verify that the property values it specifies match those of the cometslideshow database you created. Edit the property values as necessary.
     
  9. Open the slideshow project by choosing File > Open Project (Ctrl+Shift+O) and navigating to the slideshow project folder.
     
  10. Run the project as follows:
    1. Right-click the slideshow node in the Projects window.
    2. Choose Run Project from the menu.

    When you run the project, your browser displays the opening page of the Sample Application (at http://localhost:8080/slideshow/). Open another browser window and set its URL to http://localhost:8080/slideshow/, then enter a name and click the Join button in both browser windows. Now, when you click the Next Slide button in one browser window, you'll see that both browsers are updated with the next slide.
 
How the Application Works

In the following discussion, code in the examples is sometimes highlighted in color to facilitate the explanation. The discussion explains the techniques used to incorporate the technologies described earlier during application development. Headings for each code sample identify the file from which it was taken.

Using Comet With GlassFish

To use Comet and the Grizzly Comet Framework with GlassFish, simply add the line shown in red to the GlassFish configuration domain.xml file.

Code Sample from:  domain.xml
<http-listener acceptor-threads="1" address="0.0.0.0" 
  blocking-enabled="false" default-virtual-server="server" 
  enabled="true" family="inet" id="http-listener-1" port="8080" 
  security-enabled="false" server-name="" xpowered-by="true">
   
                     <property name="cometSupport" value="true"/>
</http-listener>
                  
 
Enabling Bayeux in GlassFish

To enable Bayeux on GlassFish, add the line shown in bold text to your Web application's web.xml file:

Code Sample from:  web.xml
<servlet>
   <servlet-name>Grizzly Cometd Servlet</servlet-name>
   <servlet-class>
         com.sun.grizzly.cometd.servlet.CometdServlet
   </servlet-class>
   <init-param>
      <description>
         expirationDelay is the long delay before a request is
         resumed. -1 means never.
      </description>
      <param-name>expirationDelay</param-name>
      <param-value>-1</param-value>
   </init-param>
   <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
   <servlet-name>Grizzly Cometd Servlet</servlet-name>
    
                     <url-pattern>
                     /cometd/*
                     </url-pattern>
</servlet-mapping>
                  
 

Package your war file and deploy it on GlassFish. Then, every request sent to your war file's context-path /cometd/ will be serviced by the Grizzly Bayeux runtime.

Using dojox.cometd

The Comet chat feature used in the example application was adapted from an example originally written by Greg Wilkins. As adapted, the feature enables a slideshow presentation to be shared among subscribed clients. Figure 2 shows the Comet Slideshow page, which allows the users to share a slideshow and chat at the same time.

Figure 2: Different Browsers Sharing Photos in Comet Slideshow
Click the image to enlarge it.
 
Installing and Using Dojo With NetBeans IDE

There are three ways to install Dojo, which you can read about in the book of Dojo. A quick and easy way to use Dojo with the NetBeans IDE is to download the JavaScript libraries from dojotoolkit.org. You would then perform the following steps. These steps have already been done for the example project so you do not have to download Dojo in order to run the example.

  1. Create a new NetBeans Web Applications project.
  2. Extract the Dojo Toolkit into the project web directory: .../web.
  3. Rename dojo-release-1.1.1/ to src/, which will give you the NetBeans project structure shown in the following figure.
     
     
Loading Base Dojo and Required Modules Into the Application

To load Dojo into your application, put the relative path to the dojo.js file in a script element in the head section of your HTML page, as shown here:

Code Sample from:  index.html
<script type="text/javascript" src="src/dojo/dojo.js"></script>
<script type="text/javascript" src="chat.js"></script>
 

This script element loads the base Dojo script that gives you access to all the Dojo functionality. The rest of the JavaScript code for this application is in the file chat.js. Note that NetBeans IDE 6.5 Beta has a very capable JavaScript editor, shown below editing the chat.js file:

 

In chat.js, the application uses the dojo.require function (similar to the import directive in Java) to specify which Dojo modules to load.

Code Sample from:  chat.js
dojo.require("dojox.cometd");
 

cometd
 
Initializing a Connection Between the Dojo Client and the Grizzly Bayeux Servlet

Upon loading the Slideshow application, a user can enter a username and join a slideshow session, as shown in Figure 3.

Figure 3: User Login Page
 

When a user clicks the Join button, the join JavaScript function is called. In the join function, the call to dojox.cometd.init initializes a connection to the given Comet server — in this case with the GlassFish Grizzly Bayeux servlet. Note that /cometd/* is the URL-pattern for the Grizzly Cometd servlet configured in the web.xml file for the application.

Code Sample from:  chat.js
var room = {
 ...
 join: function(name){

   dojox.cometd.init("
                    
/cometd");
   dojox.cometd.subscribe("/chat/demo", room, chatCallback");
   dojox.cometd.publish("/chat/demo",
      { user: room._username,
        join: true, chat : room._username+" has joined"});
 }
                  
 

The dojox.cometd.subscribe line subscribes the chatCallback callback function to the /chat/demo channel. Whenever a message is sent to the /chat/demo channel, the chatCallback function is called. The dojox.cometd.publish line publishes a message that the user whose name was entered with the Join button has joined the /chat/demo channel. All subscribers to the /chat/demo channel receive the message.

Publishing the Next Slide for the Comet Slideshow

When the user clicks the Next Slide button, a JavaScript function is called that publishes the URL for the next slide, as shown in Figure 4.

Figure 4: Viewing the Next Slide
 
Code Sample from:  index.html
<input id="
                     previousB" class="button" type="submit" name="previous" value="Previous Slide"/> 
<input id="
                     nextB" class="button" type="submit" name="next" value="Next Slide"/>
                  
 

The JavaScript function that is called when the user clicks the Next Slide button is shown in the following code sample. This function calls room.next, which passes the URL for the next slide. The function then increments the index for the next slide. The URLs for the slides are stored in the slideUrls array, which is loaded at initialization in the room.loadSlides() function. The function also calls the slides JAX-RS REST Web service, which is discussed later.

Code Sample from:  chat.js
var room = {

   slideUrls: null,
...

   init: function(){

    var i=0;
    room.loadSlides();

    element=
                     dojo.byId('
                     nextB');
    element.
                     onclick = function(){
        
                     room.next( slideUrls[i]);
       if (i>=slideUrls.length){i=0;}
       else {i++;}
    }

    element=
                     dojo.byId('
                     previousB');
    element.
                     onclick = function(){
        
                     room.next( slideUrls[i]);
       if (i<=0){i=0;}
       else {i--;}
    }
  }
  ...
                  
 

The function room.next, shown in the following code sample, calls dojox.cometd.publish to publish the next slide URL (which it receives as an input argument) to the /chat/demo channel. All subscribers to the /chat/demo channel receive this message.

Code Sample from:  chat.js
var room = {
    ...

    next: function(text){
        dojox.cometd.publish("/chat/demo", {slide: text});
    }
    ...
}
 

When a message is published to a Bayeux channel on the server, it is delivered to all clients subscribed to that channel — in this case, to the /chat/demo channel.

In the room.join function described earlier, dojox.cometd.subscribe("/chat/demo", room, "chatCallback") was called to subscribe the chatCallback callback function to the /chat/demo channel. The chatCallback function, shown in the following code sample, is called with the published message as an input argument. The chatCallback function updates the browser page by setting the slide dom element innerHTML to an HTML img tag with the slide URL from the published message "<img src='" + slideUrl + "'/>". This message updates the browser page with the image that corresponds to the slide URL that was published.

Code Sample from:  chat.js
var room = {
    ...
    chatCallback: function(message){
        var slide=dojo.byId('slide');
        var slideUrl=message.data.slide;
        slide.innerHTML ="<img src='" + slideUrl + "'/>";
    ...
}
 
Loading the Slide URLs From the Slides RESTful Web Service

The dojo.addOnLoad function enables you to call a function after a page has loaded and after Dojo has finished its initialization. This application uses dojo.addOnLoad to call the init function. The init function calls the loadSlides function, which in turn calls the slides JAX-RS web service and saves the results in the slideUrls object, as shown in the following code sample.

Code Sample from:  chat.js
 var room = {

    slideUrls: null,
    ...

    // load slide URLs from slides web service response
    handleResponse: function (responseObject, ioArgs){
        room.slideUrls= responseObject.slides.slide;

    },

    // make request to the slides web service
    loadSlides: function (){
        var targetURL = "resources/slides/";
        dojo.xhrGet({
            url: targetURL,
            handleAs: "json",
            load: room.handleResponse,
            error: room.handleError
        });
    },

  init: function(){
    room.loadSlides();
    ...
  }
  ...
};

dojo.addOnLoad(room, "init"); 
 

The loadTable function calls dojo.xhrGet to make an XMLHttpRequest request to the slides JAX-RS web service that is specified by the url parameter. When the response from web service is returned, the callback function handleResponse specified by the load parameter is called, and the response is passed to the callback function in the responseObject.

The handleAs parameter specifies the response data type. Specifically, handleAs: "json" identifies the type of the returned data as JSON (JavaScript object notation). In the handleResponse callback function, the slides data ( responseObject.slides.slide) that is returned from the slides JAX-RS web service is saved in the slideUrls object. The following example shows a JSON response from the slides JAX-RS web service.

Example JSON Data
{"slides":
  {"@uri":"http://host/slideshow/resources/slides/",
   "slide":[
     {"@uri":"http://host/slideshow/resources/slides/1/",
       "id":"1",
       "name":"Lions",
       "description":"Lions in South Africa",   
       "imageurl":"http://localhost:8080/slideshow/images/image1.jpg"},
     {"@uri":"http://host/slideshow/resources/slides/2/",
       "id":"2",
       "name":"Elephant",
       "description":"Elephant in South Africa",   
       "imageurl":"http://localhost:8080/slideshow/images/image2.jpg"}
    ]
  }
}

 
RESTful Web Services With JAX-RS

The dojo.xhrGet url parameter references the URI resources/slides/ for the slides RESTful web service. The slides RESTful web service was generated using NetBeans, as explained in the tutorial Generating RESTful Web Services from Entity Classes. Beginning with NetBeans 6.1, you can generate JPA entity classes from database tables. You can then generate RESTful web services from these entity classes and test the web services in a browser interface. The slides RESTful web service was generated from the slide database table.

The code shown in the following sample is a snippet from the SlidesResource.java class that was generated by the NetBeans "generate RESTful web services from entity classes" feature.

Code Sample from:  SlidesResource.java
// Service URI path "/slides/"
                      @Path("/                        slides/")                     
public class  
                     SlidesResource {

   
                                             @GET                     
   
                     @ProduceMime("application/json")
  public SlidesConverter  
                     get(
                     @QueryParam("start")
             
                     @DefaultValue("0") int start, @QueryParam("max")
            @DefaultValue("4") int max, @QueryParam("expandLevel")
            @DefaultValue("1") int expandLevel,
            @QueryParam("query")
            @DefaultValue("SELECT e FROM Slide e") String query{


        try {
            SlidesConverter slides = new SlidesConverter(
                 
                     getEntities(start, max, query),
                context.getAbsolutePath(), expandLevel);
            return slides;
        } finally {
            PersistenceService.getInstance().close();
        }
    }
                  
 

The SlidesResource class represents a list of slides. The SlidesResource get method returns a list of Slide objects in JSON format.

  • To access a resource in REST, you specify its URI. The JAX-RS annotation @Path identifies the URI path for the resource. For the resource SlidesResource , the URI path is / slides /.
  • The annotation @GET specifies the get method that supports the HTTP GET method.
  • The annotation @ProduceMime specifies the MIME types that a method can produce. Here, the annotation specifies a get method that returns a JSONArray object. The SlidesConverter class is a JAXB annotated class that is used to marshal a list of Slide objects into XML or JSON format. The getEntities method returns a list of Slide entity objects and is explained below.
  • The annotation @QueryParam specifies input parameters for methods. When the method is invoked, the input value is injected into the corresponding input argument.
  • The annotation @DefaultValue specifies a default value for an argument if no input value is given.

The following is an example of an HTTP request for this Web Service.

Request: GET http://host/slideshow/resources/slides/?start=0 
 

The following code is an example of an HTTP response for this web service.

Received:
{"slides":
  {"@uri":"http://host/slideshow/resources/slides/",
   "slide":[
     {"@uri":"http://host/slideshow/resources/slides/1/",
       "id":"1",
       "name":"Lions",
       "description":"Lions in South Africa",   
       "imageurl":"http://localhost:8080/slideshow/images/image1.jpg"},
     {"@uri":"http://host/slideshow/resources/slides/2/",
       "id":"2",
       "name":"Elephant",
       "description":"Elephant in South Africa",   
       "imageurl":"http://localhost:8080/slideshow/images/image2.jpg"}
    ]
  }
}
 

The SlidesConverter class is a JAXB annotated class, used to marshal a list of Slide objects into XML or JSON format. A snippet of the SlidesConverter class is shown in the following code sample, with annotations shown in blue .

Code Sample from:  SlidesConverter.java
                      @XmlRootElement
public class SlidesConverter {
    ... 
     
                     @XmlElement
    public Collection«SlideConverter» getSlide(){
    ...
        
                     return items;    }
    
                     @XmlAttribute
   public URI getUri() {
        
                     return uri;    }

                  
 
Java Persistence Query API

The SlidesResource getEntities method uses the Java Persistence API Query object to return a list of slides.

Code Sample from:  SlidesResource .java
                      @Path("/slides/")
public class  
                     SlidesResource {

    . . .

    protected Collection<Slide>  
                     getEntities(int start, int max,
        String query) {

        PersistenceService ps = PersistenceService.getInstance();
        Query query = ps.createQuery(query);
        query.setFirstResult(start);
        query.setMaxResults(max);
        return query.getResultList();
    }
                  
 

The Java Persistence Query APIs are used to create and execute queries that can return a list of results. The JPA Query interface provides support for pagination via the setFirstResult() and setMaxResults() methods: query.setMaxResults(int maxResult) sets the maximum number of results to retrieve, and query.setFirstResult(int startPosition) sets the position of the first result to retrieve.

The following code shows the Slide entity class, which maps to the CUSTOMER table that stores the slide instances. This is a typical Java Persistence entity object. There are two requirements for an entity:

  • The class must be annotated with an @Entity annotation.
  • The primary key identifier must be annotated with the @Id annotation.

Because the fields id, name, description, and so on, are basic mappings from the object fields that identify columns of the same name in the database table, they don't have to be annotated.

For more information on NetBeans and JPA, see the tutorial Java Persistence in the Java EE 5 Platform.

Code Sample from:  Slide .java
                      @Entity

public class  
                                             Slide                      implements Serializable {

     
                     @Id
    private Long id;
    private String name;
    private String description;  
    private String imageurl; 

    public  
                                             Slide                     () { }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    . . .
}   

                  
 
Conclusion

The example application demonstrates the use of Dojo, Comet, Bayeux, and a RESTful Web Service, coded using JAX-RS: Java API for RESTful Web Services (JSR-311), JPA, running on the GlassFish application server with Grizzly and MySQL.

References
Rate and Review
Tell us what you think of the content of this page.
Excellent   Good   Fair   Poor  
Comments:
Your email address (no reply is possible without an address):
Sun Privacy Policy

Note: We are not able to respond to all submitted comments.
Left Curve
Popular Downloads
Right Curve
Untitled Document
Left Curve
More Systems Downloads
Right Curve