The Artima Developer Community
Sponsored Link

Make Room for JavaSpaces, Part IV
Explore Jini Transactions with JavaSpaces
by Eric Freeman and Susan Hupfer
First Published in JavaWorld, April 2000

<<  Page 4 of 5  >>

Advertisement

An Example

To demonstrate space-based transactions, we will show you how to write a simple space "relay" that takes the entries from a source space and copies them to a set of target spaces (removing them from the source space in the process). You do this in a transactionally secure manner, such that each entry is removed from the source space and then relayed to all target spaces in one indivisible "operation." Like most JavaSpaces applications, you don't need a lot of code to make this happen. Here's how you do it:

First, you define a simple Message class that you'll use to instantiate the entries that are relayed:


import net.jini.core.entry.Entry;

public class Message implements Entry {
public String content;

// a no-arg constructor
public Message() {
}
}

This is the same Message entry we used in Part 1 of the JavaSpaces series. The entry simply holds a content string and contains a no-arg constructor (recall from the first article that the no-arg constructor is needed by all entries for serialization purposes).

Now you write a method that populates your source space with a set of numMessages Message entries.


private void createMessages() {  
for (int i = 0; i < numMessages; i++) {
  Message msg = new Message();
  msg.content = "" + i;
  try {
   sourceSpace.write(msg, null, Lease.FOREVER);
   System.out.println("Wrote message " + i + " to " + sourceName);
  } catch (Exception e) {
   System.err.println("Cant write message " + i + ": " + e);
  }
}
}  

This method simply iterates numMessages times, instantiating a new Message entry (setting its content to a string that simply represents the loop iteration) and writing it into the source space each time through the loop. You won't need to make this process transactionally secure, since it is just used to preload the source space.

Next you write a method relayMessages that removes messages from the source space and copies them to a set of target spaces (represented by an array of spaces). This time you'll copy the messages in a transactionally secure manner.


private void relayMessages() {
TransactionManager mgr = TransactionManagerAccessor.getManager();
Message template = new Message();
Message msg = null;
  
for (int i = 0; i < numMessages; i++) {
  Transaction.Created trc = null;
  try {
   trc = TransactionFactory.create(mgr, 300000);
  } catch (Exception e) {
   System.err.println("Could not create transaction " + e);
   return;
  }
  Transaction txn = trc.transaction;
  
  try {
   try {        
    template.content = "" + i;
    
    // take message under a transaction
    msg = (Message)sourceSpace.take(template, txn, Long.MAX_VALUE);
    System.out.println("Took msg " + i + " out of " + sourceName);
    
    // write message to the other spaces under a transaction
    for (int j = 0; j < targetSpaces.length; j++) {
   targetSpaces[j].write(msg, txn, Lease.FOREVER);
   System.out.println("Wrote message " + i + " to " + targetNames[j]);
    }
   } catch (Exception e) {
    System.err.println("Can't relay message " + i + ": " + e);
    txn.abort();
    return;
   }  
   txn.commit();
  } catch (Exception e) {
   System.err.println("Transaction failed");
   return;
  }
}    
}

The first thing you do in this method is call the getManager static method of the TransactionManagerAccessor class; as explained earlier, this returns a TransactionManager proxy object. Next you define two Message variables: one to serve as a template to match messages in your source space, and the other to hold entries you remove from the source space.

Next you enter a loop that iterates as many times as there are Message entries in your source space. Each time through the loop, you first obtain a new transaction by asking the transaction manager to create one. You then relay a message as follows: First you create a template by setting a Message entry's content field to hold the number of this loop iteration, for example "1" or "2" or "3" and so on. You then take the matching message entry from the space, being sure to specify the transaction txn as the second parameter. Once you retrieve the entry, you loop through all the target spaces, writing the entry into each of them. Here again, you supply txn as the transaction parameter in the write method in order to have it operate under the transaction. Finally, when the loop is finished, you call commit to complete the transaction.

Now we'll take a look at the exception handling in this code and show you that there are three ways the code can complete. Under one scenario, all your operations complete without throwing any exceptions. You call the transaction's commit method, and it completes successfully. The effect is that all the write operations take place in the space as one indivisible operation. On the other hand, the commit itself might throw an exception. In this second scenario, the outer catch clause catches the exception and prints the message "Transaction failed," the transaction expires when its lease time runs out (in this case, after 5 minutes), and no operations occur in the space. Under the third scenario, the write or take operations themselves throw an exception, which gets caught by the inner catch clause, where you try to abort the transaction. If the abort succeeds, then the operations called under the transaction do not affect the space. Note also that the transaction will also expire if your JavaSpaces client terminates unexpectedly or gets disconnected from the network during the transaction.

Now that you've written these three methods that will do the bulk of your work, all that remains is creating the code that invokes them:


public SpaceRelay(String[] args) throws IOException {
  
// from the command-line arguments, get the names of the source space
// & target spaces, and the number of messages to relay
. . .
  
// obtain access to the source and target spaces
sourceSpace = SpaceAccessor.getSpace(sourceName);
targetSpaces = new JavaSpace[targetNames.length];
for (int i = 0; i < targetNames.length; i++) {
  targetSpaces[i] = SpaceAccessor.getSpace(targetNames[i]);
}
  
// create the specified number of messages in source space
createMessages();
  
// relay messages from the source space to the target spaces
relayMessages();
  
}


public static void main(String args[]) {
if (args.length < 3) {
  System.out.println("Usage: SpaceRelay source target1 target2 ... targetN numMessages");
  System.exit(1);
}
  
try {
  SpaceRelay spaceRelay = new SpaceRelay(args);
} catch (Exception e) {
  e.printStackTrace();
}
}

As you can see, the main method simply instantiates a SpaceRelay object. The SpaceRelay constructor obtains the names of the source and target spaces, as well as the number of messages to relay, from the command-line arguments. Then the constructor proceeds to obtain references to those spaces. Finally, the constructor calls the methods you created that populate the source spaces with numMessages messages and relay those messages to the set of target spaces.

<<  Page 4 of 5  >>


Sponsored Links



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