Caucho Technology
hessian home

hessian rich internet application demo


Hessian RIA Demo

Files in this tutorial

Flex/Flash source files

flash/src/AboutTab.mxmlAboutTab definition.
flash/src/ComposeTab.mxmlComposeTab definition.
flash/src/ReadTab.mxmlReadTab definition.
flash/src/Word.asWord definition.
flash/src/Words.mxmlWord Play Application.
flash/src/WordSprite.mxmlWordSprite definition.
flash/src/WordValue.asWordValue definition.

Auxillary files

web.xmlConfigures the environment
build.xmlBuild file
words.jnlpJava WebStart file.

Introduction

Hessian now supports Rich Internet Application (RIA) development on two different platforms: Adobe Flex (Flash) and JavaFX.

Adobe Flex is the most popular platform for developing RIAs with a wide installed base of both developers and end users. Adobe also recently announced that an open source version of Flex will available free to developers.

JavaFX offers web developers an exciting and easy new way to build user interfaces. RIAs are also possible with JavaFX, but any network communication must be fast and efficient.

Hessian is Caucho Technology's quick and compact web services and serialization protocol. For years, Hessian has been helping developers implement web services quickly and easily with great efficiency. Now it can do the same for RIAs with easy JavaFX integration and a new 100% ActionScript implementation for Flex supported by Caucho.

This tutorial shows an example RIA that uses Hessian to perform the network communication between client and server. As we will see, Hessian is not only fast, but incredibly easy to use.

The application we will be building in this tutorial is a poem writing game. Instead of allowing free formed poems, the user is presented with a set of words that are legal to use. The user can then drag words from that set to a drawing area and construct his or her poem. This is similar to poetry magnets.

Once the user has constructed a poem, the poem can be submitted to the server which keeps a log of the last 10 poems submitted. The user can then go to a reading area and download those latest poems. The poems are presented in a list and the user may select one to read.

This program can be fun to play with, but more importantly, it provides several examples of how to work with JavaFX and Flex. Java/JavaFX interactions, Client/Server network communication, JavaFX GUI design, WebStart deployment, and server configuration are all illustrated. In Flex we show how to use Hessian to connect with a Java server. In this example, we use the same Java server for both the JavaFX and Flash client applications.

Source code organization

First, we'll look at how the source is organized. This overview will give an idea of how to structure your JavaFX programs so that they can easily interact with Java code and Hessian. At the same time, we will describe and compare the Flex source code.

Notice that in this example, there are several source files, written in several different languages: Java, JavaFX, MXML, and ActionScript. JavaFX has the ability to use Java files, which we exploit to interact with Hessian. All of the Java and JavaFX files are in the package com.caucho.ria.examples.words. The ActionScript and MXML files are in the default package. These files do not directly interact with the Java or JavaFX files. First we will look at the data classes.

In Java, the Word class describes a word that can be either in the pool of words or in a poem. It has x and y positions and a WordValue. WordValue is a Java 1.5 enum with a fixed set of word values. Each WordValue has the actual text of the word as a String as well as the initial values for x and y. We use this enum because we want to ensure that only certain words can be used in poems; we don't want to allow offensive or nonsense words to be submitted to the server.

In ActionScript, there are equivalent Word and WordValue classes. The Word class mirrors the Java Word class to aid Hessian serialization. The WordValue class simulates an enumeration in ActionScript. At the time of writing, ActionScript does not natively support enumerations as language constructs, so we use this compatibility class to aid Hessian serialization.

The WordSet class simply holds a set of Words and provides methods for comparing such sets. It also provides a way to extract "used" words from a set. In the "Compose" tab of the application, there are two areas: a pool of unused words on the left hand side that may be used for composing poems and a work area on the right where words from the pool may be dragged to construct a poem. Only one WordSet holds all the words on this tab though, so the method WordSet.getUsedWords() creates a new list of Words that describes the words used in the poem. These words are sent to the server using Hessian.

There is no equivalent class for WordSet in ActionScript or MXML. In Flex, we treat WordSets as arrays.

Next are the files used for the Hessian communication. The most important for Java and JavaFX is the WordService interface. This interface contains methods that send a poem and retrieve the latest set of poems. This interface is used by the client and the server to describe the methods that can be invoked remotely. The server must provide a class which implements this interface. In this example, the WordServiceImpl class does this. On the client side, we use a reference to an instance of this interface which is created by using a HessianProxyFactory. The WordClient class holds this instance in our example. In general, a class likeWordClient is not necessary, but for JavaFX interactions, this class holds some convenience methods which organize the code in helpful way.

For Flex, there is no need for a WordService interface. Because ActionScript is a scripting language, we can dynamically create calls for the service at runtime without needing a interface. All of the Hessian calls will go to the same service used by the JavaFX client.

Now onto the user interface files. First we look at the JavaFX implementation. The main class used is the Words class which simply creates the window used by the application and populates it with the tabbed content. The tabs are implemented by the ComposeTab, ReadTab, and AboutTab classes. The ComposeTab and ReadTab classes interact with the Java code in two ways. They use a reference to a WordClient to send and receive poems to and from the server, respectively. They also use Words to construct WordNodes. The WordNode JavaFX class is simply a graphical wrapper around a Word.

The Flash user interface implementation is organized in a similar way. It is implemented in MXML with the files Words, ComposeTab, ReadTab, and AboutTab. Each of these function similarly to their JavaFX counterparts above. The main difference is that instead of using a WordClient reference, they use a reference to a HessianService which is implemented in the same framework as other Flex RPC services such as mx.rpc.soap.WebService or mx.rpc.http.HTTPService. The WordSprite class is similar to the JavaFX WordNode class in that it provides a graphical representation of the words.

Implementing the Hessian service

The code which actually deals with Hessian in this example actually quite small -- Hessian gets out of the way and lets you write your application instead of having to focus on network communication. The central part of any Hessian server application is the interface describing the methods. In our example, this is the WordService interface.

WordService.java
/**
 * The service interface used to create client proxy stubs for the WordService.
 **/
public interface WordService {
  /**
   * Submits a poem as a List of Words to the service.
   **/
  public void submit(List<Word> words);

  /**
   * Gets the most recent poems as WordSets from the service.
   **/
  public List<WordSet> getRecent();
}
      

This interface is very simple: there is a submit() method which sends a list of Words to the server and a getRecent() method which retrieves the most recent poems submitted.

The server side of this application is fairly simple as well. The implementation is given by the WordServiceImpl class.

WordServiceImpl.java
/**
 * An implementation of the WordService.  The WordService accepts poems from
 * clients and if they are well-formed, they are added to a list of 10 most
 * recent poems.  Once 10 poems have been collected, any new poem accepted
 * replaces one of the old poems.
 **/
public class WordServiceImpl extends HessianServlet implements WordService 
{
  /** The list of recent poems. */
  private List<WordSet> _recent = new ArrayList<WordSet>();

  /** The number of words defined in our enum. */
  private static final int NUMBER_OF_WORDS = WordValue.values().length;

  public void submit(List<Word> words)
  {
    // Check for illegal submissions
    if (words.size() == 0)
      return;

    if (words.size() > NUMBER_OF_WORDS)
      return;

    for (Word word : words) {
      if (word.getX() < 202 || word.getX() > 404)
        return;

      if (word.getY() < 0 || word.getY() > 199)
        return;

      word.setX(word.getX() - 202);
    }

    ServletRequest request = ServiceContext.getContextRequest();

    WordSet wordSet = new WordSet(words, request.getRemoteAddr());

    for (int i = 0; i < _recent.size(); i++) {
      if (_recent.get(i).equals(wordSet))
        return;
    }

    if (_recent.size() >= 10)
      _recent.remove(0);

    _recent.add(wordSet);
  }

  public List<WordSet> getRecent()
  {
    return _recent;
  }
}      
      

This class essentially implements a registry for poems. The submit() method adds words to a list and the getRecent() method returns that list. There is some additional processing in the submit() method: first, it checks that the input is valid. Poems must not have more words than exist in the WordValue enum and must have valid positions. Also if a poem already exists in the recent list, it cannot be added again. Next, the IP address of the submitter is added to the poem to give readers a rough idea of where the poems are coming from. (Or rather that they are not just a generated list sitting on the server -- these poems were actually submitted by other users.) Finally, the server keeps at most 10 poems at once. If there are 10 poems already in the recent list, the oldest is removed.

Communicating with the Hessian service from JavaFX

For JavaFX, the client side of the application simply creates an instance of WordService and invokes methods on it. We use a wrapper class called WordClient to make the methods easier to call from the JavaFX code, but usually this will not be necessary.

WordClient.java
/**
 * A small wrapper around the WordService client proxy.  This class
 * initializes the proxy and does some minor formatting.
 **/
public class WordClient {
  /** An instance of the client used by the JavaFX script. */
  public static WordClient CLIENT = 
    new WordClient("http://localhost:8080/words");

  /** The URL of the service. */
  private String _url;

  /** The client proxy for the WordService. */
  private WordService _service;

  /** A constructor which takes a URL string. */
  private WordClient(String url)
  {
    _url = url;
  }

  /** Sets the static client instance's server URL. */
  public static void setServerURL(String server)
  {
    CLIENT = new WordClient(server);
  }

  /**
   * Retrieves the WordService client proxy, creating it if necessary.
   **/
  private WordService getService()
  {
    if (_service == null) {
      HessianProxyFactory factory = new HessianProxyFactory();

      try {
        _service = (WordService) factory.create(WordService.class, _url);
      }
      catch (MalformedURLException e) {
      }
    }

    return _service;
  }

  /**
   * Wraps the submit call on the WordService by extracting the used words
   * from a WordSet.
   **/
  public void submit(WordSet words)
  {
    List<Word> used = words.getUsedWords();

    if (used.size() > 0)
      getService().submit(used);
  }

  /**
   * Gets the recent words using the WordService proxy.
   * 
   **/
  public List<WordSet> getRecent()
  {
    return getService().getRecent();
  }
}      
      

The most important method in this class is getService(). This method first checks if a proxy instance has already be created and if so, simply returns that instance. If not, it creates a HessianProxyFactory and uses it to create an instance. The HessianProxyFactory.create() method takes a Java interface and a URL for the service. The Java interface will always be WordService in this case, but the URL may change. In Words.fx, the static instance is initialized using a command line argument which specifies the URL of the server. More on this later, along with how to configure the server.

Within the JavaFX code, ComposeTab simply calls WordClient.submit() and ReadTab calls WordClient.getRecent() then formats the results for the screen. This is all that is necessary for the network communication!

Communicating with the Hessian service from Flex

The Flex implementation of Hessian uses the same style as other Flex RPC services like mx.rpc.soap.WebService. In the main Words MXML file, we have the following entry:

Configurating a HessianService in MXML
<hessian:HessianService xmlns:hessian="hessian.mxml.*" 
  id="service" destination="words"/>
      

The two attributes given on this tag are the id which specifies a variable that will refer to the service in any ActionScript code and the destination. The destination is a URL which points to the location of the Hessian service. In this example, the URL is unqualified, so it is relative to the URL from which the Flash file was loaded. For example, if we were to run this example from http://www.example.com/wordplay/Words.swf, the server would be at http://www.example.com/wordplay/words. Fully qualified URLs are also possible, though if the domain differs from that of the source of the Flash file, additional procedures may be necessary to configure the Flash sandbox security. Please refer to Flash documentation for more information on this issue.

The tabs are also configured in Words.mxml:

Configurating a HessianService in MXML
<mx:TabNavigator width="100%" height="100%">
  <word:ComposeTab service="{service}"/>
  <word:ReadTab service="{service}"/>
  <word:AboutTab/>
</mx:TabNavigator>
      

Note that the service is passed in as an attribute to the ComposeTab and the ReadTab. This ensures that we use the same connection to the service in all parts of the program.

In ComposeTab we have one method that uses the service, sendToServer():

Sending a poem to the service
public function sendToServer():void
{
  var usedSet:Array = new Array();

  for each (var wordSprite:WordSprite in _words) {
    if (wordSprite.used)
      usedSet.push(wordSprite.word);
  }

  if (usedSet.length > 0)
    _service.submit.send(usedSet);
}
      

In Flash, unlike JavaFX/Java, we don't use a WordSet class to organize the poem. We simply look for all the words that were used in the poem and add them to an array which we send directly. The Flex model of RPC is revealed here: the HessianService instance contains mx.rpc.AbstractOperations which correspond to methods available on the service. (In the Flex implementation of Hessian, these operations are populated dynamically.) The user calls a send() method to invoke the method. Note that in this case, we ignore the return value of the method since it is void.

In the ReadTab, there is an invocation of another service method, getRecent(). The actual method invocation is done in the refresh() method:

Requesting the recent poems
public function refresh():void
{
  var token:AsyncToken = _service.getRecent.send();
  token.addResponder(this);
}
      

Note that now we do care about the return value of the method. Flash is different from JavaFX in that it does not have any synchronous methods -- we must register callbacks for any RPC operations. mx.rpc.AbstractOperation.send() returns a mx.rpc.AsyncToken. With this token, we can register our callback. In this case, the ReadTab itself implements the mx.rpc.IResponder interface. Thus we can register this as the callback. The most important method of this interface for this example is result().

Formatting the returned poems
public function result(data:Object):void
{
  var event:ResultEvent = data as ResultEvent;
  var poems:Array = new Array();
  var index:int = 1;

  for each(var wordSet:Object in event.result) {
    var poem:Object = new Object();
    poem.label = "Poem " + index + " (" + wordSet._submitter + ")";
    poem.wordSprites = new Array();

    for each (var wordObject:Object in wordSet._words) {
      var wordSprite:WordSprite = new WordSprite();
      var value:String = 
        WordValue.enumValueToString(wordObject._value.name);

      var word:Word = new Word(value);
      word._x = wordObject._x;
      word._y = wordObject._y;

      wordSprite.word = word;
      wordSprite.readOnly = true;

      poem.wordSprites.push(wordSprite);
    }

    index++;
    poems.push(poem);
  }

  _poems.source = poems;
}
      

The poems are returned as an array of WordSets which we iterate over in the outer for each loop of the method. The inner for each loop of the method translates the words returned into WordSprites. The poems are put into an ArrayCollection which is used as the data source for the List in the ReadTab.

JavaFX GUI Design

Most of the JavaFX GUI design in this application is standard and straightforward. However, there are some aspects which illustrate interesting issues surrounding Java/JavaFX interaction and updating the GUI based on new data from the network.

First, we look at the WordNode JavaFX class. This class is simply the GUI counterpart to a Word. It appears as a label inside of a rectangle. It also contains a reference to a Word and updates that Word's position every time its position changes because the user drags the label and rectangle with the mouse. At the time of writing, JavaFX does not provide support for binding JavaFX variables to Java variables, so we use a workaround:

Workaround binding of JavaFX variables to Java variables
trigger on WordNode.x[oldValue] = newValue {
  word.setX(newValue);
}

trigger on WordNode.y[oldValue] = newValue {
  word.setY(newValue);
}
      

Each WordNode contains position fields x and y. Each time one of those fields is updated, these triggers modify the value in the Word accordingly. Notice that this is not a two-way binding, but this is not necessary in our case because the GUI is the only way that a Word's position can be changed in our application.

The next issue dealing with Java/JavaFX interaction is updating the the GUI based on new data from the network. In this application, retrieving the latest poems from the server must be done explicitly by the user by clicking the "Get Poems from Server" button. This initiates a call to WordClient.getRecent().

Refreshing the GUI based on network data
class Poem {
  attribute wordNodes:WordNode*;
  attribute i:Integer;
  attribute submitter:String;
}

// For some reason, we can't really bind to an array, we need a wrapper
// object... 
class PoemModel {
  attribute poems:Poem*;
}

...

operation ReadTab.refresh() {
  var recent = WordClient.CLIENT.getRecent();
  var wordIter = recent.iterator();
  delete model.poems;
  var i = 1;

  while (wordIter.hasNext()) {
    var wordSet = (WordSet) wordIter.next();
    var poem = Poem { wordNodes:[], i:i, submitter: wordSet.getSubmitter() };

    var iter = wordSet.getWords();
    while (iter.hasNext()) {
      var word = (Word) iter.next();

      insert WordNode {
        value: word.getText()
        x: word.getX()
        y: word.getY()
        word: word
        readOnly: true
      } as last into poem.wordNodes;
    }

    insert poem as last into model.poems;
    i++;
  }
}
      

With the data from the server, we create new Poem instances. These objects hold a set of WordNodes which graphically represent the poem itself, as well as the number of the poem in the list and the IP of the poem's submitter in a String. After we have created and inserted all the poems, two parts of the GUI are updated automatically because of JavaFX's binding facility: a ListBox changes to reflect the new poems and the poem area changes to show whichever poem is currently selected in the ListBox.

Updating the ReadTab GUI
attribute ReadTab.title = "Read";
attribute ReadTab.model = new PoemModel();

operation ReadTab.getListBox():ListBox {
  if (listBox == null) {
    listBox = ListBox {
      cells: bind foreach (poem in model.poems)
                    ListCell { 
                      text: "Poem {poem.i} ({poem.submitter})"
                      value: poem
                    }
    };
  }

  return listBox;
}

attribute ReadTab.content = Box {
  orientation: VERTICAL:Orientation
  content: [
    GridPanel {
      rows: 1
      columns: 2

      cells: [ 
        getListBox(),
        Canvas {
          scaleToFit: false
          content: Group {
            content:
            bind ((Poem) listBox.cells[listBox.selection].value).wordNodes
          }
        }
      ]
    },

    Button {
      alignmentX: 0.5

      text: "Get Poems from Server"
      action: operation() {
        refresh();
      }
    }
  ]
};
      

Notice that we don't create the ListBox directly in the ReadTab -- instead we use a method getListBox(). At the time of writing, there appears to be a bug (or undocumented feature) in the order of initialization in JavaFX objects such that the ReadTab.listBox field is not created before the ReadTab.content field, which makes the binding ineffective. This method is a workaround to ensure that when ReadTab.content is created, ReadTab.listBox is initialized.

In the ListBox itself, the list items are simply bound to the Poems, so that when they are updated from the network, they automatically update in the GUI. Similarly in the Canvas, the content is bound to whichever Poem is selected in the ListBox.

Using Java WebStart

At the moment, JavaFX applications are started on the web using Java's WebStart technology. WebStart uses .jnlp files to specify which classes are used and how they should be launched by the client. The .jnlp file used by our application is special however. Recall that we said the Words.fx file sets the URL of the server by a command line argument. The command line arguments can be specified in a .jnlp file. So we actually preprocess our .jnlp file to include the originating server using JSP syntax. This step is necessary because JavaFX has no way of knowing which server it came from otherwise.

WebStart file before preprocessing
<%@ page contentType="application/x-java-jnlp-file" %><%
String uri = request.getScheme() + "://" + 
             request.getServerName() + ":" +
             request.getServerPort() + 
             request.getContextPath();
%><?xml version="1.0" encoding="utf-8"?>
<jnlp spec="1.5+" codebase="<%= uri %>" href="words.jnlp">
  <information>
    <title>Caucho Hessian JavaFX Demo</title>
    <vendor>Caucho Technology, Inc.</vendor>
  </information>

  <security> 
    <all-permissions/>
  </security> 
  
  <resources>
    <j2se version="1.5+" href="http://java.sun.com/products/autodl/j2se" 
          java-vm-args="-Xss1M -Xmx256M"/>
    <jar href="javafxrt.jar" main="true"/>
    <jar href="swing-layout.jar"/>
    <jar href="hessian-3.1.1.jar"/>
    <jar href="words.jar"/>
  </resources>

  <application-desc main-class="net.java.javafx.FXShell">
    <argument>com.caucho.ria.examples.words.Words</argument>
    <argument>--</argument>
    <argument><%= uri + "/words" %></argument>
  </application-desc>
</jnlp>      

Notice that the first 5 lines of the file are JSP directives. First we set the Content-Type of the page to application/x-java-jnlp-file, then we construct the URI for the server. Skipping to the end of the file, we see how the application is launched. The main class used for JavaFX programs is net.java.javafx.FXShell, which takes as its first argument the name of the JavaFX file to run. Then the next argument must be "--". This specifies that all of the following arguments are to be given to the application. Finally, we give the URI constructed at the beginning of the file as the last argument to be passed to our Words.fx file.

The rest of the file is fairly standard for .jnlp files. We must include the JavaFX .jar files javafxrt.jar and swing-layout.jar, the Hessian .jar file hessian-3.1.1.jar, and the .jar file containing our application code words.jar. Because applications started with Java WebStart are run in a sandbox, we have to sign the .jar files used in the application, including the JavaFX libraries. More information on .jnlp files and signing .jar files is available in the JavaFX Wiki here.

Server Configuration

Finally, we look at how to configure the server to provide the service to which the clients will connect.

web.xml
<web-app id="">

  <!-- Configure the WordService implementation -->
  <servlet servlet-name="words"
           servlet-class="com.caucho.ria.examples.words.WordServiceImpl"/>
  <servlet-mapping url-pattern="/words" servlet-name="words"/>

  <!-- Configure the JSP servlet to preprocess the .jnlp file -->
  <servlet servlet-name="jnlp-jsp" servlet-class="com.caucho.jsp.JspServlet"/>
  <servlet-mapping url-pattern="/words.jnlp" servlet-name="jnlp-jsp"/>

</web-app>
      

We create two servlets. The first is the WordServiceImpl class which actually implements our WordService. The second is a JSP servlet that preprocesses our .jnlp file. In this configuration, we use the Caucho JSP Servlet, but any JSP implementation can be used in any JavaEE container or Servlet engine.

Interchange between the two clients

Now that you know how the server and the two clients word, you can run both clients to read and write poems in our live demo. As a test, try writing a poem with one client, then reading it in the other. Since both clients use the same protocol, the only difference between the two is the underlying platform.


Copyright © 1998-2008 Caucho Technology, Inc. All rights reserved.
Resin ® is a registered trademark, and Quercustm, Ambertm, and Hessiantm are trademarks of Caucho Technology.