Reintroducing Jini: An introduction to Jini 2.1

Reintroducing Jini: An introduction to Jini 2.1

By Ryan Withers, OCI Senior Software Engineer

October 2007


Introduction

There is no question we live in a distributed world today. In fact the Internet has become ubiquitous within our homes and organizations. In this brave new "networked" world IT professionals have a challenge to create systems with higher reliability and availability. Technologies which help provide business continuity, fault tolerance, and competitive service level agreements are needed to support these requirements. Read on to discover one of the best kept secrets for today's distributed enterprise.

Jini 2.1

Unlike other frameworks and API's in the Java community, Jini isn't an acronym, and it has no official meaning. Jini provides services with self healing properties, persistent transport and plug-n-play attributes, all via an open and extensible API. Jini addresses many common misconceptions of distributed computing, and through doing so provides a robust, scalable platform. Given the recent paradigm shift from n-tier to service oriented systems, it is important to note Jini was service oriented long before the term was commonplace vocabulary. Sun Microsystems introduced the Jini platform at version 1.0, and has continued to support it through 1.1, 1.2, 2.0, and now 2.1. There are several large companies which have chosen the Jini Platform as their infrastructure. Orbitz is probably the most notable of these, however it is only one of several success stories. I chose the title 'Reintroducing Jini' due to a general misunderstanding the community has had about the technology, coupled with concern over Sun's long term support of the product. The good news is, the Jini Community is alive and well. Within the last year Sun released Jini under the Apache 2.0 license and renamed it the Apache River Project. In addition, you may be interested to explore work going on at the Rio, and Blitz projects, both aspire to make the Jini platform easier for developers to adopt. If you are interested in service oriented architectures, or are evaluating platforms for your company, please read on. This article will provide a look at coding up a service and associated client using Jini version 2.1. At the end the reader should have a basic knowledge of Jini including: Lookup Discovery, Service Registration, and Leasing.

Architecture

At Jini's inception, and to some extent Java's, the mantra at Sun was "The computer is the network" (Newmarch, pg 3). This carries over to Jini very naturally, where the core infrastructure supports a system of federated services across a network. Clients can "search" for these services and receive notifications of their coming and going. In Jini all services have an interface and corresponding implementation. "Searching" is often performed by looking for a service which implements a specific type of interface. Services register themselves with a ServiceRegistrar which is included as part of the core platform. Clients can request services from the service registrar or be notified of their presence. Given this plug-n-play functionality Sun engineers originally targeted devices and embedded platforms as the ideal environment for Jini. So early on, Jini's identity was associated with devices and device control. In some respects it got typecast as device control software and hasn't been able to overcome this. Consider the following interconnected devices Jini was targeted at dealing with, an alarm clock, toaster oven, fan, table lamp, among others. All of these can be turned off or on at will, many can have variable states, and some can even alert us of events like "It's time to get up." Jini was designed to handle the unpredictability of the devices suggested above. It is in this kind of dynamic environment which Jini was intended to support. Given this background one must pose the question, why wasn't this technology more successful? There were primarily two barriers to adoption, one of understanding the technology's capabilities, and the other configuration and deployment. With Jini 2.1 Sun solved the configuration and deployment issues, and those in the community are continuing to provide new tools to aid its adoption. This is all coupled with the fact that businesses and organizations are just starting to realize the benefits of a technology like Jini.

Getting Started

The provided examples were built and developed on a Linux server I have at home. You can download the starter kit which contains libraries prepackaged for either Linux, Windows, Mac OS X or Solaris. It is referred to as a starter kit because it is a reference implementation, however most find it suitable for production environments as well. Both the Linux and Solaris versions are packaged into bin executables, the windows version is a self-extracting executable, the Mac version is a zip file and the java version is packaged as a jar file (of course).

To get started just run the installer. Next we'll run the startup script included in the installation. Change into the $JINI_HOME/installverify directory and run the Launch-All command. This should startup a number of services along with the service browser depicted below. The examples will require the browser later, for now just leave it running.

This view indicates a group is defined, as well as 1 registrar. However, the registrar is not selected. The view shown below displays after the user selects the registrar, which can be done by selecting Registrar->hostname. We'll discuss the registrar as well as the purpose of groups later.

[rwithers@noah ant-project]$ ant compile
Buildfile: build.xml
 
init:
    [mkdir] Created dir: /home/rwithers/projects/Jini/code/ant-project/classes
    [mkdir] Created dir: /home/rwithers/projects/Jini/code/ant-project/dist
 
compile:
     [echo]  Building Project
    [javac] Compiling 11 source files to /home/rwithers/projects/Jini/code/ant-project/classes
      [jar] Building jar: /home/rwithers/projects/Jini/code/ant-project/dist/JnbJiniClient.jar
      [jar] Building jar: /home/rwithers/projects/Jini/code/ant-project/dist/JnbJiniServer-dl.jar
      [jar] Building jar: /home/rwithers/projects/Jini/code/ant-project/dist/JnbUniMultiExample.jar
 
BUILD SUCCESSFUL
Total time: 1 second
[rwithers@noah ant-project]$

Lookup Discovery

The three primary components of Jini are clients, services, and service locators. The service locator provides discovery functionality which clients and services can use to find each other on the network. There are two types of service locator, multicast and unicast. The LookupLocator class supports unicast discovery. In unicast URLs are used to designate the location of the service(s) we are interested in. Common URL formats for this include: jini://host or jini://host:port. In case the lookup port isn't specified, the port 4160 is chosen by default. The LookupDiscovery class supports multicast discovery; using this method the client that instantiates the LookupDiscovery tells the class what kind of services it's interested in finding via an array of Strings specifying groups. The LookupDiscovery class requires us to implement the DiscoveryListener interface. The LookupLocator is functionally synchronous while LookupDiscovery is asynchronous relying on an event listener. The two approaches are outlined below:

LookupLocator (unicast lookup)

The code below shows the retrieval of a ServiceRegistrar reference. It should be noted the code to write a client or server would be identical at this point. In other words, the registrar reference is needed for both cases. To run this code you must have the jsk-platform.jar and jsk-lib.jar files in the classpath. These jar files are found in the $JINI_HOME/lib directory.

 1  package com.ociweb.jnb.oct;
 2
 3  import java.rmi.RMISecurityManager;
 4  import net.jini.core.discovery.LookupLocator;
 5  import net.jini.core.lookup.ServiceRegistrar;
 6  /*
 7   * JiniLauncher.java
 8   */
 9  public class JiniLauncher {
10
11      /** Creates a new instance of JiniLauncher */
12      public JiniLauncher() {
13      }
14
15      public void launch() {
16          LookupLocator lookup = null;
17          ServiceRegistrar registrar = null;
18
19          System.setSecurityManager(new RMISecurityManager());
20
21          try {
22              lookup = new LookupLocator("jini://localhost");
23          } catch (java.net.MalformedURLException ex) {
24              System.err.println("Lookup failed: " + ex.toString());
25              System.exit(1);
26          }
27
28          try {
29              registrar = lookup.getRegistrar();
30          } catch (java.io.IOException ioe) {
31              System.err.println("Registrar search failed: " + ioe.toString());
32              System.exit(1);
33          } catch (java.lang.ClassNotFoundException cnfe) {
34              System.err.println("Registrar search failed: " + cnfe.toString());
35              System.exit(1);
36          }
37          System.out.println("Registrar Found");
38          // the code takes separate routes from here based on whether it is a 
39          // client or a service.
40      }
41
42      /**
43       * @param args the command line arguments
44       */
45      public static void main(String[] args) {
46          JiniLauncher launcher = new JiniLauncher();
47
48          // Acquiring a registrar via the unicast approach.
49          launcher.launch();
50      }
51  }

This code demonstrates synchronous lookup functionality using the launch() method above. Note the ant target for running the example above defines a security policy with the following entry -Djava.security.policy=${jini_home}/installverify/support/jsk-all.policy. While a discussion of security policies is beyond the scope of this article, more information can be found here. Basically, this provides Jini the access it needs to make the socket call requesting a registrar reference. After setting the security policy a call is made to the launch() method where right off the bat an RMISecurityManager is created (line 19). Then there is a try/catch block in which the LookupLocator is instantiated with a URL, in this case jini://localhost. Upon successful completion of this call we come upon the final try/catch block where the call to getRegistrar is made to obtain the registrar reference (line 29). Now the client can use the registrar reference to obtain the services it is interested in, and a server could register services. Running this code produces the output shown below:

[rwithers@noah ant-project]$ ant unicast
Buildfile: build.xml
 
init:
 
compile:
     [echo]  Building Project
 
unicast:
     [echo]  Running JiniLauncher. . .
     [java] Registrar Found
 
BUILD SUCCESSFUL
Total time: 1 second
[rwithers@noah ant-project]$

There are all kinds of problems you can run into when writing a Jini Service or Client. In order to deal with the more common issues there is a section in Jan Newmarch's online book dedicated to troubleshooting, you can find it here: Troubleshooting. This may serve as a good reference when working through the examples presented in the article.

LookupDiscovery (Multicast Lookup)

The multicast locator builds on the concepts from the unicast locator, please see the listing below along with the comments following for more information.

 1  package com.ociweb.jnb.oct;
 2
 3  import java.rmi.RemoteException;
 4  import net.jini.core.lookup.ServiceRegistrar;
 5  import net.jini.discovery.DiscoveryEvent;
 6  import net.jini.discovery.DiscoveryListener;
 7  import net.jini.discovery.LookupDiscovery;
 8  /*
 9   * MulticastRegister.java
10   *
11   * Created on September 15, 2007, 9:38 PM
12   */
13  public class MulticastJiniLauncher implements DiscoveryListener {
14
15    /** Creates a new instance of MulticastRegister */
16    public MulticastJiniLauncher() {
17      System.setSecurityManager(new java.rmi.RMISecurityManager());
18      LookupDiscovery discover = null;
19
20      try {
21        discover = new LookupDiscovery(LookupDiscovery.ALL_GROUPS);
22      } catch(Exception ex) {
23        System.err.println(ex.toString());
24        ex.printStackTrace();
25        System.exit(1);
26      }
27      discover.addDiscoveryListener(this);
28    }
29
30    public void discovered(DiscoveryEvent discoveryEvent) {
31      ServiceRegistrar[] registrars = discoveryEvent.getRegistrars();
32      for (int i=0; i < registrars.length; i++) {
33        ServiceRegistrar registrar = registrars[i];
34
35        try {
36          System.out.println("found a service locator at " +
37                              registrar.getLocator().getHost() +
38                             " listening on port " + registrar.getLocator().getPort());
39        } catch (RemoteException e) {
40          e.printStackTrace();
41        }
42      }
43    }
44
45    public void discarded(DiscoveryEvent discoveryEvent) {
46      ServiceRegistrar[] registrars = discoveryEvent.getRegistrars();
47
48      for (int i=0; i < registrars.length; i++) {
49        ServiceRegistrar registrar = registrars[i];
50
51        try {
52          System.out.println("Service locator at " +
53                              registrar.getLocator().getHost() +
54                              " listening on port " + registrar.getLocator().getPort() +
55                              " has been shutdown.");
56        } catch (RemoteException ex) {
57          ex.printStackTrace();
58        }
59      }
60    }
61
62    static public void main(String[] args) {
63      new MulticastJiniLauncher();
64
65      try {
66        Thread.currentThread().sleep(10000L);
67      } catch(java.lang.InterruptedException ie) {
68        //do nothing
69      }
70    }
71
72  }

Notice the implementation of the DiscoveryListener. This interface provides the methods the Jini framework uses to notify the MulticastRegister class that a lookup service has been found. The two events we'll be notified of are lookup services which become available on the network via the discovered() method, and lookup services leaving the network via the discarded() method. Not that lookup services are those provided behind the service browser. The only time we will receive an event is if the service browser is either shutdown (discarded) or started up (discovered). When a discovered event is received it comes with any registrars matching the groups we are interested in. In the case above we don't care what group we match on so we've specified ALL_GROUPS line 21. It's very easy to see how we could build redundancy into a network by bringing up a second lookup service in the same group and then holding registrar references to both. We are now armed with enough information to explore building a server and corresponding client. Running the code shown above produces the output shown below, pay attention to the 11 seconds in bold, this is due to a sleep in the code to assure the discovered event is received.

[rwithers@noah ant-project]$ ant multicast
Buildfile: build.xml
 
init:
 
compile:
     [echo]  Building Project
 
multicast:
     [echo]  Running Multicast Jini Launcher. . .
     [java] found a service locator at localhost listening on port 4160
 
BUILD SUCCESSFUL
Total time: 11 seconds
[rwithers@noah ant-project]$

A Jini Server Implementation

To start with server-side functionality is needed, for this I've provided both a MorseConverter, and an UpcaseConveter class. These classes are designed to take as input a text string and output the equivalent morse code or upper case text as a result. The morse converter class is shown below which shows the primary functionality implemented in the form of a convert method, along with a UML Class diagram.

Morse Converter Model

Convert method from MorseConverter

 87    /**
 88     * This method takes a text string as input and returns the morse code equivalent.
 89     *
 90     * e.g. 
 91     *      If the input text is "Learning Jini!"
 92     *
 93     *      Then the converted text should be: 
 94     *      .-.. . .- .-. -. . -. --.    .--- . -. . -.-.--
 95     *      
 96     *      Notice that there is a space between each "letter" and two spaces 
 97     *      between each "word" for readability. 
 98     *
 99     *      If the input text is changed to Learning Jini%
100     *
101     *      Then the converted text will be: 
102     *
103     *      .-.. . .- .-. -. . -. --.    .--- . -. . #
104     *
105     *      In this case the pound sign represents a character that the 
106     *      converter didn't recognize.  This character was the % symbol.  
107     *
108     * @param textToConvert - string representing the text to convert to morse code.
109     * @return String - the converted string 
110     */
111    public String convert(String textToConvert) {
112      StringBuffer convertedBuf = new StringBuffer();
113
114      if (textToConvert.length() > 0) {
115        String chrStr = "";
116        char[] carray = textToConvert.toCharArray();
117
118        for (int i=0; i < carray.length; i++) {
119          chrStr = carray[i] + "";
120
121          if (morseHash.containsKey(chrStr.toUpperCase())) {
122            convertedBuf.append(morseHash.get(chrStr.toUpperCase()) + " ");
123          } else {
124            convertedBuf.append("#"); // we didn't understand some of the input
125          }
126        }
127      }
128
129      return convertedBuf.toString();
130    }

Now with the MorseConverter implemented, we can shift our focus to exposing this functionality as a Jini Service. First the MorseConverter will become an interface, and the class we've implemented above will get renamed to MorseConverterImpl. This is shown below, one thing to note is other than exposing the interface, we've done nothing else. All of this is still just standard java compiled right from the command line. To contrast this with CORBA, this means no stubs and skeletons.

Morse Converter Interface

Although it may seem to be a small detail that MorseConverterImpl implements Serializable. This is actually significant because unlike CORBA, RMI, or RPC where you have a clearly defined notion of client and server, Jini imposes no such restrictions. The implementation class and all its dependencies are downloaded and run on the client. This is a bit of a paradigm shift from the typical client server relationship. It is this flexibility which showcases the power of Jini. This actually requires us to perform a bit of administration to make this work. Both the client and server have to specify a -Djava.rmi.server.codebase=http://localhost/classes/JnbJiniServer-dl.jar where the codebase is a URL which is requested from a web server. In this case the jar file is underneath the DocumentRoot directory which in apache defaults to the directory that pulls up when pointing to http://localhost or the main domain of the server. Then underneath the DocumentRoot there is a directory named classes where our jar file lives.

The Server

So now we've fixed the code so the service can be made available to our clients. At this point the server code needs to be written to expose this functionality. There is a distinction between service and server, the service is the code downloaded to the client, the server is the code that registers the service with the lookup registrar. The listing for the server is below:

  1  /*
  2   * MorseConverterServer.java
  3   */
  4
  5  package com.ociweb.jini.server;
  6
  7  import com.ociweb.jini.shared.MorseConverter;
  8  import java.io.DataInputStream;
  9  import java.io.DataOutputStream;
 10  import java.io.FileInputStream;
 11  import java.io.FileOutputStream;
 12  import java.rmi.RMISecurityManager;
 13  import java.rmi.RemoteException;
 14  import net.jini.core.entry.Entry;
 15  import net.jini.core.lease.Lease;
 16  import net.jini.core.lookup.ServiceID;
 17  import net.jini.core.lookup.ServiceItem;
 18  import net.jini.core.lookup.ServiceRegistrar;
 19  import net.jini.core.lookup.ServiceRegistration;
 20  import net.jini.discovery.DiscoveryEvent;
 21  import net.jini.discovery.DiscoveryListener;
 22  import net.jini.discovery.LookupDiscovery;
 23  import net.jini.lease.LeaseListener;
 24  import net.jini.lease.LeaseRenewalEvent;
 25  import net.jini.lease.LeaseRenewalManager;
 26  import net.jini.lookup.entry.Name;
 27  import net.jini.lookup.entry.ServiceInfo;
 28
 29  public class MorseConverterServer implements DiscoveryListener, LeaseListener {
 30
 31    /** Member declarations **/
 32    private String idFileName = "MorseConverter.id";
 33    private LookupDiscovery discover = null;
 34    private ServiceRegistrar[] registrars = null;
 35
 36    private Entry[] entries = { new Name("Morse Code"),
 37                                new ServiceInfo(MorseConverter.class.toString(), "OCI", "", "", "", "")};
 38
 39    protected LeaseRenewalManager leaseManager = new LeaseRenewalManager();
 40    protected ServiceID serverId = null;
 41    protected MorseConverterImpl server = null;
 42
 43    /** Creates a new instance of MorseConverterServer */
 44    public MorseConverterServer() {
 45      server = new MorseConverterImpl();
 46
 47      // Try to load service id from file.  It isn't an error if we can't load it.
 48      // If this is an initial run attempt we may not have an id. 
 49      DataInputStream din = null;
 50      try {
 51        din = new DataInputStream(new FileInputStream(idFileName));
 52        serverId = new ServiceID(din);
 53      } catch(Exception ex) {
 54        // throw an exceptions on the floor.
 55      }
 56
 57      // setup the RMI security manager 
 58      System.setSecurityManager(new RMISecurityManager());
 59
 60      try {
 61        discover = new LookupDiscovery(LookupDiscovery.ALL_GROUPS);
 62      } catch (Exception e) {
 63        System.err.println("Discovery failed " + e.toString());
 64        System.exit(1);
 65      }
 66      discover.addDiscoveryListener(this);
 67    }
 68
 69    public void discovered(DiscoveryEvent evt) {
 70      registrars = evt.getRegistrars();
 71
 72      for (int n=0; n<registrars.length; n++) {
 73        ServiceRegistrar registrar = registrars[n];
 74
 75        ServiceItem item = new ServiceItem(serverId, server, entries);
 76
 77        ServiceRegistration reg = null;
 78        try {
 79          reg = registrar.register(item, Lease.FOREVER);
 80        } catch(java.rmi.RemoteException rex) {
 81          System.err.println("Register exception: " + rex.toString());
 82          continue;
 83        }
 84        System.out.println("Service registered with id " + reg.getServiceID());
 85
 86        leaseManager.renewUntil(reg.getLease(), Lease.FOREVER, this);
 87
 88        if (serverId == null) {
 89          serverId = reg.getServiceID();
 90
 91          DataOutputStream dout = null;
 92          try {
 93            dout = new DataOutputStream(new FileOutputStream(idFileName));
 94            serverId.writeBytes(dout);
 95            dout.flush();
 96          } catch(Exception ex) {
 97            // ignore
 98          }
 99        }
100      }
101    }
102
103    public void discarded(DiscoveryEvent discoveryEvent) {
104      System.out.println("Received a discarded event.");
105    }
106
107    public void notify(LeaseRenewalEvent evt) {
108      System.out.println("Lease expired " + evt.toString());
109    }
110
111    public static void main(String[] args) {
112      MorseConverterServer mcs = new MorseConverterServer();
113
114      // Keep server running to maintain the lease and allow for 
115      // locator discovery. 
116      Object keepAlive = new Object();
117      synchronized(keepAlive) {
118        try {
119          keepAlive.wait();
120        } catch (InterruptedException ex) {
121          // do nothing
122        }
123      }
124    }
125
126  }

The most important lines above allow us to create a new implementation instance (line 45), secondly we create a service item passing in that instance (line 75). Those two lines sum up the registration of the MorseConverter interface (line 79). The register call in this implementation passes the value of Lease.FOREVER, indicating the service registration will not run out. If a time is specified as long as the server is running it will be required to renew the lease when it expires. The benefit to this is it protects clients from contacting a service which is not available. So by picking Leases carefully you can see how Jini developers can write code that has a higher availability. In addition to providing an interface implementation to the register method, there are several "entries" which allow us to store metadata about the service. Clients can use this metadata to look the service up or monitor changes in state. Now not only can we browse the entries we've created, we can also use clients to query the information we're seeing in the service browser. The service item information is shown below, it can be navigated to by double clicking the service of interest in the service browser:

Service Item

Running this code will produce the following output:

[rwithers@noah ant-project]$ ant morse
Buildfile: build.xml
 
init:
 
compile:
     [echo]  Building Project
 
morse:
     [echo]  Running Morse Converter Server. . .
     [java] Service registered with id 6e584fa6-a32a-4fe8-afc6-b5f97d07b831

Server Deployment Concerns

Jini requires a fair amount of care be taken in what class files are deployed across the network. Once this is accomplished configuration is really very minimal, most of the service wiring is performed by Jini's Discovery infrastructure. When you build the examples provided with the article there are three jar files produced:

The JnbUniMultExample.jar contains the source for both the JiniLauncher and the MulticastJiniLauncher. The JnbJiniClient.jar and the JnbJiniServer-dl.jar pertain to the following discussion. The -dl on the end of the server jar is a convention used to note that this jar file contains classes that are "downloaded" to the client. The diagram below has been borrowed from Foundations of Jini 2 Programming by Jan Newmarch, it gives a good illustration of where things need to go when running Jini services. It illustrates the idea that code is mobile when running jini services.

Conceptual Diagram

From the diagram above it is apparent both the client and server have to obtain a ServiceRegistrar. In addition, the implementation class for the server must exist on the server side and as part of the HTTP server. In other words the Jar for the server exists as part of the Server class path and is also dropped behind the http server. The client knows nothing about the server implementation and is handed the implementation at runtime. The mobility of the Server implementation and Service Registrar are illustrated by the arrows above.

Now with the server running as an experiment stop and then restart the service browser. This should show output from the server indicating two registrations have taken place. After some time you may also notice a discarded event received, which is for the previous registration. This highlights the self-healing nature of Jini, in that the Lookup service can be brought up and down as you wish and the services registered with it will be notified and refresh their information. For those familiar with CORBA, infrastructure typically has to be put into place to achieve this kind of flexibility. So we're starting to see some of the advantages to deploying a middleware solution such as Jini in a production environment. See below for the output to expect after cycling the service browser:

[rwithers@noah ant-project]$ ant morse
Buildfile: build.xml
 
init:
 
compile:
     [echo]  Building Project
 
morse:
     [echo]  Running Morse Converter Server. . .
     [java] Service registered with id 1acafcb9-9297-4159-8041-6812ef99ebbb
     [java] Service registered with id 1acafcb9-9297-4159-8041-6812ef99ebbb
     [java] Lease expired net.jini.lease.LeaseRenewalEvent[source=net.jini.lease.LeaseRenewalManager@18e8541]

The Client

Now with our new server running we can run the client to see the interaction between the two. The code for the client follows:

  1  /*
  2   * ConverterClient.java
  3   */
  4
  5  package com.ociweb.jini.client;
  6
  7  import com.ociweb.jini.shared.Converter;
  8  import com.ociweb.jini.shared.MorseConverter;
  9  import com.ociweb.jini.shared.UpcaseConverter;
 10  import java.rmi.RMISecurityManager;
 11  import java.rmi.RemoteException;
 12  import net.jini.core.lookup.ServiceRegistrar;
 13  import net.jini.core.lookup.ServiceTemplate;
 14  import net.jini.discovery.DiscoveryEvent;
 15  import net.jini.discovery.DiscoveryListener;
 16  import net.jini.discovery.LookupDiscovery;
 17
 18  public class ConverterClient implements DiscoveryListener {
 19    private static final String MORSE_SERVER_TYPE = "morse";
 20    private static final String UPCASE_SERVER_TYPE = "upcase";
 21    private static String server = "";
 22
 23    private Class[] classes = null;
 24
 25    /** Creates a new instance of MorseConverterClient */
 26    public ConverterClient() {
 27      if (MORSE_SERVER_TYPE.equals(server)) {
 28        classes = new Class[] {MorseConverter.class};
 29      } else if (UPCASE_SERVER_TYPE.equals(server)) {
 30        classes = new Class[] {UpcaseConverter.class};
 31      }
 32          // setup the RMI security manager 
 33      System.setSecurityManager(new RMISecurityManager());
 34
 35      LookupDiscovery discover = null;
 36      try {
 37        discover = new LookupDiscovery(LookupDiscovery.ALL_GROUPS);
 38      } catch (Exception e) {
 39        System.err.println("Discovery failed " + e.toString());
 40        System.exit(1);
 41      }
 42      discover.addDiscoveryListener(this);
 43    }
 44
 45    public void discovered(DiscoveryEvent evt) {
 46      ServiceRegistrar[] registrars = evt.getRegistrars();
 47      Converter converter = null;
 48
 49      ServiceTemplate template = new ServiceTemplate(null, classes, null);
 50
 51      for (int n=0; n < registrars.length; n++) {
 52        System.out.println("Lookup service found");
 53        ServiceRegistrar registrar = registrars[n];
 54        try {
 55          if (MORSE_SERVER_TYPE.equals(server)) {
 56            converter = (MorseConverter) registrar.lookup(template);
 57          } else if (UPCASE_SERVER_TYPE.equals(server)) {
 58            converter = (UpcaseConverter) registrar.lookup(template);
 59          }
 60        } catch (RemoteException ex) {
 61          ex.printStackTrace();
 62          continue;
 63        }
 64
 65        if (converter == null) {
 66          System.out.println("Converter was null");
 67          continue;
 68        }
 69
 70        // use the service to convert some text
 71        String textStr = "";
 72        try {
 73          textStr = "Learning Jini!";
 74          System.out.println(textStr);
 75          System.out.println(converter.convert(textStr));
 76
 77          textStr = "Practicing Jini!";
 78          System.out.println(textStr);
 79          System.out.println(converter.convert(textStr));
 80
 81          textStr = "Teaching Jini!";
 82          System.out.println(textStr);
 83          System.out.println(converter.convert(textStr));
 84
 85          textStr = "Implementing Jini!";
 86          System.out.println(textStr);
 87          System.out.println(converter.convert(textStr));
 88
 89        } catch (RemoteException rex) {
 90          System.err.println(rex.toString());
 91          continue;
 92        }
 93        System.exit(0);
 94      }
 95    }
 96
 97    public void discarded(DiscoveryEvent evt) {
 98    }
 99
100    public static void main(String[] args) {
101      server = System.getProperty("server");
102      if (server == null) {
103        System.err.println("You must specify -Dserver=[upcase|morse]");
104        System.exit(1);
105      }
106
107      new ConverterClient();
108
109
110      try {
111        Thread.currentThread().sleep(100000L);
112      } catch(java.lang.InterruptedException ex) {
113        // do nothing 
114      }
115    }
116
117  }

The output from running the program listed above is as follows:

[rwithers@noah ant-project]$ ant morseconverter
Buildfile: build.xml
 
init:
 
compile:
     [echo]  Building Project
 
morseconverter:
     [echo]  Running morse converter client. . .
     [java] Lookup service found
     [java] Learning Jini!
     [java] .-.. . .- .-. -. . -. --.    .--- . -. . -.-.--
     [java] Practicing Jini!
     [java] .--. .-. .- -.-. - . -.-. . -. --.    .--- . -. . -.-.--
     [java] Teaching Jini!
     [java] - . .- -.-. .... . -. --.    .--- . -. . -.-.--
     [java] Implementing Jini!
     [java] . -- .--. .-.. . -- . -. - . -. --.    .--- . -. . -.-.--
[rwithers@noah ant-project]$

The output comes from lines 73-87 in the code, and from where we've been you should notice this is using LookupDiscovery with an asynchronous event based notification. Once the event is received a ServiceTemplate is used to query the various ServiceRegistrars which come back with the event. In the example once the reference to the service is obtained the calls against it are made right within the discovered method. Since this is an example we aren't doing anything sophisticated to manage the registrar references we're obtaining. Typically the ServiceRegistrars would be obtained and stored in a service cache or some other mechanism which could be shared among the modules of a larger application.

Summary

Jini can provide far more than what we have discussed here in this Article. This is just a foundation, from which you should be able to continue learning Jini. We've looked at synchronous and asynchronous forms of service location. We've created a server that registers itself with a lookup service and exports an implementation service. To go along with this we created a client which looks the service up and downloads an implementation to do something useful. In this case we are converting text expressions to an equivalent of Morse Code. Jini has security features, it provides interfaces that allow the developer to abstract away the protocol (in other words it doesn't have to be RMI under the covers), it provides features that make it easy to build a remote service proxy. The point is Jini provides a lot of power, and we've just scratched the surface.

References

Please don't stop here, there are many resources on the web for further exploration of the Jini platform. The links below refer to articles or books used as background material for this article.