![]() | |||||||||||||||||||||||||||||||||
hessian rich internet application demo
JavaFX source files
Java source filesFlex/Flash source files
![]() 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. 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
In Java, the
In ActionScript, there are equivalent
The
There is no equivalent class for
Next are the files used for the Hessian communication. The most
important for Java and JavaFX is the
For Flex, there is no need for a
Now onto the user interface files. First we look at the JavaFX
implementation. The main class used is the
The Flash user interface implementation is organized in
a similar way. It is implemented in MXML with the files
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.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
The server side of this application is fairly simple as well. The
implementation is given by the 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
For JavaFX, the client side of the application simply creates an
instance of 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
Within the JavaFX code,
The Flex implementation of Hessian uses the same style as other
Flex RPC services like Configurating a HessianService in MXML
<hessian:HessianService xmlns:hessian="hessian.mxml.*"
id="service" destination="words"/>
The two attributes given on this tag are the
The tabs are also configured in 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
In 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
In the 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. 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 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 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
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 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 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
In the
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 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
The rest of the file is fairly standard for .jnlp files. We must
include the JavaFX .jar files 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 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.
| |||||||||||||||||||||||||||||||||