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:
- The first is development, where client and server are integrated into the VM
of the IDE (see above).
- 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.
- 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.