The TSpaces Handler Programmer's Guide will describe how to write Handlers that can be installed as new TSpaces commands. There is also a Client Programmer's Guide that will cover TSpaces client programming.
This guide applies to TSpaces Version 3.1.0
A TupleSpace (or "TS" on the server) is the combination of a database (TSDB) object and a stack of Tuple Space Handler Factory (TSFactory) objects. A database is the data structure responsible for storing and indexing a collection of Tuples on the server. A TSFactory is a class that is responsible for producing an instance of a Tuple Handler (TSHandler) for each "command" Tuple that is sent to the space. It is the TSHandler that implements the command specified in the Tuple.
The indirection that results from this scheme makes it possible to easily alter the handlers associated with a space. By adding a new factory it is possible to completely alter the semantics of a space. The selection of a handler is completely up to the factory, and the implementation of a command is completely up to the handler.
By stacking factories so that a new one passes any commands it doesn't recognize along to the previous factory, it is possible to augment the semantics of a space (adding a new command for instance) without the need to completely implement basic functionality.
TSpaces allows a user with ADMIN authority for the server to add TSFactory and TSHandler objects dynamically at runtime. This makes TSpaces extremely flexible
To show how one would implement new commands and modify the semantics of existing commands, we have included an example "handler" that will handle automatic deletion of Tuples after a specified time. This will involve modifying the Write command so that it will add a timestamp to the tuple before it is written to the database and implementing a new command START that will startup a new thread in the server that will periodically delete the expired Tuples.
Note: We have since added Tuple Expiration to the base system so this is a useless example but it is still a good example.
To implement the behavior that we want, we have defined StaleTuple, which is a SubClassableTuple, and StaleTupleSpaceHandler.
StaleTuple is used to hide the details of the Tuple format under get and set methods and is similar to our earlier example of a SubclassableTuple
StaleTupleSpacehandler is where all of the interesting code resides. Since the implementation of a handler at runtime involves both client and server actions, let's look at the client actions first. The client code that will be invoked is in the main() method for StaleTupleSpacehandler. In a real application, this would more likely be part the application code but it is convenient here to bundle it all in one file.
TupleSpace ts = new TupleSpace(TSName,
Host,TupleSpace.DEFAULTPORT,
null,null,
MyUserid,MyPassword); This will create a TupleSpace with the specified name on the specified server. Note that we specify our userid and password which in our case was specified on the command line but could have been prompted for with a GUI interface. This userid must have ADMIN authority for the Server so that we will have the authority to issue the following methods.
Now we will add a TSFactory object that supports downloadable handlers. We have supplied TSFExtendable which will satisfy this need.
final String FN = "com.ibm.tspaces.server.handler.TSFExtendable";
ts.addFactory( new Class[] { Class.forName(FN)} ); Now we will create an instance of StaleTupleSpaceHandler and issue an addHandler command for each new or modified command that this handler will service.
StaleTupleSpaceHandler dh = new StaleTupleSpaceHandler();
ts.addHandler( TupleSpace.WRITE, new Class[] { dh.getClass()} );
ts.addHandler( StaleTupleSpaceHandler.START, new Class[] { dh.getClass()} );We will now issue the START command and use our modified Write command to write an instance of StaleTuple to this TupleSpace.
Tuple argtuple = new Tuple(60*1000);
ts.command( StaleTupleSpaceHandler.START,argtuple);
StaleTuple test1 = new StaleTuple("key1","some data");
test1.setTimeToLive(15000); // 15 seconds of life
ts.write(test1); The above code issues the new START command and specifies that the new thread should be invoked every 60 seconds. Note that we have to use the TupleSpace.command() method since the TupleSpace class doesn't know anything about "START". The command() method will simply send the START command and any argument Tuple to the server. However, the normal TupleSpace.write command can be used but at the server our new implementation will be invoked for this particular space. As we will see in the next section, our new write implementation will use the specified TimeToLive value to generate a timestamp that is added to the StaleTuple before it is written to the database.
Now let's look at the implementation of the StaleTupleSpaceHandler. There are a number of methods that are required to be present for a Command handler to implement or extend commands. In addition to the brief description here, the sample code has comments that describe what is needed.
Null Constructor
The null
constructor is called by the Factory to construct the handler.
handles()
The handles()method
is called by the Factory to determine if the command handler wants
to handle this command and the SuperTuple that is passed to it. It
would normally return true.
attributes()
The attributes() method returns the
Permission that a client must have to invoke the command. As shown
below, the method returns an array of permissions that the user
must have.
public AccessAttribute []
attributes(String cmdString, SuperTuple argTuple)
throws TSHandlerException {
AccessAttribute admin[] = { AccessAttribute._ADMIN_ATTRIBUTE };
AccessAttribute write[] = { AccessAttribute._WRITE_ATTRIBUTE};
if(cmdString.equals(TupleSpace.WRITE) )
return write;
if(cmdString.equals(StaleTupleSpaceHandler.START) )
return admin;
} // attributes
command()
The command method is the real meat of
the implementation. We show below a simplified implementation of
the "WRITE" command. It first calls a method in
StaleTuple that places the current date/time stamp into the 1st
field of the tuple. It then extracts the original WRITE routine
and invokes it to actually write the tuple. It then returns a
tuple that contains the timestamp field.
Note that there is a check for the Write command being enabled. This enabled switch would be set by the START command. For this command this is really not needed but because the Server internally uses the WRITE command, it may be important that the user added WRITE command is completely setup before it is turned loose to handle all WRITE requests.
if (_enabled)
((StaleTuple)argTuple).updateExpirationDate();
final TSHandler simpleWrite = ts.mostBasicHandler( TupleSpace.WRITE, argTuple);
simpleWrite.command( ts, TupleSpace.WRITE, argTuple, clientID_, communicator_, user_);
retValue.add(argTuple.getField(0));
return retValue;The implementation of the START command is quite different. It gets the parameter that says how often we want to check for expired tuples and then calls a special constructor for the StaleTupleSpaceHandler to construct a new Runnable instance and then it sets up a thread for the daemon and starts the thread executing.
Field first = argTuple.getField(0); final long howOften = ((Long)first.getValue()).longValue(); StaleTupleSpaceHandler newExecuter = new StaleTupleSpaceHandler(ts,howOften,clientID_, communicator_); Thread doTheLoop = new Thread(newExecuter,"StaleKiller"); doTheLoop.setDaemon( true); // this is a daemon so set it on doTheLoop.start(); _enabled = true; // enable the WRITE command
The code for the run method is shown below. Basically it builds a template that contains the current date and time and then uses this template to issue a delete command that will delete all StaleTuple instances that have an earlier timestamp. In order to satisfy the transaction locking of TSpaces, it first generates a Client TransactionID and calls the TransactionManager to begin a transaction. After the delete, it call the TransactionManager to commit the transaction.
public void run(){
long throwOutBeforeThis = System.currentTimeMillis();
StaleTimestamp timestamp = new StaleTimestamp(throwOutBeforeThis);
StaleTuple template = new StaleTuple(timestamp);
int ct =0;
String myClientID;
TransactionManager transMgr = TSServer.getTransMgr();
TSHandler simpleDelete = _ts.mostBasicHandler( TupleSpace.DELETE, template);
while(true) {
// generate a new Transaction Identifier and tell TransactionManager
ct++;
myClientID = _clientID+"START"+ct;
int transID = transMgr.beginTrans(myClientID);
throwOutBeforeThis = System.currentTimeMillis();
// Create a subclass of Field that contains the current date/time
timestamp = new StaleTimestamp(throwOutBeforeThis);
template = new StaleTuple(timestamp);
SuperTuple delTuple = simpleDelete.command( _ts,
TupleSpace.DELETE,
template,
myClientID,
_communicator,
_user);
transID = transMgr.commitTrans(myClientID);
delay( (int)_howOften) ;
} The sharp eyed reader may have noticed that something is wrong with the above description. The DELETE command only deletes Tuples that match the template so it should only delete Tuples that have the exact same timestamp instead of earlier timestamp. The reason that this works is that the time stamp field is an instance of StaleTimestamp which implements the matches() such that any timestamp that is less than the template timestamp will return true.