The Artima Developer Community
Sponsored Link

Survival of the Fittest Jini Services, Part II
Use Transactions to Coordinate the Reliable Interaction of Jini Services
by Frank Sommers
First Published in JavaWorld, July 2001

<<  Page 6 of 8  >>

Advertisement

Putting it All Together

The following code illustrates the purchase of a book under a transaction:

public class BookStoreImpl implements BookStore, TransactionParticipant {

  ....

   /**
    * Calling this method initiates the purchase transaction.
    * Since the purchase itself might be part of a larger transaction,
    * we allow a Transaction object to be passed in the
    * method call. An example would be a transactional email service
    * that guarantees the delivery of the confirmation: If the delivery
    * fails, the transaction is aborted. In that case, the email service
    * will be the transaction client. If null is passed
    * in as the Transaction object, a new transaction will be created,
    * and the BookStoreImpl becomes the transaction's client.
    */
    public OrderConfirmation buyBook(Book book,
                                     Account creditCard,
                                     Customer customer,
                                     Address shipTo,
                                     int daysToDelivery,
                                     Transaction txn)
        throws NoSuchBookException, CreditCardException, ShippingException,
            BookStoreException, RemoteException, TransactionException {


    boolean client = false;
    TransactionManager txMan = null;
    ServerTransaction sTxn = null;
    Lease txnLease = null;

    //If the transaction is null, we'll be the client, and we also
    //need to create a new Transaction.
    if (txn == null) {
        client = true;
        txMan = discManager();

        //This object is a bundle of the ServerTransaction + its lease.
        //We specify a lease of 3 minutes for the transaction.
        Transaction.Created ct = TransactionFactory.create(txMan, 180 * 1000);
        sTxn = (Transaction)ct.transaction;
        txnLease = (Lease)ct.lease;
  
        //Manage the transaction's lease. The implementation is not shown.
        manageTrLease(txnLease);

    } else {

        //We can only handle ServerTransaction.
        if (txn instanceof ServerTransaction)
            sTxn = (ServerTransaction)txn;
        else
            throw new TransactionException("Unknown transaction semantics.");
    }

    //Everything from here will be performed under the transaction.
    //////////////////////////////////////////////////////////////

    try {

        //Since we need to ensure that we save or print the confirmation,
        //we also will have to join the transaction.
        sTxn.join(this, 0);

        //Call each method of the other services, passing the transaction object
        //as parameter. Each service must join the transaction as well.

        //Decrement the inventory count for this book.
        //If no more books are available, a NoSuchBookException will be
        //thrown. We catch exception and, if we are the transaction client,
        //cause the transaction to abort before returning the exception
        //to the caller.
            int remaining = bookDatabase.decrementInventory(book, sTxn);
          
        //Obtain the best deliver price for the book. The implementation
        //is not show, but is available in the full example.
        //This might return a ShippingException, which we catch and
        //and handle similarly to the NoSuchBookException.
        PackageDesc packDesc = PackageDesc.createDescription(book);
        PickupConfirmation pickupConf =
            ShippingSelector.schedulePickup(wareHouseAddress,
                                                shipTo,
                                                  packDesc,
                                            int daysToDelivery,
                                              sTxn);


        //Finally, we attempt to charge the credit card. TotalPrice
        //includes the book's price, local tax, and shipping charges.
        //TotalPrice is an implementation of Price.
        //The system determines tax, based on location.
        //If the charge attempt fails, the exception will be handled
        //similar to the other service-specific exceptions.
        TotalPrice price = CashRegister.computePrice(book, conf);
        Charge chg = new Charge(price);
        ChargeConfirmation chgConf = card.debit(account, chg, sTxn);


        //Now that we have succeeded in all the operations with other
        //services, produce and save the OrderConfirmation. This must
        //succeed before the transaction can be committed.
        //saveConfirmation may return a CannotSaveException.
        OrderConfirmation orderConf =
            new OrderConfirmation(pickupConf, chgConf, book);
        saveConfirmation(orderConf);

        //If we are the client, commit the transaction.
        if (client)
            ///////////////////////////////////////////
            //Transaction ends here, if we're the client.
            ///////////////////////////////////////////
               sTxn.commit();
        
        //Return orderConf.
        return orderConf;

        } catch (Exception e) {
            ///////////////////////////////////////////
            //Transaction ends here if we abort.
            ///////////////////////////////////////////
            if (client)
               abortTransaction(sTxn);

            if ((e instanceof NoSuchBookException) ||
                (e instanceof ShippingException) ||
                (e instanceof CreditCardException) ||
                (e instanceof TransactionException))

                throw e;

            else

                throw new BookStoreException(e.getMessage());
        }
   }



    /**
     * Discover TransactionManager. This method should really
     * declare more specific exceptions.
     */
    private TransactionManager discManager() throws Exception {

       ServiceDiscoveryManager serviceDiscoveryManager;
       ...
       Class[] trTypes = {TransactionManager.class};
       ServiceTemplate tmpl = new ServiceTemplate(null, trTypes, null);
       ServiceItem item = serviceDiscoveryManager.lookup(tmpl, null);
       TransactionManager tm = (TransactionManager)item.service;

       return tm;
    }

    /**
     * We are not using a transaction for this method.
     */
    public Collection findBooks(Book template) throws RemoteException {
         //Find books matching non-null fields of the specified template.
           ...
    }

  ...
  
}


The following list explains the code in more detail:

You might have noticed an interesting twist here: In some situations, you want to ensure that the customer can actually print or display confirmation. For instance, if the printer or display fails, you'd rather the transaction be aborted. This might also be the case for an airline ticket sale or the filing of a tax return. The challenge with these real-world activities is that it is very difficult to undo them. If, after the confirmation has printed, the credit card service decides to abort the transaction, then the printed confirmation becomes invalid. But it's already in the customer's hands!

The only solution here is to ensure, as much as possible, the success of online activities first, and only then perform the offline actions associated with the transaction. That is why we only saved the confirmation during the transaction, and left it up to the customer to print it at his convenience. When you need physical proof to be part of the transaction, you probably need to print a cancellation note as well when you abort it. Of course, printing that note can fail, too.

This is one area where Jini-enabled devices will simplify life: printers, cell phones, email systems, and storage devices can all become transaction participants along with business-specific services. If you need that ticket to print out, that confirmation number to display on your cell phone, or that email message to be delivered, the transaction will not complete until these physical actions succeed. (Of course, this can also backfire: if you ask your coffee machine and toaster to transactionally prepare a breakfast, when your toaster burns the bread, the coffee machine might feel obligated to undo your coffee. That's an example of a situation in which you shouldn't use transactions!)

In the final part of this series, we'll look at some of the failure conditions that plague real-life networks, and what transactions can do about them.

<<  Page 6 of 8  >>


Sponsored Links



Google
  Web Artima.com   
Copyright © 1996-2017 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use - Advertise with Us