Chapter 10. Jini: Sun's Technology of Impromptu Networks

  • Examples of Jini

  • Where Did Jini Come From?

  • Our Working Jini Example

  • Basic Jini Concepts: "Discovery, Join, and Lookup Oh My!"

  • Getting Started with Jini

  • Lets Get to the Code!

The introduction of the iMac was no small factor in Apple's rejuvenation. It was also a revolutionary computer. The iMac's success came from its appeal to regular folk, those ordinary people who cared less about the size of its hard drive and more about the color of the case. In short, the iMac was marketed (and purchased) as an appliance. People could buy it, bring it home, plug it in, and it just worked. It worked right out of the box, no hassles, and no complicated instructions. That concept is what Jini is all about.

Jini is Sun's solution for creating common, everyday, networking appliances that just "plug and work." I use the word "appliances" instead of "application" because an appliance is a simple piece of technology that everyone can use. An application, on the other hand, is a technology that often requires development of a skill set in order to use it productively. Few people would consider the ability to start a dishwasher a skill set.

Examples of Jini

So what is Jini? Jini is a paradigm for how the service providers and service consumers should interact on a network. It is a layer of architecture that is dependent on a variety of other pieces of architecture as described in Figure 10-1. Sun's current implementation of Jini, Jini 1.0, is written completely in Java, but not all pieces of the Jini architecture must be. The best way to introduce Jini is to describe the technology in use. Sun's promotional literature is full of interesting examples, and examples of the technology aren't hard to dream up. The most common example is that of a printer.

The layers of architecture needed by Jini.

Figure 10-1. The layers of architecture needed by Jini.

ABC Industries recently doubled the size of its office staff from 12 people, to 24. Laura Cogswell, ABC Industries office manager, noticed a situation brewing over the limited printing resource of their single laser printer. The lines were long, and the printer never seemed to stop spitting out paper. It was obvious that ABC Industries simply needed an additional printer.

Laura took a trip down to the local office supply depot where she casually purchased an additional name brand laser printer. Getting it back to the office, Laura took it out of the box, plugged in the power chord, plugged in the networking cable, and switched it on. Within moments, the printer came to life and began printing out reports. The two printers continued to work side by side satisfying the printing needs of ABC Industries. Several members of the office staff were impressed to find the new printer, which they hadn't even known existed there when they clicked the print icon, had automatically printed their reports. How was this possible? Both the printers, and the PCs of ABC Industries, were "Jini Enabled."

What's important about the last example? Well, the first and foremost thing is that Laura is not an IT specialist. She's an office manager. The same person who might arrange to purchase copier paper, a fax machine, or even break-room supplies. She treated the printer problem the same way she might have treated a problem with a refrigerator that was too small, or a coffee machine that didn't make enough coffee. She went out and got another one, plugged it in, and went about her business. She did not have to deploy a set of drivers across the company's computer system (incidentally consisting of UNIX boxen, Macintoshes, and Windows machines), nor did anyone have to set up the new printer on his or her PC.

The rest of the chapter is dedicated to explaining the basics of Jini and a little about how Jini can make this possible. This chapter is, by no means, meant to be an in-depth study of Jini, but it is intended to provide an introduction to the technology and a glimpse at how to get started with it.

Where Did Jini Come From?

Jini is Sun's continued dedication to the founding principles of a language called Oak. Oak was intended as a platform-independent, simple, object-oriented approach to working with "smart appliances," like set top boxes, clocks, microwaves, cell phones, you name it. The problem was that companies in the smart appliance market found themselves spending lots of time and money supporting a myriad of software environments. It seemed that each appliance they developed used different hardware that either had its own software environment or was different enough from the "standard" programming environments to require individual attention.

Oak was intended to help companies deploying different smart appliances concentrate on the appliance itself and not on the overhead of the software environment. Oak was supposed to be write once, run anywhere. Sound familiar? It should because out of Oak came Java. Java gained its popularity because it offered platform independence for application developers on personal computers. You can write an application on a Mac and have the exact same code run on a Windows machine, or a UNIX box. Over time, Java technology has become more robust and mature. Sun never abandoned its roots in the smart appliance market. Instead, they are redefining what people think an appliance is, and bringing the full weight of Java (both its strengths and weaknesses) to bear on the genre. Sun's reference implementation of Jini is a software architecture layer that makes extensive use of Java RMI, which makes possible the concept of a "plug and work" network appliance.

Our Working Jini Example

For the rest of this chapter, we will discuss Jini in terms of a single example, that of a Morse code printer. The Morse code printer itself is a small black box out of which come two cables, one for the Ethernet network, and one for power (some networking solutions use standard 60-Hz ac power lines as their medium rather than Ethernet cables. In that instance, we would only need one cable, the power cord!). Also affixed to the box are two large LEDs, one red and one green. Inside our little network printer is a complete JVM with all of the appropriate Java 2.0 and Jini 1.0 libraries. The printer works by translating messages sent to it into "dashes" and "dots" flashed out by the green LED according to the standard Morse code protocol. In this way, a message consisting of "SOS" would cause the green light to pulse three times quickly, three times slowly, and then three times quickly again.

Morse code was invented over a century ago and wasn't originally conceived of for use as a Jini-based print server (although it does work surprisingly well for this). During the course of a message translation, it is possible that a situation could arise that Morse code is unable to account for. In this case, the red LED will flash, indicating that an error has occurred, and the printer will skip the untranslatable characters and continue on.

In our example we will assume that a standards organization has blessed a particular Java interface to network printers and that this interface is well known. Any entity wishing to use the services of the network printer can do so by utilizing this well-known interface. In our example, an ambitious Java programmer has created a simple client utilizing this interface in the hopes of communicating with our network printer.

The idea, of course, is that someone can walk into a room, plug the "Jini'fied Morse code printer" into the network, go over to a computer that was already running and on the network, activate the client application, and be able to immediately print to the new printer. How could this happen? Let's look at Jini's basic infrastructure.

Basic Jini Concepts: "Discovery, Join, and Lookup Oh My!"

In any Jini community, sometimes called a Djinn, federation, or collectives, there exist three main elements: a service, in our example the Jini'fied Morse code printer; a client that consumes the desired service, like our Java PrintClient application; and a Jini Look Up Service (JLUS) that acts as a coordinator to help the Jini client find the Jini service it is looking for. To see how these three Jini elements interact with each other, let's look to our example.

Server

In the beginning, we plug our patented "Jini'fied Morse code printer" into the network and switch it on. At first, it is unaware that anything else on the network exists. Luckily, Jini has a protocol for getting in touch with other Djinn; it's called Discovery. In our example, Discovery takes the form of a message broadcast to our entire local network asking for any available JLUSs to identify themselves. Each JLUS that hears this broadcast responds by giving our network printer a representative of the JLUS. This representative takes the form of a ServiceRegistrar object. The ServiceRegistrar object works as a proxy to the JLUS. Any work that we want to do with the JLUS we can do by invoking methods on the registrar object.

Figure 10-2 illustrates the concept of Jini Discovery. Here the service finds the local JLUS and obtains an object that functions as an interface.

Jini Discovery.

Figure 10-2. Jini Discovery.

To simplify things, only one JLUS responds to our printer's discovery effort. In reality there could be multiple JLUSs out there, or none at all! (In the last case, we could include in our service implementation code that would create its own JLUS. For now, we just assume that there will be at least one.)

Next we want to tell JLUS all about the great service our Jini'fied Morse code printer offers so that others can take advantage of our printer's availability. This process is referred to as the Join protocol.

In order to join a Djinn, our service has to do two things. First, it must create and provide a proxy object. A proxy, in general, is an agent through which someone or something interacts with another, a go-between. Here, the proxy object is exactly its namesake. It provides the mechanism through which an interested client will communicate with our printer.

The proxy can be any Java object! It could be a full-blown Java GUI application using TCP/IP sockets to "talk" to its server, or it could be an object implementing a simple Java RMI interface. The actual protocol used between the service and the client depends upon the particular implementation of Jini. The interface is defined by whatever the proxy object is. In our example, our printer will use Java RMI to communicate with any interested clients, so the proxy object will be something that implements PrintServiceInterface.

The second thing we should do as part of the Join process is define the set of attributes that our service possesses. These could be anything from defining the name of the printer, or the location of the printer, to expressing all the classes that our proxy implements. Why do this? Well, by providing more information about our service, prospective clients have more information from which to say why our service may or may not be the best for them.

Both pieces of information are packaged up and sent to the JLUS by providing them as arguments in one of the ServiceRegistrar's methods. The JLUS receives these items and stores them for later. This is shown in Figure 10-3.

Jini Join.

Figure 10-3. Jini Join.

In Jini Join, as shown in Figure 10-3, our service first constructs a service item describing itself as JLUS and then gives that service item to the JLUS, thus officially joining the local DJinn.

This is all well and good, but what happens if our Jini'fied Morse code printer gets "accidentally" kicked across the room by a frustrated red–green color-blind user? Well, in its battered, disconnected state it certainly isn't available to print messages anymore. How does the JLUS know that our printer isn't available?

One possible methodology would be for the JLUS to continuously poll the service to see if it's still alive. The greater the frequency of polling, the smaller the period of uncertainty about the status of a service and the quicker the response of the Djinn as a whole in dealing with the loss of a service. This approach, however, puts a great deal of burden on the JLUS and also creates quite a lot of network traffic.

As Figure 10-4 shows, the Jini Service continually tracks time elapsed on its lease and renews each lease cycle before the expiration of the lease.

Jini Lease Renewal.

Figure 10-4. Jini Lease Renewal.

Jini actually deals with this by assigning a "lease time" to a service. In an abstract sense, a Jini lease is an agreement between the JLUS and the Jini Service that guarantees that the Jini Service will be up and available throughout the duration of the lease. Jini puts the burden of renewing the lease squarely on the Jini Service, not on the central JLUS. When the lease time period expires, the JLUS will simply remove the information it has about the subject service from availability to the Djinn. It will not, of its own accord, inform the subject Jini Service that the lease has expired. It is up to the Jini Service to enquire about the lease time granted to it by the JLUS. It is also the responsibility of the Jini Service to track the amount of time transpired and to renew its lease when appropriate.. This is sort of a feed forward system. The Jini Server pushes the lease renewal effort. This is illustrated in Figure 10-4.

Client

Now that our printer is plugged in and participating in the Djinn, let's look at Jini from a service consumer's perspective. Remember that right now, in our example, a service item describing our Jini'fied Morse code printer exists on the JLUS. Also remember that the service item contains a proxy object for our Jini-enabled printer.

For the purposes of demonstrating Jini, we have a client whose only job is to find the Jini'fied Morse code printer and send messages to it. The client doesn't have to be a stand-alone Java program, however. It could just as easily be part of an operating system. Such integration would provide our plug-and-work Jini printing capabilities to every application running on that operating system. For the purposes of our example, we will just consider the stand-alone application case.

When the client is run, it has to find the JLUS, just like the service did. It goes through exactly the same Discovery process the service did, eventually obtaining a ServiceRegistrar object for each JLUS that responds. Again, in our example, only one JLUS exists and responds.

Next, the client has the task of using the JLUS to find the desired service. To do this, the client describes the desired service in any one of several ways. The general method is shown in Figure 10-5. The client provides a template against which the JLUS can stack up potential services and find the one that's a match. This template describes desired server characteristics including attributes that services may have defined, such as their name or location, and interfaces that services must match. Our client could try to look up the service by the "well-understood" PrintServiceInterface it should implement. In our example, however, the client actually looks for a match by name. It fills out the appropriate attribute in the template, and gives it to the JLUS by invoking a method on the ServiceRegistrar object. Again, the service registrar is the interface to the JLUS for the client as well as the service.

The Jini Client constructs a template identifying the desired service, and the JLUS matches this template against all registered service items and returns the proxy object from the matching service item.

Figure 10-5. The Jini Client constructs a template identifying the desired service, and the JLUS matches this template against all registered service items and returns the proxy object from the matching service item.

The JLUS then uses the template to find potential matches, which it does in our example. It then takes the proxy object from the matching service item and returns that as the search result to the client. Had the search been unsuccessful, it would have returned a null instead.

The client receives the proxy and then uses it to communicate directly with the service, leaving the JLUS entirely out of the picture at this point. All future communication between the client and service can now happen directly. In our example, the proxy object given to the client simply uses Java RMI to remotely invoke the print method on our service. We could have chosen as our proxy to have a little program that would simply have streamed data to our print service through a TCP/IP socket.

The user makes the client send the message "SOS" and watches as the dots and dashes get flashed out on the Jini'fied Morse code printer.

It is interesting to note, however, that the JLUS itself follows a similar protocol. Both the client and the service received a ServiceRegistrar object through which they communicated with the JLUS. The ServiceRegistrar object is the JLUS's proxy!

Getting Started with Jini

Before you can delve into making your own Jini services, clients, or even look-up services, you must overcome a few preliminary hurdles before you set up your development environment. These hurdles are neither overwhelming nor are they trivial, however. They often represent an initial challenge to getting started with Jini.

  • Obtain the latest Java SDK for your OS (should be 2.0 or higher).

  • Install the Java SDK.

  • Set up your Java environment as appropriate.

  • Test your Java environment to make sure it works.

  • Obtain the latest Jini SDK (should be 1.0 or higher).

  • Install the Jini SDK.

  • Adjust your classpath as appropriate.

  • Test the Jini installation to make sure it works.

  • Keep the Jini Service and Jini Client environments independent from each other during development. This avoids accidental and invalid dependencies on resources that otherwise wouldn't be shared.

The Java and Jini SDK are available by download directly from Sun (http://java.sun.com). These both include some setup instructions and example applications, which you can run to make sure everything is working. It's just good common sense to make sure you have a working initial setup before you start writing and trying to debug your Jini code.

The last point is not so much a setup step as a development consideration. Keep in mind that, when you develop for Jini, you're developing for a distributed computing environment. When the Jini Service is first brought online, it will be completely unaware of the client. Likewise when the client is first brought up, it is unaware of the service. These two entities make initial contact through a JLUS and, before this, can have no shared resources (data files, code snippits, whatever) that aren't supposed to be explicitly built into both.

The resources mentioned at the end of this chapter can lend considerably more help in dealing with these issues than any discussion possible in the space allotted here. I encourage you to peruse them.

Let's Get to the Code!

The following code only looks at the Jini portions of our examples. The Java code comprising the LED device driver and Morse code translation system are not central to understanding how Jini works and aren't presented here. Those interested in further exploring these pieces of software can obtain the original source online at http://watson2.cs.binghamton.edu/~steflik/jini or on the accompanying CD-ROM.

Implementing the Jini Server

One of the first stipulations we made about our Jini'fied Morse code printer was that it implemented an agreed-upon well-known interface. In practice, this is currently the most difficult part of Jini's promise. There have been ads showing Jini-enabling digital watches to communicate with toasters and other currently brainless home appliances. The problem here (besides understanding why you would want to do such a thing in the first place) is that this means all the people who make digital watches and all the people who make toasters have all agreed on what their equipment's interface will be. At the time of this writing, there are efforts underway to define such well-known interfaces for printers and what not, but they have not come to a conclusion, so for our purposes, we will assert that such a well-known interface already exists and looks like this:

import java.rmi.*;
import java.io.*;

public interface PrintServiceInterface extends Remote  {
    public boolean print(String printString) throws RemoteException;
}

This code is standard Java RMI. It defines an interface called PrintServiceInterface that defines the relationship between the client and the service. The client can invoke the method print on any object implementing this interface and pass to that object a string. The object is allowed to pass back to it a boolean or throw a remote exception, that is, the whole interface. The idea, of course, behind this interface is that the object implementing this interface will be the proxy from our printer and that it will take the string passed to it and flash it out in Morse code. If it is unable to do so, for some reason, our printer should inform the client that there was an error by passing a false back as the return value from the method. Hopefully, however, it will be successful and pass a true value as the return value from the method call.

Let's look at the Service code:

/*----------------------Imports-------------------------------------*/
import java.io.*;
import java.rmi.*;
import java.rmi.server.*;
import com.sun.jini.lookup.*;
import net.jini.core.entry.*;
import net.jini.core.lookup.*;
import net.jini.core.discovery.*;
import net.jini.lookup.entry.*;
import com.sun.jini.lease.*;
/*------------------------------------------------------------------*/

public class PrintService extends UnicastRemoteObject
   implements PrintServiceInterface, ServiceIDListener, Serializable
{
   LightDriver      ld;
   MorseTranslator  mp;
             // Print Service Constructor
             public PrintService() throws RemoteException
             {
                super();
                try
                {
                   ld = new LightDriver();       // create light driver
                   mp = new MorseTranslator(ld); // connect translator to driver
                }
                catch (Exception pse) { pse.printStackTrace();
                   System.exit(0);
                }
             }

             // This method is what satisfies the PrintServiceInterface ,
             // requirement it is through RMI that the client remotely calls
             // this method to deliver a string to the server.
             public boolean print(String printString) throws RemoteException
             {
                try
                {
                   System.out.print("PRINT SERVICE, Printing: " +
                                printString + " Length= " + printString.length());
                   mp.doTranslation(printString);  // morse print message
                   return true;                    // return success
                }
                catch (Exception pe) {pe.printStackTrace();
                         return false;             // return failure
                }
             }

             // This satisfies the ServiceIDListner interface
             public void serviceIDNotify (ServiceID id)
             {
             }

             public static void main(String[] args)
             {
                // this will define our service to the JLUS
                Entry[] serviceAttributes;
                // this object handles discovery & join
                JoinManager joinManager;
                // this becomes our server proxy
                PrintService printService;
                // holds the ID given to us by an JLUS
                ServiceID myID;
                try
                {
                   // This creates a security manager, allowing our service to go
                   // to tp remote sources for code.  This is the same as in Java
                   // RMI. We need this to download the JLUS ServiceRegistrar
                   // Object.
                   System.setSecurityManager(new RMISecurityManager());

                   //Setup to perform the Jini Discovery and Join Process
                   printService = new PrintService();
                   serviceAttributes = new Entry[1];
                   serviceAttributes[0] = new Name("Jinified Morse Code Printer");

                   //Create the JoinManager object, which automatically Discovers
                   // and Joins Jini Look Up Services.
                   joinManager = new JoinManager(
                   printService,             // proxy object
                   serviceAttributes,
                   printService,             // ServiceIDListner
                   new LeaseRenewalManager() // auto-renews leases
                   );
        }
        catch (Exception me)
        {
           System.out.println("PrintService main(): Exception ");
           me.printStackTrace();
        }
    }        // EndMainMethod
}         // End PrintService Class

A Walk Through the PrintService Code

So what's happening here? Well, what do we know about a Jini Service so far? We know that a Jini Service must go through the Discovery process to find a JLUS. We know that once it's found the JLUS it must go through the Join process to make its services available to the Djinn. We also know that as part of the Join process the service has to provide a proxy object and somehow describe itself to the JLUS so that it can be found by interested clients. Does all this happen here? Yes, let's dissect the code a little.

After the initial includes, we can see that our constructor instantiates a LightDriver and a MorseTranslator object. These two objects simply provide the mechanics of making a message flash out in Morse code; as mentioned before, we won't discuss them in detail here. What's important is that our service constructor initialize the mechanics necessary to implement the service. No big deal yet.

The next thing we stumble upon as we traverse the code is the Print method. Aha! This is the method that our well-known interface said we had to have; indeed, if we jump back a couple of lines, we see that our class does in fact implement that interface. Examining the message reveals that basically all our service does is pass the string argument to a method on our MorseTranslator object and then pass back a true. Ideally, we should have some way to determine whether our message was printed or not and pass back a true or false accordingly; nonetheless, we satisfied the requirements of the interface. OK, but that's only useful once you are actively participating in the Djinn. We haven't gotten to that code yet, so now what?

Continuing through the code we bump into an empty little method entitled service-IDNotify. It doesn't do anything, so we must need it to satisfy an interface, and sure enough we see that our class also implements the ServiceIDListener interface. What's that for? This is a piece of the Discovery process as we'll see in a moment.

Next we hit the main of our PrintService class. The next significant thing we do here is instantiate an RMI security manager. This is done so that we will be able to download remote code and use it locally. Why would we do that? We need to do that in order to obtain the ServiceRegistrar object from the JLUS.

Next we have to do a few things to explicitly set up for Join the discovery process. We create our proxy object, an instantiation of our PrintService class, and we create an array of Entry objects, although we only hold a single element. These entry objects are used to describe our service. In the very next line we define a name object, which we set equal to what we're calling our service. In our example, this is what is going to be used to identify our service by the client. We'll talk more about that later. For now we have proxy object, and we have our service description.

Finally, we instantiate a JoinManager. What is this guy? Well for us, it's the lazy coder's (and simplest) way to handle all the service's basic Jini responsibilities. Let's look at this.

It takes four arguments in its constructor: a proxy object, the Service Item, a Service-IDListener, and a LeaseManager. There's a lot of powerful stuff all packed into one class! We pass it our brand-new PrintService object as the proxy, which makes sense. We then hand it our attributes, which we knew we had to do, but for the third object we pass our PrintService again. What is a ServiceIDListener anyway?

Well, in Jini, all services are assigned an ID when they first register with a JLUS. The ID that is given to your service is guaranteed to be completely unique. There shouldn't be another service in the whole world that has that same ID. The idea is that your service is supposed to remember this ID and pass it as all the other JLUSs that it may register with as time goes by. In that way, there is a single common way to recognize a specific service regardless of what JLUS a client finds it on. The method for passing this ID back to a service is through a ServiceIDListener. Each ServiceIDListener has to implement the ServiceIDNotify method, which ours does. In reality, this is simply an RMI callback from the JLUS saying, "This is your serviceID." For our example, we don't do anything with this ID. In general, you will.

That leaves only the final argument. Here we create something called a Lease-RenewalManager. As you can guess by the name, that class is all we need to keep our lease fresh and our service part of Djinn. You can see that in one single object we've delegated Discovery (it downloads and utilizes the ServiceRegistrar object), Join, and lease renewal all away! This works great in our simple case, but there are many other circumstances when you will want a more fine-grain control over how your service participates in a Djinn. For that to happen, you won't be able to just use a JoinManager. You will have to do a little more work. Because this chapter is just intended to get you introduced to Jini, we don't cover that here, but all the references mentioned at the end of this chapter can assist with that.

What's after that? Nothing, our main ends, and our service exits. Right? Almost; it turns out that the Join manager creates some threads to handle all the activities that we've just delegated to it. As long as it's alive and active (which is until the power is pulled), our service will continue to run. If you wind up implementing Discovery, Join, and lease managing yourself, you will also have to include a way to keep your service from just exiting. A simple while(true) {} works pretty well.

Implementing the Jini Client

Our Jini Client is a simple stand-alone Java application that finds our Jini'fied Morse code printer service and prints out "Hello World." Somewhere in this code, we will have to create a template to identify the service we're looking for, perform Discovery to find a JLUS, do a lookup on that JLUS to actually find our service, and finally use that service through its proxy.

/*------------------------ PrintClientImports -------------------*/
import java.io.*;
import java.net.*;
import java.rmi.*;
import com.sun.jini.lookup.*;
import net.jini.core.entry.*;
import net.jini.core.lookup.*;
import net.jini.core.discovery.*;
import net.jini.lookup.entry.*;
import net.jini.discovery.*;
/*-------------------------------------------------------------*/

public class PrintClient implements DiscoveryListener
{
   public ServiceRegistrar[] registrars; // holds list of JLUSs'
                                         // registrars
   public boolean JLUSfound = false;     // will trigger lookup
                                         // process

   // This method is required to implement DiscoveryListener.
   // This is how our client will be notified when a
   //   ServiceRegistrar object is received from a JLUS
   public void discovered(DiscoveryEvent ev)
   {
      // Obtain the array of registrar objects from all available
      // LUSs'
      registrars   = ev.getRegistrars();
      JLUSfound = true;
   }
   // Used to deal with the situation when a JLUS is discarded by
   // our discovery object.  We don't use it here, but more
   // robust code would.
   public void discarded(DiscoveryEvent ev)
   {
      //just satisfying DiscoveryListener interface
   }

   public static void main(String[] args)
   {
       LookupDiscovery discovery;   // discovers available LUS
       ServiceRegistrar ourreg;     // holds the test registrar
       LookupLocator lookup;        // used to lookup service
       Entry[] desiredattribs;      // attributes desired in service
       // a template the service must fill
       ServiceTemplate desiredtemplate;

       Object serviceobject;           //result of the search
       PrintServiceInterface printer;  //handle to hold found service
       PrintClient    printclient;     //This holds the instantiation
                                       //of our client

       try
       {
           // Set the security manager for handling downloaded code:
           // needed to obtain the ServiceRegistrars and the servers
           // proxy object
           System.setSecurityManager( newRMISecurityManager() );

           // Perform Discovery Process for all JLUS containing the
           // public group
           discovery = newLookupDiscovery(new String[] {""});

           // add a DiscoveryListener to recieve the ServiceRegistrar
           // objects our LookupDiscovery finds.
           printclient = new PrintClient();
           discovery.addDiscoveryListener(printclient);

           // create a description for the service we are looking for
           // must have the name PrintService…
           desiredattribs = new Entry[1];
           desiredattribs[0] = new Name("Jini'fied Morse Code
                               Printer");
           Class[] clArray = new Class {PrintServiceInterface};
           desiredtemplate = new ServiceTemplate(null, clArray,
                             desiredattribs);
           while (true)
           {                                        // begin while
              if (printclient.JLUSfound)
              {                                     // begin if found
                 printclient.JLUSfound = false;     // reset flag
                 System.out.println("PrintClient: " +
                                    printclient.registrars.length +
                                    "Jini Lookup Service(s) Found.");

                 // Go through the complete array of registrar objects
                 // lookup on each one to find the PrintService
                 // if found print out "Hello World!"
                 for (int i=0; i<printclient.registrars.length; i++)
                 {  //begin for
                    lookup = printclient.registrars[i].getLocator();
                    System.out.println(
                    "PrintClient: LUS found at " +
                    lookup.getHost() + " on port " +
                    lookup.getPort());

                    //perform the actual Jini lookup
                    serviceobject = printclient.registrars[i].lookup
                                    (desiredtemplate);

                    if (serviceobject instanceof PrintServiceInterface)
                    {
                      System.out.println("Found a match!
" +
                      "Calling print method: Hello World!
");
                      printer = (PrintServiceInterface) serviceobject;
                      if (!printer.print("Hello World!
"))
                      {
                        System.out.println("PrintClient: Print Failed!");
                      }
                      else System.out.println("PrintClient: Print
                        Successful!");
                    }
                    else System.out.println("No match Found. :-<");
               }    //end for
            }  //end if
         }  //end while
      }
      catch (Exception me)
      {
          me.printStackTrace();
      }
   }  // end main
}  // end client

A Walk Through the PrintClient Code

Unlike our PrintServer class, we won't use a single class to handle all our responsibilities. Therefore, it will be the responsibility of our PrintClient class to handle the Discovery and Lookup process directly as well as have the main method. The first hint at how we are going to do this occurs as we see our PrintClient implementing the DiscoveryListener interface. Much as in AWT programming, by implementing this interface, we can use an instance of our PrintClient class to register itself as an event handler. The events we will handle are the two methods of the DiscoveryListener interface, Discovered and Discarded. Discovered is called when a JLUS is found and its ServiceRegistrar object is obtained. Discarded is called when a JLUS we had been aware of, becomes unavailable to us. Since we don't care too terribly much about the second situation in this example, we don't do anything in the Discarded method. On the other hand, the Discovered method is going to be the way our PrintClient will get its hands on the ServiceRegistrar object, so we will want to do something here, and we do.

We obtain the array of ServiceRegistrar objects from the method's only parameter and store it for later use. The next, and last, thing we do here is to set a flag. As you might guess, this will signal other mechanics to do the bulk of the work later.

In our PrintClients main method, we start off by creating placeholders to handle the other things we're going to need to get through the Discovery and Lookup process. Just like the service, we have to extend the security model in order to run remote code in our local VM. Unlike the service, however, we will not just be obtaining the ServiceRegistrar object, but also the print service's proxy object.

Next, we actually go through and perform Jini Discovery. This is done by simply creating a LookupDiscovery object. Just creating it will send it off to find all the JLUSs it can. We don't have to tell it any more information. During the instantiation of this object, however, a new Jini concept called groups comes into play.

Basically, groups are a way to logically organize services together in a Djinn. Without going into great detail, a company may be a single large Djinn but may also decide to define different groups within that Djinn, like the "Engineering Services" group or the "Cafeteria Services" group. Groups are just a way for Jini Services to further differentiate and organize themselves within a Djinn. For our Discovery, we pass an empty string as the argument. This says that we want to look in the public group, which is the default group all services join.

Since we sent the LookupDiscovery object to go out and discover the available JLUSs, we must have a way for it to give us all the ServiceRegistrars it discovers. This is where our PrintClients DiscoveryListener interface comes in handy. We create an instance of our PrintClient and pass it to our LookupDiscovery object as the event handler for Discovered and Discarded events. Now whenever our LookupDiscovery object finds new JLUSs, it will notify us by calling our Discovered method.

Next, we define the service we want to look for. Here again, we create an array of Entry objects with only 1 element. We make this element a Name and initialize it to our "Jini'fied Morse Code Printer."

Finally we get to the driving while loop of our PrintClient class. Here we will loop forever. Each time we loop, we will check the JLUSfound flag kept by our PrintClient to see if there are any targets to perform Lookup on. While there aren't any, we'll loop forever. When we get one, we fall through the if statement and begin to process it.

Now, in a real Jini environment, there just may be multiple lookup services. That's why, when we ask the DiscoveryEvent object back in the Discovered method for a ServiceRegistrar, it actually hands us an array of them. We can query this array, just like any other in Java, and find out exactly how many registrars we found. The answer we get here will depend on how many JLUSs you started up when you went to run our little example, probably one. Our client is a little robust at least and will handle the case of N JLUSs.

We index through the array, just as you would any other. On each element (JLUS ServiceRegistrar object), we perform Jini Lookup. To do this, we pass the ServiceTemplate we made to identify our service by its name as the argument in the registrar's aptly named lookup method. The result will either be NULL, meaning no match, or the proxy object of the first service that matched the template.

We check to make sure that the proxy object implements our well-known PrintServiceInterface by using a bit of RTTI, and if it does, we send "Hello World!" out to the printer. If the proxy object doesn't implement that interface (who knows why you would have a service out there calling itself Jini'fied Morse code printer that didn't implement our interface, but you may), we say that we couldn't find a match.

After we have exhausted all the ServiceRegistrar possibilities, we fall out of the four loop and eventually out of the if where we check the JLUSfound flag. Since we reset that flag, we will loop forever until the user kills the process, or our LookupDiscovery Object finds a new JLUS.

Running the Jini Server

Because standard RMI stubs and skeletons are being utilized, this service requires a Web server running to distribute the stub file to anyone who needs it. Don't forget to set the RMI codebase parameter to point to the URL where the stubs are served from. If this is confusing, it may be helpful to go back and refresh your knowledge of Java RMI. In effect, the PrintService_Stub.class file is the PrintServices proxy object.

As an additional note, we will need a security policy file that allows the service enough privilege to be able to download and run code given to it by the JLUS.

Required Files in Service Directory for running:

  • LightDriver.class—Light Driver for Linux System

  • MorseTranslator.class—Actual Morse code translator

  • MorseTable.dat—Table used by MorseTranslator

  • PrintService.class—The Jini PrintService

  • PrintServiceInterface.class—The well-known interface

  • PrintService_Skel.class—The server-side part of the RMI stubs

  • policy.all—The Java RMI security policy

Required Files to be served by the service's webserver:

  • PrintService_Stub.class—The client-side part of the RMI stubs

To run the server, you must have at least one JLUS running already on your local network, such as Reggie the JLUS that comes with the Jini SDK.

The following commands will start up the Web server to serve the Stub file and run the service.

In a UNIX environment:

echo Starting Server Codebase Webserver
java -jar /path_to_Jini/jini1_0/lib/tools.jar -dir
/path_to_service_codebase_directory/service-codebase -verbose
-port 8001 &

echo Starting Service
java -Djava.security.policy=policy.all
-Djava.rmi.server.codebase=http://192.168.1.4:8001/ PrintService

You would replace the text in italics with the appropriate path for your system.

In a Windows environment:

echo Starting Server Codebase Webserver
java-jar Drive:path_to_Jinijini1_0lib	ools. jar -dir
path_to_server_codebase_directoryservice-codebase -verbose
-port 8001 &

echo Starting Service
java -Djava.security.policy=policy.all
-Djava.rmi.server.codebase=http://192.168.1.4:8001/ PrintService

You would replace the text in italics with the appropriate path for your system.

Assuming the appropriate classpath setup, the server should then run. As it is written, the server will execute and remain silent. It would be an easy task to include some code to inform the user that it is up and running. Because the serviceIDNotify method gets called when the JLUS first assigns an ID to the service, a statement could be placed here indicating to the user that a successful join had occurred.

Running the Jini Client

Running the client is a little simpler. Again, we assume that there is a JLUS already running on our local network. Here also, a policy file that allows the client enough privilege to download and execute code from both the service and the JLUS is needed.

Required Files in Service Directory for running:

  • PrintClient.class—Our PrintClient class

  • PrintServiceInterface.class—The well-known interface

  • policy.all—The Java RMI security policy

The following commands will run the client.

In a UNIX or Windows environment:

echo Starting the Client
java -Djava.security.policy=policy.all PrintClient

Again, this assumes that your classpaths are set up properly for your Java and Jini installations.

Good References to Get You Started

As mentioned earlier, this chapter is really only an introduction to Jini. I highly recommend purchasing Core Jini by W. Keith Edwards, as both an excellent introduction and a comprehensive text on this technology. In addition, several kind individuals have made tutorials and examples of their Jini efforts available online. Much of this is equally helpful in getting your own Jini projects up and running. Because the Web is ever changing, I don't include the URLs, but I do encourage people to use their favorite search engine to find and visit these resources:

  • Noel Enete's Jini and Java Nuggets

  • Jan Newmarch's Guide to JINI Technologies

  • Bill Venner's Jini resources at Artima

  • All the material available through Sun on Jini, from the FAQ to the specifications.

Concerning the actual Jini'fied Morse code printer, an actual Morse code printer was built at Binghamton University in 1999. The prototype system utilized a simple plastic box containing two LEDs wired to a parallel cable. This parallel cable was connected to a Red Hat Linux box running on an old 486 class PC. The PC acted as the "embedded smarts" of the Jini device, implementing the full Java 2.0 SDK and Jini 1.0 SDK. Though the prototype system was quite large, and thus not "embedded" by any standard, its use as a model was still valid. The operating system, Java and Jini Runtimes, Ethernet, and parallel ports all could have easily been implemented on one of several credit-card-sized PC systems commonly available from embedded systems manufacturers.

It is worth mentioning that Linux was the operating system of choice for this application for a couple of reasons. Linux is a freely available, full-fledged UNIX clone. Linux has been used in a variety of embedded applications. Also, because of the security restraints and cross platform features of Java, it wasn't possible to access the printer hardware directly as an address in memory, as you might in C or C++. However, in Linux, as in other UNIX systems, hardware is accessible as simple files in the /dev file structure. In this way, Java code could easily be written to twiddle the output bits on the parallel port (/dev/lp1) in exactly the same manner as writing to a file! This made the entire task of writing a "device driver" in Java trivial.

Summary

In summary, most basic Djinn consist of a JLUS, a service provider, and a service consumer. Both the service provider and consumer must use the JLUS as a middleman to coordinate initial contact between them. Once contact has been made, however, they do not need to use the JLUS to communicate with each other. Direct service to client communication is handled through the services proxy object, which is given to the JLUS by the service, and obtained by the client from the JLUS. This client can be anything from a full-fledged, stand-alone Java GUI to a simple RMI client that implements a "well-known interface." Jini's mechanism for removing services that fail is through leasing. It is the responsibility of the service provider to maintain its lease with the JLUS. The JLUS will not usually inform the service that its lease has expired. These are the basic concepts of Jini.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset