Browse user interfaces for Jini services

Build a JFC-based application for browsing and launching Jini services

In October 1999, JavaWorld columnist Bill Venners first introduced readers to a working draft of the ServiceUI specification for attaching user interfaces to Jini services. In the year and a half since then, the ServiceUI project at, largely due to Venners's efforts, has released a 1.0 specification and reference implementation.

In this article, I'll explain how to attach an interface to a simple Jini service, and then walk you through building an application to browse those services using Swing components and the ServiceUI framework.

Project requirements

Before starting any development project, it's important to define what you're trying to build. If you're learning to program on your own, that may only consist of notes on scraps of paper. If you're a developer working within a team, however, that often entails rigorous documentation garnered from multiple whiteboard-intensive meetings. Here I'll briefly describe our goals for the project and the tasks ahead of us.

The Time service

The Jini service Time is intended to demonstrate how to attach a graphical user interface (GUI) to a service. The service itself will be extremely simple: it's sole purpose is to provide a way to retrieve the current time and date.

The GUI attached to that service should provide a time display retrieved from the service, and a way for the user to update the display.

The Jini TaskBar

A Jini service with a user interface is useless if you have no way to view the UI. To that end, we'll design an application that can discover Jini services on the network and display associated UIs. While finding network services involves a certain amount of complexity, the application should try to hide that complexity from the user. Therefore, we'll model our application after one of the most common application browsing and launching systems: the TaskBar.

Getting started

Before we get too far along in the development process, you should first make sure that your development environment is properly set up. While Jini is compatible with J2SE 1.2.2 and above, I recommend using JDK 1.3 from Sun on either Linux or Windows 2000.

You'll also need the Jini Technology Starter Kit 1.1. We'll be using the jini-core.jar and jini-ext.jar libraries for development. In addition, you'll need a Jini lookup server somewhere on your network.

Finally, our project will use the ServiceUI 1.0 API from the community, as well as a utility class from the Jini-Tools project. Check out the Resources section for locations of these items.

TimeService interface

The most important component of any Jini service is its interface. The interface provides a layer of abstraction, under which services can choose varying implementations, while clients only need to know how to operate on an instance of the interface.

Designing truly generic service interfaces is not a simple matter. As a rule of thumb, check first with the community to see if others have crafted an interface that suits your needs. If they have not, you may want to start a community project, working with others to craft a pseudostandard interface or set of interfaces that provide a generic API for services such as yours. There may be times when a proprietary interface is the best course of action, but if you plan to introduce a service that others could use, keep the community in mind.

That said, our service here offers little practical use, so we will be crafting a proprietary interface. Below is the code for our interface:

public interface TimeService extends{
    public java.util.Date getTime() throws java.rmi.RemoteException;

That interface declares a single method, getTime(), which returns an instance of java.util.Date.

You'll notice that the interface extends instead of java.rmi.Remote. For that example, we'll use a specific type of Jini service in which the entire service is contained within the proxy. However, we've already made sure to declare that our single method may throw java.rmi.RemoteException. That is done so that we may later implement a remote version of the service, while still allowing client code to interact with the TimeService interface. Although we won't be using it in this example, I'll define the interface for the remote version of TimeService below:

public interface RemoteTimeService extends TimeService, java.rmi.Remote{

That interface defines no new methods (although it could), and simply extends both the TimeService interface and java.rmi.Remote. If a service were to implement that interface, the RMI stub would be substituted as a proxy when registered with the Jini lookup service.

TimeService implementation

Implementing the TimeService interface is equally as simple:

public class TimeServiceImpl extends java.lang.Object 
             implements TimeService{
   public TimeServiceImpl() {
      //Nothing to do here
   public java.util.Date getTime() {
      return new Date();

Since that object needs no resources, there is nothing to do in a constructor. Our single method inherited from the TimeService interface instantiates and returns a new Date object, which is initialized by default to the current system time.

Now we'll get into the code for the user interface. While I will typically define more complex user interfaces as distinct classes, I've chosen to implement the UI as a static inner class.

The code below declares a new class, TimeJFrame, which extends javax.swing.JFrame and implements java.awt.event.ActionListener. The constructor takes a net.jini.core.lookup.ServiceItem as the sole parameter. From the ServiceItem, we extract the service proxy and store it as an instance variable. The constructor also creates two Swing components, a JButton and a JLabel, and adds them to its content pane. The class registers itself to receive events from the button, so that we may respond and update the UI with the current time from the service:

   protected static class TimeJFrame extends JFrame implements ActionListener{
      protected TimeService jini_Time_Service;
      protected JLabel      comp_Time_Label;
      protected JButton     comp_Time_Button;
      protected SimpleDateFormat util_Date_Format;
      public TimeJFrame(ServiceItem item){
         super("Time Service UI");
         util_Date_Format = new SimpleDateFormat();
         comp_Time_Button = new JButton("Update Time");
         comp_Time_Label = new JLabel(); 
         comp_Time_Label.setBorder( BorderFactory.createTitledBorder(
            BorderFactory.createEtchedBorder(),"Time is:"));
         getContentPane().setLayout(new BorderLayout());
      public void actionPerformed(ActionEvent evt){
            Date d = jini_Time_Service.getTime();
         }catch(RemoteException e){
The view of a TimeJFrame when launched from the JiniTaskBar

The actionPerformed() method responds to button clicks by retrieving the current time from the service and updating the UI with a formatted text string representing the time. Since we declared the getTime() method in TimeService to throw an exception, we have enclosed the code within a try-catch block. We should take action to tell the user that an exception has occurred, but for now we will simply print out the stack trace. Since our implementation is not a remote object, the exception should never be thrown, but we need to take caution in case the implementation changes.

Service registration

Finally, we are ready to examine the code that will perform the registration of our service with the Jini lookup service. Some static methods will help perform that for us.

The first method to be called is initialize_Security_Manager(). That method sets the system security manager and makes sure that the security policy file has been set. We'll examine the policy in more detail when we run the service.

The next method called is initialize_Codebase_Server(). One of the most common mistakes developers make when working with Jini is forgetting to provide a Web server for dynamic class downloading. That procedure utilizes an embedded HTTP server, which can load and serve class files from the JVM's classpath. In exchange for the minimal overhead of a few extra threads, we gain tremendous flexibility in deployment. The utility class used here, dreamBean Software's DynaServer, can load any class or resource that is available to the local ClassLoader implementations offered to the server. DynaServer has been contributed by its author, Rickard Öberg, under an open source license to the Jini-Tools project lead by Iain Shigeoka.

That procedure is especially useful when developing ServiceUI-based services, which may use images, sounds, or other resources that need to be loaded on the client side. Instead of specifying a static location from which classes and resources should be loaded, we let the server decide how to set the java.rmi.server.codebase property and, on the client side, we can use the RMIClassLoader to get resources for us. While we're not loading any resources in our UI, here is an example on how you can load an image: resourceURL = getClass().getResource("/images/logo.gif");
JButton logoButton = new JButton(new ImageIcon(resourceURL)); 

As long as the service has the images directory located within its classpath (or in a jar included in the classpath), the client-side code will be able to dynamically locate the resource and load it from the CodebaseServer.

Finally, we're ready to instantiate the service and register it with the lookup service. The registration step is what differentiates a Jini service from a simple RMI remote object, and since that is where we link the service with the ServiceUI, it is that much more of a crucial step. We'll walk through the initialize_Service() method a step at a time:

   protected static void initialize_Service(TimeServiceImpl proxy){
      try {
         UIDescriptor jini_Service_UI_Descriptor = getUIDescriptor();
         Entry[] jini_Service_Attributes = { new Name("Time Service Example"),
                                             jini_Service_UI_Descriptor };

First, that static method takes an instance of our proxy. You'll notice that the method's entire body is enclosed within a try-catch block. Next, we initialize the attributes, which will be attached to the service when we register it with the lookup service. There we are attaching a net.jini.lookup.entry.Name, which contains a String-based human-readable name of the service. We are also attaching a net.jini.lookup.entry.UIDescriptor, which is the primary means for attaching a UI to a service.

The UIDescriptor object we create is part of the ServiceUI framework. That object is a subclass of the net.jini.entry.AbstractEntry class, and it contains a description of our UI, as well as all the code necessary to instantiate the UI. The getUIDescriptor() method takes care of creating the object for us:

   public static UIDescriptor getUIDescriptor() throws IOException{
      UIDescriptor desc = new UIDescriptor();
      desc.role = MainUI.ROLE;
      desc.toolkit = JFrameFactory.TOOLKIT;
      desc.attributes = new HashSet();
         new UIFactoryTypes(   
      desc.factory = new MarshalledObject(new Factory());
      return desc;

The role and toolkit fields contain strings describing the UI. Because they are public fields, the client may request services from the lookup service by specifying template entries to be matched. The ServiceUI 1.0 specification defines three core roles: MainUI, AdminUI, and AboutUI. The role system is extensible, so that as service designers, we may choose to add a new role if we have a UI type that does not match one of the predefined types.

The toolkit field describes the resources needed by the UI. For this example, you need the javax.swing toolkit available, but you might imagine a service intended for a J2ME MIDP device, which does not have AWT or Swing toolkits available. A resource-constrained device might search only for services with UIs that use the available toolkit, and not use precious memory examining services that it cannot use.

Finally, the attributes field is a java.util.Set that contains a UIFactoryTypes object. UIFactoryTypes is intended to describe the type of factory object contained within the UIDescriptor. The set can also contain other descriptive information about the UI, including the preferred size or location of the JFrame.

The last field in the UIDescriptor is the Factory object. That field does not actually describe the UI, but it contains an object that is capable of instantiating the UI. That Factory object is wrapped in a MarshalledObject, so that the client can examine the UIDescriptor without requiring it to download all the classes associated with the UI.

1 2 3 Page 1
Page 1 of 3