The Artima Developer Community
Sponsored Link

Jini Seminar by Bill Venners
Jini Programming Model
Lecture Handout

Agenda


Distributed Challenges


Partial Failure


Leasing


Lease Negotiation


Lease Use


The Lease Interface

package net.jini.lease;
import java.rmi.RemoteException;

public interface Lease {

    /**
     * Requests a lease that never expires.
     */
    long FOREVER = Long.MAX_VALUE;

    /**
     * Requests a lease time most convenient for grantor
     */
    long ANY = -1;

    /**
     * Serialized form is a duration - best for between hosts
     */
    int DURATION = 1;

    /**
     * Serialized form is absolute
     */
    int ABSOLUTE = 2;

    /**
     * Returns absolute time that the lease will expire,
     * represented as milliseconds from the beginning of the epoch,
     * relative to the local clock.
     */
    long getExpiration();

    /**
     * Indicates holder no longer interested in resource.
     */
    void cancel()
        throws UnknownLeaseException, RemoteException;

    /**
     * Renews a lease for an additional period of time,
     * specified in milliseconds.
     *
     * This duration is used to determine a new expiration time
     * for the existing lease. It is not added to the original lease.
     *
     * If the renewal fails, the lease is left intact for the same
     * duration that was in force prior to the call to renew.
     */
    void renew(long duration)
	throws LeaseDeniedException, UnknownLeaseException,
        RemoteException;

    /**
     * Sets the format to use when serializing the lease.
     */
    void setSerialFormat(int format);

    /**
     * Returns the format that will be used to serialize the lease.
     */
    int getSerialFormat();

    /**
     * Creates a Map object that can contain leases whose renewal or
     * cancellation can be batched, and adds the current lease to that map.
     * The current lease is put in the map with the duration value given
     * by the parameter.
     */
    LeaseMap createLeaseMap(long duration);

    /**
     * Returns a boolean indicating whether or not the lease given as a
     * parameter can be batched (placed in the same LeaseMap) with the
     * current lease.  Whether or not two Lease objects can be batched
     * is an implementation detail determined by the objects.
     */
    boolean canBatch(Lease lease);
}

Service Registration is Leased


Event Notification


Local versus Distributed Events


Jini Distributed Events


Distributed Event Participants


The RemoteListener Interface

      package net.jini.event;

      public interface RemoteEventListener
          extends java.rmi.Remote, java.util.EventListener
      {
          void notify(RemoteEvent theEvent)
             throws UnknownEventException, java.rmi.RemoteException;
      }

The RemoteEvent Class


An Event Generator Example

    public EventRegistration register(long eventID,
        MarshalledObject handback, RemoteEventListener toInform,
        long leaseLength) throws UnknownEventException, RemoteException;

The EventRegistration Class

      package net.jini.event;
      import net.jini.lease.Lease;

      public class EventRegistration implements java.io.Serializable {

          public EventRegistration(long eventID, Object source,
              Lease lease, long seqNum);
          public long getID();
          public Object getSource();
          public Lease getLease();
          public long getSequenceNumber();
      }

Remote Events Diagram


Third Parties


An Event Mailbox


Transactions


Jini Doesn't Do ACID


Completion Protocol


TransactionManager

package net.jini.core.transaction.server;

public interface TransactionManager
    extends Remote, TransactionConstants {

    /**
     * Begin a new top level transaction
     */
    Created create()
        throws LeaseDeniedException, RemoteException;

    public static class Created implements Serializable {
        long id;
        Lease lease;
    }

    /**
     * Join a transaction managed by this TransactionManager
     */
    void join(long id, TransactionParticipant part, long crashCount)
        throws UnknownTransactionException, CannotJoinException,
        CrashCountException, RemoteException;

    /**
     * Returns the current state of the given transaction.
     *
     * Returns any constant in TransactionConstants:
     * ABORTED, ACTIVE, COMMITTED, NOTCHANGED, PREPARED, VOTING.
     */
    int getState(long id)
	throws UnknownTransactionException, RemoteException;

    /**
     * Commit the transaction.
     */
    void commit(long id)
        throws UnknownTransactionException, CannotCommitException,
            RemoteException;

    /**
     * Abort the transaction.
     */
    void abort(long id);
        throws UnknownTransactionException, CannotAbortException,
            RemoteException;
}

TransactionParticipant

package net.jini.core.transaction.server;

public interface TransactionParticipant
    extends Remote, TransactionConstants {

    /**
     * Requests participant prepare itself to commit and vote on
     * the outcome. Returns ABORTED, PREPARED, or NOTCHANGED.
     */
    int prepare(TransactionManager mgr, long id)
        throws UnknownTransactionException, RemoteException;

    /**
     * Requests participant make all its PREPARED changes visible outside
     * the transaction, and unlock any relevant resources.
     */
    int commit(TransactionManager mgr, long id)
        throws UnknownTransactionException, RemoteException;

    /**
     * Requests participant roll back any changes and unlock any
     * relevant resources.
     */
    int abort(TransactionManager mgr, long id)
        throws UnknownTransactionException, RemoteException;

    /**
     * Used when just one participant left to prepare and all other
     * participants (if any) have responded with NOTCHANGED.
     */
    int prepareAndCommit(TransactionManager mgr, long id)
        throws UnknownTransactionException, RemoteException;
}

Transaction Semantics


TransactionFactory

    package net.jini.core.transaction;

    public class TransactionFactory {

        /**
         * Create a new top-level transaction.
         */
        public static Transaction.Created
            create(TransactionManager mgr, long leaseFor)
            throws LeaseDeniedException, RemoteException;
    }

Transaction

package net.jini.core.transaction;

public interface Transaction {

    /**
     * Commit the transation. Manager initiates voting. Returns after
     * all participants have indicated either PREPARED or NOTCHANGED.
     * Else, throws CannotCommitException.
     */
    void commit()
        throws UnknownTransactionException, CannotCommitException,
            RemoteException;

    /**
     * Abort the transaction. Returns as soon as abort decision is
     * recorded.
     */
    void abort()
        throws UnknownTransactionException, CannotAbortException,
            RemoteException;
}

ServerTransaction

package net.jini.core.transaction;

public class Transaction
    implements Transaction, Serializable {

    /**
     * Join the transaction.
     */
    public void join(TransactionParticipant part, long crashCount)
        throws UnknownTransactionException, CannotJoinException,
            CrashCountException, RemoteException;

    /**
     * Returns the current state of the transaction.
     */
    public int getState()
        throws UnknownTransactionException, RemoteException;
}

Transactions Steps

  1. Client hands factory method TransactionManager and lease duration
  2. Client gets back a new Transaction object and Lease object
  3. Client passes Transaction to participants when asking a participant to do a task "under the transaction"
  4. Participants must "join" the transaction before performing the task
  5. If client or a participant "aborts" the transaction, the TransactionManager will instruct all participants to "roll back"
  6. If client or a participant "commits" the transaction, the TransactionManager will query all participants
  7. If all participants report either "prepared" or "no-change," the TransactionManager will instruct all participants to "roll forward"

Transactions Diagram


Conclusion


Exercises

Problem 1.

In the ProgMan/ex1 directory, create a class that implements of the com.artima.fortune.Fortune interface:

package com.artima.fortune;

import java.io.Serializable;

public interface Fortune extends Serializable {

    String getFortune();
}

Name your class whatever you want. Each time your getFortune() method is invoked, it should print out a fortune. Use any fortune that you think might either plausibly appear in a fortune cookie at a Chinese restaurant, or something you think is funny. We'll let you know if it actually is funny. Your Fortune object should have at least four fortunes in its repertoire. Each time getFortune() is invoked, your method should randomly select one of its fortunes and return it.

Problem 2.

Create a Java application named FortuneServer that performs multicast discovery and registers your Fortune service with all discovered lookup services. You can simply pass an instance of your Fortune implementation class, a LookupDiscovery, and a LeaseRenewalManager to a JoinManager. Something like:

Fortune fortuneService = new FortuneImpl();

String[] groups = new String[] {""};
LookupDiscovery lookupDisc = new LookupDiscovery(groups);

JoinManager joinManager = new JoinManager(fortuneService,
    new Entry[0], new FortuneServer(), lookupDisc, new LeaseRenewalManager());

Make your FortuneServer class itself implement ServiceIDListener. In its serviceIDNotify() method, print out the service ID assigned by the lookup service. That way you'll know when your service is successfully registered.

Lastly, make sure that you keep the main thread running so that the JoinManager will continue to renew the lease on your service registration. Something like this will serve that purpose:

while (true) {
    try {
        Thread.sleep(1000 * 60);
    }
    catch (InterruptedException e) {
    }
}

Start your FortuneServer and make sure it prints out a service ID. Then leave your FortuneServer running, so others will be able to use your Fortune service.

Problem 3.

Create a Java application named FortuneClient whose main() method just creates an instance of itself and invokes its own run() method:
public static void main (String[] args) {

    FortuneClient fc = new FortuneClient();
    fc.run();
}

Create a run() method that returns void and takes no arguments. In this method, set the security manager and perform multicast discovery. Name your discovery listener RegistrarFinder:

String[] groups = new String[] {""};
LookupDiscovery lookupDisc = new LookupDiscovery(groups);
lookupDisc.addDiscoveryListener(new RegistrarFinder());

Define a private inner class named RegistrarFinder that implements DiscoveryListener. DiscoveryListener should maintain a java.util.Set of ServiceRegistrars currently discovered. As new lookup services are discovered, they should be added to the Set. Any existing lookup services that are discarded should be removed from the Set.

Test this functionality by printing out a jini: URL for each lookup service as it is added and removed from your Set.

Problem 4.

Lastly, enhance your run() method so that each time the user hits return on the command line, your method:

Test your FortuneClient by repeatedly hitting return. You should get a random mixture of fortunes provided by your own Fortune service and those of your peers.

Problem 5.

Modify your ChatWriter from the problem set for the Jini Runtime Infrastructure module so that it looks for a transaction manager in addition to a JavaSpace. Your ChatWriter should wait until both a transactiion manager and the appropriately named JavaSpace is found before it starts reading lines from the standard input. (Although you look for a particular JavaSpace, you can accept any transaction manager.)

Problem 6.

Add two constants to your ChatWriter class that specify the default room name and your name, as in:
private static final String roomName = "DefaultRoom";
private static final String userName = "Bill";

Try to pick a name likely to be unique among your peers.

Next, instead of just writing chat messages to the space with a position value of Long(0), go through the full process outlined in the JavaSpaces lecture for writing a message to the chat JavaSpace:

The procedure is:

Here's the code snippet from the JavaSpaces lecture:

 1 // Create the template for NextMessageNumber
 2 NextMessageNumber template = new NextMessageNumber(
 3     currentChatRoom, null);
 4
 5 // Create a transaction
 6 Transaction.Created trans = TransactionFactory.create(
 7     transMan, TIME_OUT);
 8
 9 // Renew lease on transaction
10 long expireTime = trans.lease.getExpiration();
11 Date date = new Date();
12 long currentTime = date.getTime();
13 long leaseLen = expireTime - currentTime;
14 if (leaseLen < TIME_OUT) {
15     leaseMan.renewUntil(trans.lease,
16         currentTime + leaseLen, null);
17 }
18
19 // Take the NextMessageNumber entry
20 NextMessageNumber nextMsgNumEntry =
21     (NextMessageNumber) space.take(template,
22     trans.transaction, TIME_OUT);
23
24 // Grab the next message number
25 myMessageNum = nextMsgNumEntry.nextMessageNum;
26
27 // Increment the number in the entry
28 nextMsgNumEntry.increment();
29
30 // Write the incremented number back to the space
31 write(nextMsgNumEntry, trans.transaction, Lease.FOREVER);
32
33 // Create the ChatMessage entry using the message number
34 ChatMessage msgEntry = new ChatMessage(currentChatRoom,
35     userName, myMessageNum, message);
36
37 // Write the chat message to the space
38 space.write(msgEntry, trans.transaction, MESSAGE_LEASE_LENGTH);
39
40 // Commit the transaction
41 trans.transaction.commit();

Test your revised ChatWriter with your test JavaSpace. To get a NextMessageNumber entry written to the space in the first place, feel free to use the WriteEntry class from the ProgMan/soln/prob5 directory.

Problem 7.

Change your ChatReader so that it first reads the current NextMessageNumber entry for the default room. Then, instead of taking all ChatMessage entries, it just does a blocking read on the next ChatMessage that will be written to the space. Simply fill your ChatMessage template with the default room and next message number. Each time the read returns, just increment your local copy of the next message number and do another blocking read for a ChatMessage using the new value of next message number.

Problem 8.

Lastly, change your spaceName constant in both ChatReader and ChatWriter so that instead of reading from and writing to your test space, you read from and write to a shared community space. This will allow you to actually chat with your peers.

Sponsored Links

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use