TotSP HOWTO: Jini Programming in Java

It has been a while since we had a good TotSP
original tutorial up
here, so lets take a look at my lasted little
project: Working with
Jini from Sun.

Getting started with Jini

If you are like me, and used to be a big reader
of Wired
magazine, you might remember when Bill Joy at Sun
first announced
Jini. It was going to be the saviour of the world.
You frigde could
talk to your PC and all that. Well, it has not
taken off as
explosively as Bill might have hoped, maybe
because the Embedded Java
market is just getting traction, but it is still
very cool.
Jini includes severa
technologies for
distrubuted computing envrionments. The first and
foremost is its
IPMulticast service announcement and registry
system. This is what we
are going to play with today. This is a system by
which RMI objects (
or really, any kind of remote service front end
object ) can be
passed out to the network and found by any other
machine on the
network. Here we are going to use vanilla RMI
Remote Stubs, but they
could be EJB Home Stubs, or anything else you
want.

Environment

All
examples here are given
on my RedHat 7.1. Some massaging may be in order.
You need to get:


  1. JDK 1.3.0 ( linux
    version is available at http://www.blackdown.org
    )
  2. JINI
    1.1 Development
    Kit

I just
installed each of
these in /opt on my box.
Next, you
are going to need
to create a Java Security Policy file. I won't go
into detail about
how this works ( check the Java Security book from
O'Reilly ), but
you need this written out to a file ( all.policy
):

grant{
        java.security.AllPermission;
}
Next, you're going to need a log dirctory somewhere. I don't care, just pick one. Ok, lets look at the services needed to make this happen:
  1. HTTP Server to serve you jar filesWe are just going to use the one that comes with the Jini distro. This is for the dynamic executable downloads, but we aren't covering that here yet.
  2. rmidThis comes with the JDK distribution. It is a service for your computer that can automatically create instance of server objects when something requests it. We won't use this directly, but Reggie needs it.
  3. ReggieThis is the Jini lookup registrar. You can run all of these on your network you like, but you need at least one. This guy holds the serialized "Service" objects, and assigns unique service Ids on the network.

Starting up your environment:

  1. HTTP:java -jar $JINI_HOME/lib/tools.jar -port 8080 -dir $JINI_HOME/lib/ -trees \verbose
  2. rmid:rmid -log $YOUR_LOG_DIR/rmid -J-Djava.security.policy=all.policy
  3. Reggie:java -jar -Djava.security.policy=all.policy $JINI_HOME/lib/reggie.jar http://[Host Where HTTP Runs]/reggie-dl.jar all.policy $YOUR_LOG_DIR/reggie totsp -Dnet.jini.discovery.inderface=[IP Address of adapter to muticast]

Troubleshooting:

On some Linux distros, IPMulticast may be in the kernel, but not on. To turn it on use: "ifconfig eth0 multicast on; route add -net 224.0.0.0 netmask 224.0.0.0 eth0". Also, make sure the machine names for your box all resolve properly.

Checking your config:

The Jini distro comes with a handy tool for browsing you Jini network. You can lauch it by running:java - cp .:$JINI_HOME/lib/jini-examples.jar -Djava.security.policy=all.policy -Djava.rmi.server.codebase=http://[HTTP_Host]/jini-examples-dl.jar com.sun.jini.example.browser.Browser -admin One this loads, select your Reggie host from the "registrar" drop down, and you should see a service registered as net.jini.core.lookup.ServiceRegistrar. If you don't, something went horribly, horribly wrong.

Code:

Next, our code. We are going to put together an RMI server object, that will register itself with Reggie, and a client that can fetch the remote stubs and call our one business method. Remember in your project, you will need to make sure everything passed around over the wire implements java.io.Serlializable. There are some tricks to serialization, but you should see the tutorials at java.sun.com for more info on this. First, our interface. This is the interface we will use when working with the remote objects:
import java.rmi.Remote;
import java.rmi.RemoteException;

public interface HelloWorld extends Remote {
 public String helloWorld() throws RemoteException;
}
Nothing too complicated there. Next, lets look at our server object:
import java.rmi.*;
import java.rmi.server.*;
import net.jini.lookup.*;
import net.jini.lookup.entry.*;
import net.jini.core.entry.*;
import net.jini.discovery.*;
import net.jini.core.lookup.*;
import net.jini.lease.*;
import java.util.Calendar;


public class HelloWorldImpl extends
UnicastRemoteObject implements HelloWorld,
ServiceIDListener {
   
   /* This will control the maximum number of
instances of HelloWorldImpl to create. */
   static final int MAX_INSTANCES = 3;

   /* This is an array containing the names of the
Jini groups to register with. */
   static final String [] GROUPS =  {
"cooper" };

   /* This is a reference to the remote stub for
this object */
   RemoteObject remote = null;

   
   LookupDiscoveryManager ldm = null;
   JoinManager jm = null;
   LeaseRenewalManager lrm = null;
   protected JoinManager myJM = null;
   
   /* The number of instances active */
   static int count = 0;

   /* this instance number */
   int id = 0;
   
   /* this instances Jini service id */
   String serviceId = "";


   public HelloWorldImpl() throws RemoteException {
      count++;
      this.id=count;
      System.out.println("Instance created:
"+id );

      //This sets up the name for this service in
the Jini registry
      Entry[] attributes = new Entry[1];
      attributes[0] = new
Name("HelloWorld");


      //This is where we actually register with
the Jini registry
      try{
          ldm = new LookupDiscoveryManager( GROUPS,
                null /* not using UnicastLocators */,
                null /* not using
DiscoveryListener */ );
          jm = new JoinManager( this,
           attributes,
           this /* for the ServiceIDListener */,
           ldm,
           lrm );

      }
      catch(Exception e){
          e.printStackTrace();
          System.exit(1);
      }

   }

   /* This is own only "business logic"
method. */
   public String helloWorld() throws RemoteException {
      return "Hello from "+id;
   }

   public static void main( String[] args ){
      try{
         if(System.getSecurityManager() == null ){
            System.setSecurityManager( new
RMISecurityManager() );
         }
    
       for( int i = 0; i < MAX_INSTANCES ; i++ ){
          HelloWorld server = new HelloWorldImpl();
 
       //This is optional. It just binds the
instance to a "normal" RMIRegistry
service as well.
      
//Naming.rebind("HelloWorld-"+server.id,
server);
       }

       System.out.println("main() done.");
     }
     catch(Exception e){
       e.printStackTrace();
     }
   }
   /* This is the mehod required by
ServiceIDListener. This is how Reggie tells up
what our JINI service ID is. */
   public void serviceIDNotify(ServiceID serviceId) {
      System.out.println("instance " +
id + " has been assigned service ID: " +
serviceId.toString());
      this.serviceId = serviceId.toString();
   } 
   
}
This is just a standard RMI server object with a couple of important differences. First in the ServiceIDListener interface. This is just an event listener that the Jini APIs will use to tell us what our Jini Service ID is when it get registered. Second is the stuff in the constructor. Once again, I wont go into the details, but this represents how we bind our Remote stub to the Jini registries. Pay attention to the GROUPS array. You can bind a single service to as many groups as you want. These are just little organizers for your Jini network. If you have a lot of registries, groups can keep you from having to search every registrar for the service you need in your client application. Ok, so now that you have this stuff, you can compile it. Also, you will need to compile the stub implementation for the HelloWorldImpl class, if you haven't done this before, its easy, just run: "rmic -v1.2 HelloWorldImpl" after you javac'd it. The v1.2 switch just tells rmic to use the JDK 1.2+ RMI framework. It is cleaner in that it doesn't require skeletons and all that trash. Everything is handled through reflection for those needs. After you have run rmic, you can start your "HelloWorld" service by running "java -Djava.security.policy=all.policy HelloWorldImpl". Check the Jini Browser. You should now see three items registered called "HelloWorld". Wasn't that fun. Finally, we need a client to call our service. Here's some source:
import net.jini.discovery.*;
import net.jini.core.lookup.*;
import java.io.IOException;
import java.rmi.RemoteException;
import javax.rmi.*;
import java.rmi.RMISecurityManager;
import java.rmi.server.UnicastRemoteObject;
import java.rmi.MarshalledObject;
import net.jini.core.event.*;
import net.jini.core.lease.*;
import net.jini.lease.LeaseRenewalManager;

import net.jini.lookup.entry.Name;
import net.jini.core.entry.Entry;

/* Normally you would have a discovery listener
implemented separate from
  your main run thread... */
public class HelloWorldClient implements
DiscoveryListener {

   protected ServiceRegistrar[] registrars;
   static final int MAX_MATCHES = 5;
   
   public HelloWorldClient() throws RemoteException{

if (System.getSecurityManager() == null) {
    System.setSecurityManager(new
RMISecurityManager());
}
       LookupDiscovery discover = null;
       
       try {
           discover = new
LookupDiscovery(LookupDiscovery.NO_GROUPS);
           discover.addDiscoveryListener(this);
    String[] groups = { "cooper" };
           discover.setGroups(groups);
} catch(IOException e) {
           System.err.println(e.toString());
    e.printStackTrace();
    System.exit(1);
}


   }


   public synchronized void
discovered(DiscoveryEvent evt) {
         registrars = evt.getRegistrars();
         doLookupWork();
   }

   
   private void doLookupWork() {

    String[] groups;
    String msg = null;
    if(registrars.length > 0){
        msg = "";
       
System.out.println("------------------------------------------");
        System.out.println("Registrar: "
+ registrars[0].getServiceID());
        try {
     groups = registrars[0].getGroups();
     if (groups.length > 0)
  for (int o=0; o   
   msg += groups[o] + " ";   } 
           System.out.println("Groups
Supported: " + msg);        } 
catch (Exception e){      
e.printStackTrace();       System.exit(1);
  }      Entry [] attributes = new
Entry[1];  attributes[0] = new
Name("HelloWorld"); 
ServiceTemplate st = new ServiceTemplate( null,
null, attributes );  for(int i = 0; i <
registrars.length; i++ ){      try{   
  //If you only want a single instance of the
service, you can use   // Object object =
registrars[i].lookup( st );   //Without the
max matches.   ServiceMatches sm =
registrars[i].lookup( st, MAX_MATCHES );  
for( int j=0; j < sm.items.length; j++ ){ 
     Object object = sm.items[j].service;    
  System.out.println( object );      
HelloWorld helloWorld = (HelloWorld)
PortableRemoteObject.narrow( object,
HelloWorld.class );   System.out.println(
helloWorld.helloWorld() );   }      }
     catch(Exception ee){  
System.out.println("in lookup");   
ee.printStackTrace();      }       
}     }// of if length > 0
   }

   public void discarded(DiscoveryEvent evt) {

   }

   public static void main( String[] args ){
try{
    HelloWorldClient thisClient = new
HelloWorldClient();
    synchronized( thisClient ){
 thisClient.wait(0);
    }
}
catch(Exception e){
    e.printStackTrace();
    System.exit(1);
}
   }
 
}
Now here, what we do, is find all instanced of our HelloWorld service- remember, we started three, and call the helloWorld() method on them, so they say hi to us. Notice the ServiceTemplate we search for. This can be used to match many many Entry's for a particular service, though we are only using name here. Also, see the difference between registrar.lookup() with and without a "max_matches". Without a max-matches, it just returns a "service" object for an available service. Notice the time delay between taking down your HelloWorldImpl service and it being removed from the Jini registry. Your client code should generally test a service object it gets back and make sure its Ok. Additionally, you can use the LeaseRenewalManager from your service to control the time delay, but remember, short leases mean network chatter. Thats it for this episode. Check the Jini site at java.sun.com for API docs and some lame tutorials. I also recommend the redbook, Professional Jini by Sing Li from Wrox. The guy is wordy, but the book convers all the stuff you need to know in a moderately digestable order.   jini @ java.sun.com