Jive Community Forums

 

  JavadesktopFAQ JavadesktopWIKI JavadesktopBLOG JavadesktopPROJECTS JavadesktopHOME javadotnet javadesktopFORUMS javadesktopARTICLES

Server-Side Swing for Rich Internet Applications

by Bernhard Wagner

March 15, 2005


This article submitted by Canoo presents a product that shows how the Java platform’s capabilities on the desktop can be leveraged for server-side J2EE applications. Canoo’s UltraLightClient offers a server-side API to Swing, enabling development of rich GUIs executing in a thin client presentation engine.

Java Ubiquity Bonus

A characteristic of the Java platform is its availability on virtually every type of machine. This yields the obvious advantage that programs are portable. A less evident but more interesting benefit is that the Java platform’s ubiquity helps in making client/server computing more efficient and cost effective.

If programs need to be split up and distributed across multiple machines, a uniform language and execution environment is a tremendous bonus. Developers can work using the same tools on all platforms. Moreover, they can streamline their design by making it more homogeneous and more flexible with respect to distribution.

An example where the latter advantage can be leveraged is the rich user interface of a client/server application. Given the Java platform on both sides, the half-object design pattern can be employed to realize applications with rich GUIs in a thin-client architecture.

The half-object pattern can be applied to user interface widgets, providing a server-side API for them. Canoo’s UltraLightClient does this for Swing. By means of this API, UltraLightClient offers a pure Java approach for developing Rich Internet Applications (RIA).

In the following, we will investigate this approach by having a closer look at UltraLightClient (ULC). We will discuss the benefits and the limitations of both the product and the approach in general.

Half-Object Pattern for Swing

The half-object design pattern proposes a mechanism that abstracts away from object distribution across address spaces (Gerard Meszaros: Pattern: Half-Object + Protocol; in Pattern Languages of Program Design; James O. Coplien and Douglas C. Schmidt, eds. Addison-Wesley, © 1995, ISBN 0-201-60734-4). The pattern splits an object into two interdependent half-objects, one in each address space.

Each half-object provides the functionality and data that is most frequently used by the objects of the address space it is residing in. The two half-objects synchronize their state across the address boundary by means of a communication protocol that is optimized specifically for the needs of the individual type of object. The protocol may be synchronous, asynchronous, or even both.

Half-Object Pattern

If we apply this pattern to the classes of Swing, we can create server-side peer classes for the client-side Swing widgets, and transfer the API to these peers on the server. The result is that the developer can program as if the widgets were located on the server.

ULC’s Implementation

A straightforward implementation of the half-object pattern would subclass each Swing component twice, creating one subclass for the client side and one for the server side. ULC’s implementation is slightly different, because the server-side objects are deployed in a J2EE container, either as a servlet or an EJB. Running Swing in a J2EE container is neither desirable nor feasible. ULC’s solution is to create a proxy for the client-side Swing components. This proxy is then split into two half-objects. The client-side half-object interacts with the Swing component, and the server-side half-object provides the API for the developer.

ULC’s Half-Object Pattern for Swing

The two half-objects communicate by means of an optimized protocol that minimizes both the amount of data and the number of messages transferred. The client side is stateless, though it caches state for performance reasons. This enables dynamic restarting of the client at any time. The client simply gets the current state from the server to reconstruct the user interface. This capability is useful, for instance, in a scenario where an applet is running as a portlet (http://www.jcp.org/en/jsr/detail?id=168) that needs to restart when the portlet context is switched.

The ULC protocol is both synchronous and asynchronous. Data requests are handled asynchronously, while events are synchronous by default. A developer can configure certain events to be delivered asynchronously, but this feature requires caution because it may lead to inconsistent half-objects.

Server-side API

Let’s see to what extent the half-object pattern can hide the breach between address spaces. “Hello World” is a good example to start with. Here is a Swing-based version:

public class Hello {
   public static void main() {
       JFrame frame = new JFrame("Hello Sample");
       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
       frame.getContentPane().add(new JLabel("Hello world!"));
       frame.setVisible(true);
   }
}

To change this into a ULC application, we essentially replace the classes JFrame and JLabel by ULCFrame and ULCLabel, respectively (see ULC Developer Guide, section 5.1).

public class Hello extends AbstractApplication {
     public static void main(String[] args) {
         DevelopmentRunner.setApplicationClass(Hello.class);
         DevelopmentRunner.main(args);
     }
     public void start() {
         ULCFrame frame = new ULCFrame("Hello Sample");
         frame.setDefaultCloseOperation(ULCFrame.TERMINATE_ON_CLOSE);
         frame.add(new ULCLabel("Hello world!"));
         frame.setVisible(true);
     }
}

The visual representation and behavior of the two applications are the same.

Hello World Application

This means that for ULCFrame and ULCLabel, the API is identical to the Swing peer classes.

The same is true for more complex components like ULCTree and JTree, a fact that is illustrated by one of the sample applications that comes with the ULC distribution. This application is an online shop for the products of a hardware retailer. Its products are structured into categories as shown below.

Online Shopping Application

Selecting a product category in the tree displays all products belonging to this category in the product table. Vice-versa, when a set of products in the table is selected, the corresponding categories are highlighted in the tree.

The following snippet from the source code shows how the latter is achieved, i.e. how the category nodes of the tree are highlighted, given a set of selected items in the product table.

public class ProductBrowser {
    private ULCTree fCategoriesTree;

    public void selectPathsInTree(List products) {
        Set pathsToSelect = new HashSet();
        for (Iterator iterator = products.iterator(); iterator.hasNext();) {
            Product product = (Product)iterator.next();
            AbstractCatalogNode parentNode = (AbstractCatalogNode)product.getParent();
            pathsToSelect.add(new TreePath(parentNode.getPath()));
        }
        TreePath[] treePathsToSelect = (TreePath[])pathsToSelect.toArray(new TreePath[0]);
        fCategoriesTree.setSelectionPaths(treePathsToSelect);
        fCategoriesTree.scrollPathToVisible(treePathsToSelect[0]);
    }
}

Again, the Swing version is identical, except for the class names ULCTree and JTree, respectively.

Minor differences between using ULC and Swing emerge when the split between client and server imposes restrictions on the flow of control. An example for such a restriction is the request/response behavior enforced by J2EE, which necessitates that modal dialogs be realized differently. For instance, in Swing, a JOptionPane could be used to set up a modal dialog. As soon as the dialog is displayed, the main execution thread blocks until the dialog is closed by pushing a button. When the dialog is closed, the value of the button can be retrieved to determine what further actions to take.

int value = JOptionPane.showConfirmDialog(null, message, "Order confirmation", JOptionPane.OK_CANCEL_OPTION);
if (value == JOptionPane.OK_OPTION) {
    fShoppingCartTableModel.emptyShoppingCartTableModel();
    fOrderButton.setEnabled(false);
    sendOrder();
}

In a ULC application, the main execution thread will not wait like in Swing because the thread runs on the server. As a consequence, you have to set up a window listener that reacts to the dialog closing action.

ULCAlert alert = new ULCAlert("Order confirmation", message, "OK", "Cancel");
alert.addWindowListener(new IWindowListener() {
    public void windowClosing(WindowEvent event) {
        if (alert.getValue().equals("OK")) {
            fShoppingCartTableModel.emptyShoppingCartTableModel();
            fOrderButton.setEnabled(false);
            sendOrder();
        }
    }
});
alert.show();

After the user closes the dialog, the window listener on the server side is called, which then takes the same actions as in the Swing example above.

Another difference appears when you want to optimize the responsiveness of the user interface. Consider the online shopping application shown above: when the user clicks on the product items in the upper right-hand view, the product details are displayed in the view below. Assuming that the details need to be fetched from the server, busy waiting is not desirable at this point. The data should be loaded in the background, while the user interface stays responsive. In Swing, the list selection listener that monitors selection changes and fetches the product details would be realized as follows (note the use of a second thread):

public class ProductSelectionListener implements ListSelectionListener {
    public void valueChanged(ListSelectionEvent event) {
        Thread thread = new Thread() {
            public void run() {
                final List products = fetchProductsFromServer();
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        fProductDetailsPane.setProducts(products);
                    }
                });
            }
        };
        thread.start();
    }
}

This listener would then be added to the list selection model: 

listSelectionModel.addListSelectionListener(new ProductSelectionListener());

Since the ULC API is server-side, the corresponding ULC solution is to configure the list selection event to be delivered asynchronously:

ClientContext.setEventDeliveryMode(ulcListSelectionModel, 
    UlcEvent.LIST_SELECTION_EVENT, IUlcEventConstants.ASYNCHRONOUS_MODE);

where the selection listener is implemented as follows without a second thread:

class ProductSelectionListener implements IListSelectionListener {
    public void valueChanged(ListSelectionEvent event) {
        fProductDetailsPane.setProducts(getSelectedProducts());
    }
}

As in Swing, the listener is then added to the list selection model: 

ulcListSelectionModel.addListSelectionListener(new
ProductSelectionListener());

A third distinction between ULC and Swing are enablers and validators. To reduce server roundtrips, ULC offers enablers that allow, for example, local enabling of a button as soon as the user types into an associated text field. Likewise, validators can be used for local syntax checking of user input. Notice that such enablers and validators can be realized with Swing as well. The essential difference is that ULC needs to deliver them out-of-the-box since its API is entirely server-side.

Concluding, we can say that except for a few details, using the ULC peer classes is like using Swing. The effect for development is dramatic: you essentially create a multi-user client/server application the same way and with the same effort as a single-user stand-alone application.

IDE Integration

ULC is a pure Java library and therefore fits into any Java IDE. There is one particularly useful tool that comes with ULC: the DevelopmentRunner.

This tool executes both the client and the server in a single virtual machine, simulating their processes and communication. To use it, you just call it in the main method:

public static void main(String[] args) {    
  DevelopmentRunner.setApplicationClass();
  DevelopmentRunner.main(args);
}

Executing a ULC application within your IDE is a bonus for development. It eliminates the need for deployment, thus shortening the edit/compile/test cycle. Moreover, you can debug within a single address space, which is a considerable advantage. And finally, the DevelopmentRunner provides a graphical user interface that supports the monitoring of client/server interaction:

DevelopmentRunner UI

The dialog shows the messages exchanged, and enables you to vary the speed of communication between client and server. In this way, you can simulate real life behavior in different network setups.

For developers who work with Eclipse, Canoo offers an integrated visual editor that generates code based on the visual representation of a GUI. The functionality of this editor corresponds to the visual editors for Swing that are available for Eclipse.

Runtime Execution

From an architectural point of view, programs using ULC are similar to HTML-based J2EE applications: the entire business logic and the model of the presentation logic execute on the server.

Runtime Architecture

The client is a presentation engine that is independent of a particular application. The engine can serve any number of applications, similar to a browser. The difference compared to a browser is that the ULC engine executes a Swing-based event loop and communicates with the server-side peer objects, interpreting descriptions of widgets and events instead of HTML.

Application Deployment

A ULC based application can be deployed in a servlet container or in an EJB container. Since ULC is J2EE compliant, the deployment scenario is a matter of configuration and does not affect the application code. Deployment as a servlet will result in the following execution setup:

Deployment in a Servlet Container

And the alternative deployment as a stateful session bean:

Deployment in an EJB Container

The servlet container is preferable for most scenarios because it carries less overhead and implies communication via HTTP(S), which alleviates the usage of the Internet as a transmission channel.

An EJB container provides more options for application management, such as support for passivation of inactive sessions, which may be a valuable feature to achieve higher performance and scalability. Due to the fact that J2EE compliance enforces RMI/IIOP(S) for communication, this scenario will be typically used in the Intranet.

Client Deployment

The presentation engine has two components: the launcher and the core. The launcher creates the execution environment and starts the core therein. For example, the launcher can be a standalone Java application creating a secure sandbox for the presentation engine. It can also be an applet running the engine within a browser. A third alternative is to run the engine within a Java Web Start execution environment.

Deployment Options for Presentation Engine

The ULC release contains default launchers for each environment. The DefaultJnlpLauncher, for example, is provided for the Java Web Start environment. The corresponding JNLP file – for the online shopping application shown above – then looks as follows (productcatalog_webstart.jnlp):

<application-desc
     main-class="com.ulcjava.environment.jnlp.client.DefaultJnlpLauncher"
     <argument>http://localhost:8080/productcatalog.ulc</argument>

     <argument>900</argument>
</application-desc> 

And this is the HTML code to run the same application as an applet (productcatalog_applet.html):

<PARAM name="java_code"
    value="com.ulcjava.environment.applet.client.DefaultAppletLauncher.class">

<PARAM name="url-string"
    value="http://localhost:8080/productcatalog-applet.ulc">

<PARAM name="keep-alive-interval"
     value="900">

Security

The fact that ULC programs execute on the server and do not transfer executable code to the client is an evident bonus for security.

Beyond this, ULC offers a variety of choices to meet stringent security requirements, similar to a browser. The presentation engine can be executed in a sandbox, either as an applet or a Java Web Start application. Moreover, HTTPS or IIOPS can be chosen for communication in order to provide data encryption and client/server authentication.

In a trusted environment, for instance, the presentation engine can run outside the sandbox if it is restricted to connect to trusted hosts. Alternatively, the presentation engine can be signed by a trusted entity and its server access restricted to specific hosts. Finally, access to security-sensitive functionality - e.g. access to portions of the file system - can be restricted, using the Java Security Architecture.

Most of these security restrictions will be implemented by defining a specific launcher. The following example shows how access can be restricted to a specific host.

public class CanooJnlpLauncher extends AbstractJnlpLauncher {
    public static void main(String[] args) {
        String urlString = args[0];
        String host = new URL(urlString).getHost();
        if (!(host.endsWith(".canoo.com")) {
            showMessageDialog("Invalid host!");
            System.exit(-1);
        }
        new CanooJnlpLauncher().start(...);
    }
}

Transparent and Optimized Communication

J2EE compliance and the deployment options for both the client and the server demand that the developer be able to configure the communication protocol. Indeed, the ULC library supports all protocols that are allowed by J2EE. There is no impact on the application code. Configuring the application shown above with HTTPS instead of HTTP, for example, is a matter of changing the URL in the JNLP file (productcatalog_webstart.jnlp):

<application-desc
     main-class="com.ulcjava.environment.jnlp.client.DefaultJnlpLauncher">
     <argument>https://localhost:8443/productcatalog.ulc</argument>
     <argument>900</argument>

</application-desc>

Notice that this level of configurability requires Java platform v 1.4, which includes support for HTTPS. For earlier versions – ULC runs from 1.2 upwards – the protocol handler and security provider must be set explicitly within the code.

The essential point here is that the developer just selects the protocol. There's no need to know any details about a particular protocol to send or receive messages, which means that communication is entirely transparent for programming.

Transparency of communication is a major benefit of the half-object pattern and ULC, for two reasons: first, hand-coding of communication is pretty hard and error prone. Programming will be much easier if a proven, built-in protocol handles this task. Second, a built-in protocol can be optimized to an extent that is hardly feasible for an individual application. It is not surprising that Canoo has leveraged this potential, applying the entire range of techniques available for such optimizations:

  • asynchronous lazy loading ensures that only visible items of the user interface are transferred;
  • data compression reduces the size of messages;
  • client-side caching ensures that data is transferred only once;
  • client-side enablers and validators of user input reduce roundtrips.

All of these optimizations are executed irrespective of whether the developer chooses HTTP, HTTPS, RMI/IIOP, or RMI/IIOPS as the underlying transport protocol.

Standalone Execution

Given JRE as the execution environment on both client and server, it is evident that running an application on a single machine is a minor challenge. As mentioned above, ULC comes with the DevelopmentRunner that executes client and server in a single VM, simulating their network interaction. This scenario is helpful for three use cases:

  1. The first is development, where client and server are integrated into the VM of the IDE (see above).
  2. The second use case is an evolutionary release strategy where you start out with a standalone single user application that grows into a multiuser client/server application. Given the DevelopmentRunner, such an evolution is trivial. All the developer needs to do is observe the rules of thread-safe programming and concurrent database access, which essentially boils down to avoiding static variables and ensuring that database updates are transactional. This done, any standalone application can be run as a multiuser client/server application without changing the code.
  3. The third use case is on/offline execution of applications. The DevelopmentRunner enables creation of applications that can be run both stand-alone and in client/server mode, and is therefore an ideal basis for programs that can run online as well as offline.

A further scenario for standalone execution is to run client and server in separate VMs but on a single machine. This scenario will be useful for multichannel applications, where the server part of the application is shared except for the presentation layer that offers both an HTML GUI and a Swing GUI. For such scenarios, it may be desirable to install a complete infrastructure for a web application server on a standalone machine, particularly for development.

ULC comes with a default launcher for this scenario:

java com.ulcjava.environment.standalone.client.DefaultStandaloneLauncher <a
href="http://localhost:8080/productcatalog.ulc">http://localhost:8080/productcatalog.ulc</a>

Extension API

Out of the box, ULC provides the peer classes for the complete set of standard Swing widgets. For cases where these are not sufficient, ULC offers an API for extensions. This API enables both the integration of existing third party widgets and the development of new widgets.

Creating and integrating new widgets requires three steps:

  • First, the new Swing-based widget is created. This step is independent of ULC, i.e. identical to creating a new widget for an ordinary Swing program.
  • Second, the client half-object is developed. It must be a subclass of com.ulcjava.base.client.UIComponent.
  • Third, the server half-object is created, which must be a subclass of com.ulcjava.base.application.ULCComponent.

Integrating a widget from an existing third party library eliminates the first step of this process.

The developer has a lot of leeway for such integration. She or he may decide on the behavior of the widget in the ULC context, choosing anything between a simple and a sophisticated solution. The decisive factor is how much effort is put into the synchronization of the half-objects and the corresponding communication protocol.

Consider, for example, integrating an existing widget that displays city maps. In a simple solution, we would synchronize the half-objects just once, i.e. the server-side widget would send the entire data to the client together with the message that instantiates the widget. The client-side object would then be autonomous and no longer communicate until terminated. In a more sophisticated approach, the server-side object would find out how much of the map will be visible on the client and send only the corresponding fraction of the data. Subsequently, the client-side object would have to request further data if the user scrolled to previously invisible parts. Such lazy loading can be hard to realize, especially if combined with other optimizing techniques like asynchronous communication and caching. Needless to say that complexity is further increased for widgets that allow data manipulation.

Since ULC goes a long way in optimizing synchronization for the standard Swing widgets, there is some useful infrastructure for developing extensions. Examples are event handlers and both synchronous and asynchronous data transfer functions. Yet, there is no silver bullet providing a lean and efficient synchronization of half-objects for all kinds of extensions. Which is no surprise, because the strength of ULC’s approach is that optimizations are made for each individual type of widget.

Conclusion

Applying the half-object design pattern is a promising approach to leverage J2SE in a J2EE architecture. Canoo’s UltraLightClient succeeds in providing a server-side API for the entire set of Swing widgets, with transparent and highly optimized client/server interaction. The benefit for development is that a number of difficult issues are resolved, including the split between client and server, communication, and most security concerns. A further impressive feature is that ULC based programs can be configured to run in any standard Java platform environment. The most formidable advantage, however, is the server-side architecture that fits seamlessly into existing web platforms. The result is that thin client web applications and platforms can profit from the rich GUI capabilities of Swing and the J2SE environment.

The limitations of ULC’s approach are reached when heavy processing is needed on the client side. Productivity applications like sophisticated visual editors are an example. For such applications, splitting the presentation layer by means of the half-object pattern is a bad idea, because this will introduce a lot of complexity. The proper domain for the half-object pattern and ULC is the typical client/server business application running in the Intranet or Internet. For these latter scenarios, ULC is an effective way to leverage the GUI capabilities of J2SE without giving up the advantages of HTML-based web applications.