SDSU CS 696 Emerging Technologies: Java Distributed Computing
Spring Semester, 1999
Jini Distributed Events
Previous    Lecture Notes Index    Next    
© 1999, All Rights Reserved, SDSU & Roger Whitney
San Diego State University -- This page last updated 13-Apr-99

Contents of Doc 25, Jini Distributed Events


References


Jini™ Distributed Event Specification Version 1.0 January 25, 1999
Available at: http://www.sun.com/jini/specs/index.html

Jini API, Local on-line version at: http://www-rohan.sdsu.edu/doc/jini/doc/api/index.html


Doc 25, Jini Distributed Events Slide # 2

Distributed Events


Jini’s distributed events system allows code in one VM to register to be notified of an event in another VM.

The objects involved in a distributed event system are


Differences from single VM events


Remote event listening is leased. This allows the event generator to remove stale listeners from its notification list. An event generator uniquely labels (with a long) all different types of events it generates. An event generator provides sequence numbers for each event it generates. The number must increase. When two events of the same type are generated, the one generated later must have a larger sequence number. Ideally the sequence numbers of two consecutive events (of the same type) differ by one.

Types of Notification

One time notification
After the listener registers for an event, it is notified of the next event of the proper type generated. The listener is then removed from the list of listeners for that type of event. If the listener wants another notification, it must register again.
Repeatable
The listener is not removed from the list of listeners after being notified of an event.


Doc 25, Jini Distributed Events Slide # 3

Classes & Interfaces


RemoteEventListener
RemoteEvent
EventRegistration

Interface net.jini.core.event.RemoteEventListener


A Remote event listener must be registered with the event generator. The method used to register for an event is not specified by Jini. To be registered for a remote event an object must implement the RemoteEventListener interface. This interface is a Remote interface. This means that the Remote event listener must have a rmi stub (generated by rmic) and be exported (either explicitly or by being a subclass of java.rmi.server.UnicastRemoteObject or java.rmi.activation.Activatable). The event generator notifies the listener of an event by calling the notify method. If the listener does not recognize the type of event sent to it, the listener can throw the UnknownEventException. The event generator should then remove the listener from the list of listeners for that type of event.

public void notify( RemoteEvent theEvent )
   throws UnknownEventException, java.rmi.RemoteException

Doc 25, Jini Distributed Events Slide # 4

net.jini.core.event.RemoteEvent


A RemoteEvent object contains the following:

The later data, the handback, is given to the event generator when an object is registered as an event listener. It can be used to store data the listener needs to process the event. It relieves the listener from having to store this data for each event it registers for. This could be handy when the object that deals with the event is activatable. The handback is of type java.rmi.MarshalledObject. The event generator does not have to support the handback object. If the listener does not use the handback object, null can be supplied when registering with the event generator.

Constructor
RemoteEvent(java.lang.Object source, 
   long eventID,
   long seqNum,
   java.rmi.MarshalledObject handback)
Methods
getID() 
getRegistrationObject() 
getSequenceNumber() 
getSource()

Doc 25, Jini Distributed Events Slide # 5

net.jini.core.event.EventRegistration


When a listener is registered with an event generator, the generator is to return an EventRegistration object. This object contains the following information:

Event registration lease

Event type

Event source

Current event type sequence number

Constructor
EventRegistration(long eventID, 
   java.lang.Object source, 
   Lease lease, 
   long seqNum)
Methods
getID() 
getLease() 
getSequenceNumber() 
getSource()

Doc 25, Jini Distributed Events Slide # 6

Distributed Event Example

In this example a single counter object acts as a server. It supports one type of remote event: decrease. When the decrease method of the counter is called all listeners are notified and removed from the listener list.

Classes in Example
RemoteCounter
Remote interface for counters.
RemoteCounterImpl
A counter object that supports leased distributed events. Implements RemoteCounter and Landlord interfaces. Needs a stub class generated.
LeasedEventData
Used by RemoteCounterImpl to store information about remote event listeners and their leases.
RegisterCounter
Registers a RemoteCounterImpl object as a Jini service
RemoteCounterClient
Calls the increase() and decrease() methods in the RemoteCounter Jini service
CounterEventClient
A remote event listener that listens for a counter object to generate the decrease event. Needs a stub class generated.

When I generated the stub classes for this example I had to explicitly give rmic a path to the required Jini jar files, even though those jar files are standard extension on the jdk used to run the examples. An example of generating a stub:

rmic -classpath .:/export/home/whitney/lib/jini1_0/lib/jini-core.jar:/export/home/whitney/lib/jini1_0/lib/sun-util.jar:/export/home/whitney/lib/jdk1.2/jre/lib/rt.jar RemoteCounterImpl

Doc 25, Jini Distributed Events Slide # 7
Example Source Code

RemoteCounter

package whitney.jini.examples.event;
import java.rmi.MarshalledObject;
import java.rmi.Remote;
import java.rmi.RemoteException;
import net.jini.core.event.EventRegistration;
import net.jini.core.event.RemoteEventListener;
import net.jini.core.lease.LeaseDeniedException;
public interface RemoteCounter extends Remote
   {
   public abstract int increase() throws RemoteException;
   public abstract int decrease() throws RemoteException;
   public abstract int count()  throws RemoteException;
   public EventRegistration addDecreaseListener( RemoteEventListener 
      decreaseListener, MarshalledObject handbackObject ) 
      throws RemoteException, LeaseDeniedException;
   }

Doc 25, Jini Distributed Events Slide # 8

RemoteCounterImpl

package whitney.jini.examples.event;
import com.sun.jini.lease.landlord.Landlord;
import com.sun.jini.lease.landlord.LandlordLease.Factory;
import com.sun.jini.lease.landlord.LandlordLease;
import com.sun.jini.lease.landlord.LandlordLeaseFactory;
import com.sun.jini.lease.landlord.LeasedResource;
import java.rmi.MarshalledObject;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import net.jini.core.event.EventRegistration;
import net.jini.core.event.RemoteEventListener;
import net.jini.core.event.UnknownEventException;
import net.jini.core.lease.Lease;
import net.jini.core.lease.LeaseDeniedException;
public class RemoteCounterImpl extends Thread 
   implements RemoteCounter, Landlord {
   private static final  long DECREASE_ID = 0;
   
   private long decreaseEventSequence = 0;
   
   private final long maxLeaseDuration = 1000 * 60 * 2; //2 minutes
   private int leasesCreated = 0;
   LandlordLeaseFactory leaseFactory = new LandlordLease.Factory();
   HashMap activeLeases = new HashMap();
   private int count;
   
   public RemoteCounterImpl(  ) { this( 0); }
   
   public RemoteCounterImpl(  int initialCount) {
      count = initialCount;
   }
   
   public String toString() { return "Counter("  + count + ")"; }
      
   // Count interface methods
   public int count() { return count; }
   public int increase() {
      printMessage(  "Increase called" );
      return ++count;
   }
   public int decrease() {
      decreaseEventSequence++;
      printMessage(  "Decrease called" );
      notifyDecreaseListeners();
      return --count;
   }

Doc 25, Jini Distributed Events Slide # 9

RemoteCounterImpl – Counter Methods


   public EventRegistration addDecreaseListener( RemoteEventListener 
      decreaseListener, MarshalledObject handbackObject ) 
      throws RemoteException, LeaseDeniedException {
      printMessage(  "Register listener" );
      Object cookie = newLeaseCookie();
      long leaseExpiration = maxLeaseDuration + now();
      Lease eventLease = 
         leaseFactory.newLease( cookie, this, leaseExpiration );
      printMessage(  "Create EventRegistration" );
      EventRegistration decreaseReg = new EventRegistration( 
         DECREASE_ID, this, eventLease, decreaseEventSequence);
         
      LeasedEventData eventData = 
         new LeasedEventData( leaseExpiration,decreaseListener, handbackObject );
      activeLeases.put( cookie, eventData );
      printMessage(  "End register listener" );
      return decreaseReg;
      }

Doc 25, Jini Distributed Events Slide # 10

RemoteCounterImpl – Notify Listeners


   private void notifyDecreaseListeners() {
      printMessage(  "Start notify of event" );
      long now = now();
      Map clonedMap;
      synchronized ( activeLeases ) {
         clonedMap = (Map) activeLeases.clone();
         activeLeases.clear();
      }
      printMessage(  "Cloned map" );
      Iterator listeners = clonedMap.values().iterator();
      while ( listeners.hasNext() ) {
         LeasedEventData eventInfo = (LeasedEventData) listeners.next();
         
         try {
            printMessage(  "Send notify" );
            if ( eventInfo.getExpiration() > now )
               eventInfo.notifyListener(DECREASE_ID, this, decreaseEventSequence );
         } catch ( UnknownEventException doesntKnow ) {
            printMessage("UnknownEventException" + doesntKnow.getMessage());
         } catch ( RemoteException rmiProblem) {
            printMessage(  "RemoteException" + rmiProblem.getMessage() );
         }
      } 
   }

Doc 25, Jini Distributed Events Slide # 11

RemoteCounterImpl – Landlord Methods


   // Landlord methods
   public  void cancel(java.lang.Object cookie) {
      printMessage( "Cancel called" );
      if ( !activeLeases.containsKey( cookie ) )
         return;
      
      synchronized ( activeLeases ) {
         activeLeases.remove( cookie );
      }
   }
   public void cancelAll(java.lang.Object[] cookie) {
      for ( int k = 0; k < cookie.length; k++ )
         cancel( cookie[k] );
   }
   public long renew(java.lang.Object cookie, long extension) 
      throws LeaseDeniedException {
      printMessage( "Renew called " + extension );
      synchronized ( activeLeases ) {
         if ( !activeLeases.containsKey( cookie ) )
            throw new LeaseDeniedException( "Requested resourse does not exist");
         
         long newDuration = Math.min( extension, maxLeaseDuration );
         
         LeasedEventData eventLease = 
            (LeasedEventData) activeLeases.get( cookie);
         eventLease.setDuration( newDuration  );
         return newDuration;
         }
      }

Doc 25, Jini Distributed Events Slide # 12
RemoteCounterImpl – Landlord Methods Continued

   public Landlord.RenewResults renewAll(java.lang.Object[] cookie, 
         long[] extension) {
      long renewalGranted[] = new long[ cookie.length ];
      Exception renewalDenied[] = new Exception[ cookie.length ];
      
      for ( int k = 0; k < cookie.length; k++ )
         try {
            renewalGranted[k] = renew( cookie[k], extension[k] );
            renewalDenied[k] = null;
         } catch (LeaseDeniedException deny ) {
            renewalGranted[k] = -1;
            renewalDenied[k] = deny;
         }
      return new Landlord.RenewResults(renewalGranted, renewalDenied );
   }

Doc 25, Jini Distributed Events Slide # 13

RemoteCounterImpl – Check for expired leases

   public void run() {
      printMessage ( "Run called" );
      try {
         while (true) {
            Thread.sleep( maxLeaseDuration/2 );
            printMessage ( "CheckAllLeases" );
            checkAllLeases();
         }
      } catch (InterruptedException threadInterupted ) { }
   }
   
   private void checkAllLeases() {
      synchronized ( activeLeases ) {
         Iterator counterKeys = activeLeases.keySet().iterator();
         while (counterKeys.hasNext() ) {
            Object key = counterKeys.next();
            if ( hasLeaseExpired( key ) ) {
               counterKeys.remove();
               printMessage( "lease expired " + key  );
            }
         }
      }
   }
   private boolean hasLeaseExpired( Object cookie ) {
      LeasedEventData aCounter = (LeasedEventData) activeLeases.get( cookie);
      
      long expiration =  aCounter.getExpiration( );
      printMessage( "lease check " + cookie  + "  duration " + (expiration - now()) );
      if ( now() >= expiration  )
         return true;
      else
         return false;
      }

Doc 25, Jini Distributed Events Slide # 14

RemoteCounterImpl – Helper methods


   private Integer newLeaseCookie() {
      return new Integer( leasesCreated++);
      }
   private long now() {
      return System.currentTimeMillis();
   }
   private void printMessage( String message ) {
      System.out.println( toString()+ " "  + message);
   }
}

Doc 25, Jini Distributed Events Slide # 15

LeasedEventData

package whitney.jini.examples.event;
import java.rmi.MarshalledObject;
import java.rmi.RemoteException;
import java.util.List;
import java.util.Map;
import net.jini.core.event.RemoteEvent;
import net.jini.core.event.RemoteEventListener;
import net.jini.core.event.UnknownEventException;
public class LeasedEventData {
   MarshalledObject handbackObject;
   RemoteEventListener eventListener;
   
   long leaseExpiration;
   
   public LeasedEventData( long leaseExpiration,
                           RemoteEventListener eventListener,
                           MarshalledObject handbackObject
                           ) {
      this.leaseExpiration = leaseExpiration;
      this.handbackObject = handbackObject;
      this.eventListener = eventListener;
      }
   public long getExpiration() { return leaseExpiration; }
   public void setExpiration( long newLeaseExpiration) {
      leaseExpiration =  newLeaseExpiration;
   }
      
   public void setDuration( long newLeaseDuration) {
      setExpiration( newLeaseDuration + System.currentTimeMillis() );
   }
   
   public void notifyListener(  long eventID, Object eventSource, 
                                 long eventSequenceNumber ) 
      throws UnknownEventException, RemoteException {
      RemoteEvent eventData = 
         new RemoteEvent( eventSource, eventID, 
                           eventSequenceNumber, handbackObject );
      eventListener.notify( eventData );
   }
}

Doc 25, Jini Distributed Events Slide # 16

RegisterCounter

package whitney.jini.examples.event;
import com.sun.jini.lease.LeaseRenewalManager;
import java.rmi.RMISecurityManager;
import java.rmi.server.UnicastRemoteObject;
import net.jini.core.discovery.LookupLocator;    
import net.jini.core.entry.Entry;
import net.jini.core.lease.Lease;
import net.jini.core.lookup.ServiceID;
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceRegistrar; 
import net.jini.core.lookup.ServiceRegistration;
import net.jini.lookup.entry.Name; 
public class RegisterCounter {
   public static void main (String[] args) throws Exception {
      System.setSecurityManager (new RMISecurityManager ());
      LookupLocator lookup = new LookupLocator ("jini://eli.sdsu.edu");
      ServiceRegistrar registrar = lookup.getRegistrar ();
      
      Entry[] serverAttributes = new Entry[1];
      serverAttributes[0] = new Name ("CounterEvent");
      ServiceID serverID = null;
      
      System.out.println ( "Create server" );
      RemoteCounterImpl theServer = new RemoteCounterImpl();
      theServer.start();
      
      System.out.println ( "Server is started" );
      RemoteCounter serverStub = 
         (RemoteCounter) UnicastRemoteObject.exportObject( theServer );
      System.out.println ( "Server exported" );
      ServiceItem countServer =  
         new ServiceItem( serverID, serverStub, serverAttributes);
      System.out.println ( "Start Jini registration" );
      
      ServiceRegistration serverReg =
         registrar.register(countServer, 1000 * 60 * 5 );
   
      System.out.println ( "Server registered" );
      
      Lease serverLease = serverReg.getLease();
      LeaseRenewalManager manageLease = 
         new LeaseRenewalManager( serverLease, Lease.FOREVER, null );
      
      System.out.println ( "Server lookup lease handled" );
      Thread.sleep( 1000 * 60 * 7 );
      // Server is just for testing, kill it to save the effort of manual killing it
      System.out.println ( "Server going down: ");
      manageLease.cancel(serverLease); //just to be polite
      UnicastRemoteObject.unexportObject( theServer, true );
      System.exit( 0 );
      }
   }

Doc 25, Jini Distributed Events Slide # 17

RemoteCounterClient

package whitney.jini.examples.event;
import java.io.IOException;
import java.rmi.RemoteException;
import java.rmi.RMISecurityManager;
import net.jini.core.discovery.LookupLocator;    
import net.jini.core.entry.Entry;
import net.jini.core.entry.Entry;
import net.jini.core.lookup.ServiceRegistrar; 
import net.jini.core.lookup.ServiceTemplate; 
import net.jini.core.lookup.ServiceTemplate; 
import net.jini.lookup.entry.Name; 
public class RemoteCounterClient extends Thread {
   RemoteCounter myCount;
   String name;
      
   public static void main (String[] args) throws Exception {
      if (args.length == 0 ) {
         System.out.println( "Usage: java CounterEventClient clientName");
         System.exit(0);
      }
      RemoteCounterClient aClient =
         new RemoteCounterClient(args[0], "jini://eli.sdsu.edu", "CounterEvent" );
      aClient.start();
   }
   public RemoteCounterClient(String name, String lookupService, 
         String serviceName) 
      throws IOException, RemoteException, ClassNotFoundException {
      this.name = name;
      System.setSecurityManager (new RMISecurityManager ());
      LookupLocator lookup = new LookupLocator ( lookupService );
      ServiceRegistrar registrar = lookup.getRegistrar ();
      
      Entry[] serverAttributes = new Entry[1];
      serverAttributes[0] = new Name ( serviceName );
      ServiceTemplate template = new ServiceTemplate (null, null, serverAttributes);
      myCount = (RemoteCounter) registrar.lookup (template);
      printMessage(  "Have a counter"  );
   }

Doc 25, Jini Distributed Events Slide # 18
RemoteCounterClient - Continued

   public void run() {
      printMessage(  "Start run method"  );
      try {
         for ( int k = 0; k < 5; k ++ )
            callCounter();
      } catch (Exception threadKilled ) {
         printMessage( "Exiting on exception: " + threadKilled.getMessage() );
      }
      printMessage( "Exiting" );
   }
   
   private void callCounter() throws InterruptedException, RemoteException {
      for ( int k = 0; k < 5; k ++ ) {
         myCount.increase();
         sleep( 1000 * 4 );
      }
      
      printMessage(  "Calling decrease" );
      myCount.decrease();   
   }
      
   private void printMessage( String message ) {
      System.out.println( "CounterClient " + name + " "  + message);
   }
}

Doc 25, Jini Distributed Events Slide # 19

CounterEventClient

package whitney.jini.examples.event;
import com.sun.jini.lease.LeaseListener;
import com.sun.jini.lease.LeaseRenewalEvent;
import com.sun.jini.lease.LeaseRenewalManager;
import java.io.IOException;
import java.rmi.MarshalledObject;
import java.rmi.RemoteException;
import java.rmi.RMISecurityManager;
import java.rmi.server.UnicastRemoteObject;
import net.jini.core.discovery.LookupLocator;    
import net.jini.core.entry.Entry;
import net.jini.core.event.EventRegistration;
import net.jini.core.event.RemoteEvent;
import net.jini.core.event.RemoteEventListener;
import net.jini.core.event.UnknownEventException;
import net.jini.core.lease.Lease;
import net.jini.core.lease.LeaseDeniedException;
import net.jini.core.lookup.ServiceRegistrar; 
import net.jini.core.lookup.ServiceTemplate; 
import net.jini.lookup.entry.Name; 
import net.jini.core.lease.LeaseMap;
import net.jini.core.lease.LeaseException;
public class CounterEventClient extends Thread 
   implements LeaseListener, RemoteEventListener {
   RemoteCounter myCount;
   String name;
   EventRegistration decreaseRegistration;
   LeaseMap leases;
   long leaseDuration;
   
   public static void main (String[] args) throws Exception {
      if (args.length == 0 ) {
         System.out.println( "Usage: java CounterEventClient clientName");
         System.exit(0);
      }
      CounterEventClient aClient =
         new CounterEventClient(args[0], "jini://eli.sdsu.edu", "CounterEvent" );
      aClient.start();
      }
   // RemoteEventListener method
   public void notify(RemoteEvent theEvent) 
      throws UnknownEventException, RemoteException {
      if (theEvent.getID() != decreaseRegistration.getID() )
         throw new UnknownEventException("Do not notify me of these events");
      printMessage( "Received decrease event " + theEvent.getID() );
      printMessage( "Count after decrease is " + myCount.count() );
      leases.remove( decreaseRegistration.getLease() );
      registerForRemoteEvent();
   }

Doc 25, Jini Distributed Events Slide # 20
CounterEventClient - Continued
   //LeaseListener method
   public void notify(LeaseRenewalEvent lostLease) {
      Exception deniedReason = lostLease.getException();
      if (deniedReason != null )
         printMessage( "Lost lease because: " + deniedReason );
      else
         printMessage( "Lost lease, no reason available" );
   }
   private void registerForRemoteEvent() throws RemoteException {
      printMessage( "Start registration for decrease event "  );
      try {
         MarshalledObject handbackObject = new MarshalledObject( "Not used" );
         decreaseRegistration = 
            myCount.addDecreaseListener( this, handbackObject );
         Lease eventLease = decreaseRegistration.getLease();
         long leaseDuration = eventLease.getExpiration() - now() - 100;
         leases = eventLease.createLeaseMap(leaseDuration ); 
         printMessage( "Lease duration " +  leaseDuration );
         printMessage( "Registered for decrease event "  );
      } catch (LeaseException noRegistration ) {
         printMessage( "Registration for decrease event refused"  );
      } catch (IOException marshallingProblem ) {
         printMessage( "Marshalling problem"  );
      }
   }

Doc 25, Jini Distributed Events Slide # 21
CounterEventClient - Continued

   public CounterEventClient(String name, String lookupService, 
         String serviceName) 
      throws IOException, RemoteException, ClassNotFoundException {
      this.name = name;
      System.setSecurityManager (new RMISecurityManager ());
      LookupLocator lookup = new LookupLocator ( lookupService );
      ServiceRegistrar registrar = lookup.getRegistrar ();
      
      Entry[] serverAttributes = new Entry[1];
      serverAttributes[0] = new Name ( serviceName );
      ServiceTemplate template = new ServiceTemplate (null, null, serverAttributes);
      myCount = (RemoteCounter) registrar.lookup (template);
      printMessage(  "Have a counter"  );
      RemoteEventListener notifyMe = (RemoteEventListener) 
         UnicastRemoteObject.exportObject( this );
      registerForRemoteEvent();
   }
   private long leaseDuration() {
      return decreaseRegistration.getLease().getExpiration() – 
         System.currentTimeMillis();
   }

Doc 25, Jini Distributed Events Slide # 22
CounterEventClient - Continued

   // Maintain the lease
   public void run() {
      while ( isLeaseValid() ) {
         try {
            sleep( 1000 * 30 );
            leases.renewAll();
            printMessage( "Lease duration: " + leaseDuration() );
         } catch (LeaseException denied ) {
            printMessage( "Lease renewal denied. Reason give: " +
               denied.getMessage() );
         } catch (Exception threadKilled ) {
            printMessage( "Exiting on exception: " + threadKilled.getMessage() );
            break;
         }
      }
   }
   private boolean isLeaseValid() {
      if (leaseDuration() > 0 )
         return true;
      else
         return false;
   }
   private void printMessage( String message ) {
      System.out.println( "CounterClient " + name + " "  + message);
   }
   private long now() { return System.currentTimeMillis(); }
}

Copyright ©, All rights reserved.
1999 SDSU & Roger Whitney, 5500 Campanile Drive, San Diego, CA 92182-7700 USA.
OpenContent license defines the copyright on this document.

Previous    visitors since 13-Apr-99    Next