The TSpaces Client Programmer's Guide will describe the client interface only. Also, it is not an exhaustive list. A full description of the TSpaces programming interface can be found in the TSpaces Programming Interface Specification. There is a Server Administration guide that will cover Server issues. There is also a Handler Programmer's Guide that will cover how to write new commands that can be added to the TSpaces server.
This guide applies to TSpaces Version 3.1.*
A Tuplespace is a globally shared, associatively addressed memory space that is organized as a bag of tuples (defined below). The basic element of a Tuplespace system is a tuple, which is simply a vector of typed values, or fields. Templates are used to associatively address tuples via matching. A template is similar to a tuple, but some (zero or more) fields in the vector may be replaced by typed placeholders (with no value) called formal fields.
A Field object is the most basic component of the Tuplespace data structure hierarchy. A Field object contains:
Type (i.e. "java.lang.String" or "com.company.appl.EmployeeObject")
Value
Field Name (optional): Specifying a field name implies that this field will be indexed (and searched).
Here is an example of creating a Field object that contains a String and has a name of "KeyField":
String name = "George";
Field f1 = new Field("KeyField",name); If you have a Field object, you would use the getValue() method to access the value of the Field.
String name = (String)f1.getValue();
A Tuple object is an ordered sequence of Fields.
The SuperTuple class is where the interesting functionality of tuples is implemented, but it's an abstract class, so clients should create either Tuple or SubclassableTuple objects. For example the following will create a Tuple that contains a name and address fields.
Field f1 = new Field("George");
Field f2 = new Field("1 Main St,Santa Cruz,CA,95062");
Tuple t1 = new Tuple(f1,f2); A template tuple is a SuperTuple that is used for matching. One or more of the Fields in a template may be "wildcards" and consist of only the class type with no value. These are often referred to as "Formal Fields". For Example:
Field f1 = new Field("George");
Field f2 = new Field(String.class);
Tuple template = new Tuple(f1,f2); The above template would match any Tuple that has "George" as the 1st Field and any String as the 2nd Field.
A TupleSpace is a shared named collection (bag) of Tuples. The basic operations supported by the collection are to add a Tuple to the space (write), and to remove one from the space (take). The collection is stored and administered across a network on one or more "T Space Servers". Clients on the same or different machines can be accessing the space simultaneously. Some will be adding tuples to the space and some removing them. A client thread accesses the collection/space using the methods of a TupleSpace instance. For each different TupleSpace a client wishes to access, it requires a separate instance of this class, even if they are managed by the same server. The details of how the operations are completed are hidden from the user. All they need is the name of the space, and the name of a server that manages that space.
A TSpaces server contains many Tuplespaces. It is up to the application writer to decide whether to use one or many tuplespaces for a particular application. However, it is important to know that it's really hard to run out of spaces, so there's no reason to try to fit too many different tuple types into a single space.
The basic primitive operations supported by the space are:
write( tuple ) Adds a tuple to the space.
take( template_tuple ) Performs an associative search for a tuple that matches the template. When found, the tuple is removed from the space and returned. If none is found, returns null.
waitToTake( template_tuple ) Performs an associative search for a tuple that matches the template. Blocks until a match is found. Removes and returns the matched tuple from the space.
read( template_tuple ) Like "take" above, except that the tuple is not removed from the space.
waitToRead( template_tuple ) Like "waitToTake" above, except that the tuple is not removed from the space.
multiRead( template_tuple ) Like "read" above, except returns the entire set of tuples that match.
countN( template_tuple ) Like "multiRead" above, except that it returns a count of matching tuples rather than the set of tuples itself.
There are also numerous other more specialized commands and there is an ability to define your own specialized commands.
The concepts of TupleSpaces was first developed as part of the Linda System at Yale University in the 1980s. JavaSpaces from Sun MicroSystems is also based on Linda and an excellent book JavaSpaces Principles, Patterns, and Practice has been written that covers the techniques of using "Spaces" for distributed programming. The first chapter from the book is available online
Although the examples in the book use the JavaSpaces implementation, it is trivial to write the same examples using the TSpaces implementation of Tuplespace. The HelloWorld example described later is an example of how to convert a JavaSpaces application to TSpaces.
Lets take a look at a some very simple code that will read and write data using TSpaces. The following are just fragments of the code, for the complete source, look at the source for Example1.java
String host = "tserver1.company.com";
...
TupleSpace ts = new TupleSpace("Example1",host);
The above will contact the TSpaces server that is running on the "tserver1" system and connect to the TupleSpace that is named "Example1". If the "Example1" space does not exist, it will be created at this time.
Field f1 = new Field("Key1");
Field f2 = new Field("Data1");
Tuple t1 = new Tuple(f1,f2);
ts.write(t1);
Tuple t2 = new Tuple("Key2",new Integer(999),"Data2");
ts.write(t2);
Tuple t3 = new Tuple("Key2","Data3");
ts.write(t3);
The above will write 3 Tuples to the TupleSpace allocated previously. Note that the 3 Tuples do not really need to all have the same number or type of Fields. The 2nd Tuple has 3 Fields while the others have 2 Fields.
Also, the contents of the Tuple could have been directly coded in the write command. or we could have done this the long way and directly allocated the Fields of the tuple. The following are all equivalent:
Field f1 = new Field("Key1");
Field f2 = new Field("Data1");
Tuple t1 = new Tuple(f1,f2);
ts.write(t1);
-or-
Field f1 = new Field("Key1");
Field f2 = new Field("Data1");
Tuple t1 = new Tuple();
t1.add(f1);
t1.add(f2);
ts.write(t1);
-or-
Tuple t1 = new Tuple("Key1","Data1");
ts.write(t1);
-or-
ts.write("Key1","Data1"); The 2nd method (using add(field)) is required when the Tuple has more than 5 Fields.
Now we will read one of the above Tuples using the first field as a key and then extract the contents of the second field.
Tuple template = new Tuple("Key2", new Field(String.class));
Tuple tuple = ts.read(template);
String data = (String)tuple.getField(1).getValue();The read command will read the matching Tuple from the TupleSpace database and return it to the caller. The parameter passed to read is a template Tuple. A template Tuple is a Tuple that is used to select matching Tuples. It will contain 0 or more Fields that are either Actual Fields or Formal Fields.
Actual Fields have a value that must be matched exactly against the corresponding Fields in the tuples that are in the TupleSpace.
Formal fields have a class but no value and only describe the type of value that is to be returned. Basically they act as wildcards.
In the example above, the Field that results from new Field(String.class) is a Formal Field that would match any Field that contains a String object.
The Tuple that is returned must exactly match the format of the template Tuple. So in the example above, the Tuple("key2","Data3") will be returned. The Tuple("key2",new Integer(999),"data2") will not be returned because it does not match the format of the template.
The Formal Fields in a template must match the actual Field or be a superclass of the Field. For example, if you want to have a wildcard for one of the Fields that will match any object, then you would use the Serializable.class for the Formal Field.
ts.write("key","a string");
ts.write("key",new Integer(99));
Tuple template = new Tuple("key",new Field(Serializable.class));
Tuple result = ts.multiRead(template);The above command would pick up both Tuples because the Formal Field matches any Field that contains any Serializable object.
The take command is similar to read but it will remove the matching Tuple from the TupleSpace database and return it to the caller.
The example above uses the simple TSpaces class names ( i. e. Tuple). The compiler needs to be able to translate this to the fully qualified name (i. e. com.ibm.tspaces.Tuple) so you need to add an import statement:
import com.ibm.tspaces.*;
In the section on how to compile we will describe how to point the compiler and runtime environment to the proper TSpaces library.
The example above ignored
exceptions that can be thrown by TSpaces. However, almost all of the
TupleSpace methods can throw TupleSpaceException and therefore one
needs to wrap the TSpaces code in Try/Catch blocks.
For example:
try {
TupleSpace ts = new TupleSpace("Example1",host);
ts.write("Key1","Data1");
ts.write("Key2",new Integer(999),"Data2");
ts.write("Key2","Data3");
} catch(TupleSpaceException tse) {
System.out.println("TupleSpace Exception: " + tse.getMessage());
tse.printStackTrace();
}Some of the examples that follow may not show the try/catch in order to make the example easier to follow, but be assured, you must have either try/catch blocks or declare that the method throws TupleSpaceExecption.
One of the most important features of TSpaces is that a client can issue a command to read a Tuple before another client has written the desired Tuple. When an application issues a WaitToRead or WaitToTake call, and the data is not yet there on the server, the application blocks on the call until an answer is returned. When a tuple arrives on the server that matches the WaitToRead or WaitToTake query, it is sent to the client and the application resumes.
As an example, assume that in a client running on machine1, you had the following code:
try {
TupleSpace ts = new TupleSpace("Example",host);
SuperTuple answer = ts.waitToTake("3",new Field(String.class));
...
} catch(TupleSpaceException tse) {
System.out.println("TupleSpace Exception: " + tse.getMessage());
}And assume that in a client running on machine2, you had the following code:
try {
TupleSpace ts = new TupleSpace("Example",host);
ts.write("1","Data1");
Thread.sleep(5000);
ts.write("2","Data2");
Thread.sleep(5000);
ts.write("3","Data3");
} catch(TupleSpaceException tse) {
System.out.println("TupleSpace Exception: " + tse.getMessage());
}Assuming that the applications started at the same time, the application on machine1 would issue the waitToTake and then be blocked. However, about 10 seconds later as soon as the matching Tuple from machine2 arrived at the server, the server would send it to machine1 which would unblock and process the matching Tuple.
The default for blocking commands is to wait forever. However one can specify an optional timeout parameter to specify that if the operation has not been satisfied when the time limit is exceeded, then the waiting operation should be terminated and the null should be returned. So if we wanted to only wait for 60 seconds, then the code above would now look like the following:
try {
TupleSpace ts = new TupleSpace("Example",host);
Tuple template = new Tuple( "3",new Field(String.class));
SuperTuple answer = ts.waitToTake(template,60*1000));
if (answer == null) {
System.out.println("Operation timed out");
} else {
...
}
} catch(TupleSpaceException tse) {
System.out.println("TupleSpace Exception: " + tse.getMessage());
}
The examples above used the Tuple class to contain the Fields that are written to or read from a TupleSpace. The Tuple class is a subclass of SuperTuple which is an abstract class and can not be instantiated. The Tuple class is declared final and cannot be subclassed. An alternative to Tuple is SubclassableTuple which can be subclassed.
Some of the advantages and disadvantages of defining your own SubclassableTuple are:
Advantage You can use the constructors to make it easier to define tuple templates. For example, you can have:
template = new SCTuple(key)
instead of
template = new Tuple(key,new Field(String.class));
Advantage You can simplify the code to retrieve data from the tuple. For example, you can have:
data = sctuple.getData()
instead of
data = (String)tuple.getField(1).getValue()
Advantage Your class that extends SubclassbleTuple can do validity checking of the Fields so that problems of applications that write "invalid" Tuples can be avoided.
Disadvantage The same version of the SubclassableTuple must be available on all of the clients and at the server.
It is important to note that since a SubclassableTuple is not a subclass of Tuple, one does not have to be concerned that a Tuple template will accidentally match a user defined SubclassableTuple.
Let's take a look at an example that is similar to example1 but uses SubclassableTuple. The following are just fragments of the code, for the complete source, look at the source for Example2.java
Example2Tuple mytuple;
mytuple = new Example2Tuple("Key1","Data1");
ts.write(mytuple);
mytuple = new Example2Tuple("Key2","Data3");
ts.write(mytuple);
The Example2Tuple is defined as shown below.
class Example2Tuple
extends SubclassableTuple
implements Serializable {
public Example2Tuple(String key,String data) {
super(key,data);
}
public Example2Tuple(String key) {
// build template if only one operand
super(key,new Field(String.class));
}
public String
getData() throws TupleSpaceException {
return (String)this.getField(1).getValue();
}Now we will read one of the above Tuples using the first field as a key and then extract the contents of the second field.
Example2Tuple template = new Example2Tuple("Key2"));
mytuple = (Example2Tuple)ts.take(template);
String data = mytuple.getData();If you compare this to Example1, you will see that the interface to TupleSpace has been simplified. In fact, a subclass of SubclassableTuple can be made into a JavaBean and make it very easy to use in a Java IDE.
No Programmer's Guide would be complete without a "Hello World" example. In this example, we will give yet another example of TSpaces coding and also show how one would convert a JavaSpaces program to TSpaces. The first chapter from the excellent book, JavaSpaces Principles, Patterns, and Practice is available online This chapter contains a HelloWorld example and uses it to introduce JavaSpace programming. We will use a somewhat different example and show both the JavaSpaces and TSpaces example. For our example, we will show simple message passing between 2 programs. One program will send a message to the "World" program and then wait for a reply. The "World" program will wait for a message and when it gets it, it will send a reply back to the message's sender.
It is fairly easy to modify a JavaSpaces application to run under TSpaces. Both systems have a common heritage but there are some terminology differences. Instead of defining Tuples, with JavaSpaces, you define classes that implement the Entry class. These Entry objects then have Public instance variables that correspond to the TSpaces Field objects. One can fairly easily convert a JavaSpaces class that implements Entry into a TSpaces SubclassableTuple class. For this example, the Message class is the Entry object that we need to convert. The JavaSpaces version is:
import net.jini.core.entry.Entry;
import java.io.Serializable;
public class Message implements Entry {
public String to;
public String from;
public Serializable message;
public Message() {
}
public Message(String to) {
this.to = to;
this.from = null;
this.message = null;
}
public Message(String to, String from, Serializable message) {
this.to = to;
this.from = from;
this.message = message;
}
}The equivalent TSpaces Message class is:
import com.ibm.tspaces.*;
import java.io.Serializable;
public class Message extends SubclassableTuple {
public Message() throws TupleSpaceException {
super(new Field(String.class),new Field(String.class),new Field(Serializable.class));
}
public Message(String to) throws TupleSpaceException {
super(to,new Field(String.class), new Field(Serializable.class));
}
public Message(String to,String from, Serializable message) throws TupleSpaceException {
super(to, from, message);
}
//
// get/set methods for Destination, From and Message follow
//
public String getDestination() throws TupleSpaceException{
return (String)getField(0).getValue();
}
public void setDestination(String to) throws TupleSpaceException{
getField(0).setValue(to);
}
public String getFrom() throws TupleSpaceException{
return (String)getField(1).getValue();
}
public void setFrom(String to) throws TupleSpaceException{
getField(1).setValue(to);
}
public Serializable getMessage() throws TupleSpaceException{
return getField(2).getValue();
}
public void setMessage(Serializable message) throws TupleSpaceException{
getField(2).setValue(message);
}
}The major changes are:
JavaSpace implements the Entry Interface while TSpaces extends the SubclassableTuple class.
With JavaSpaces, you define the tuple data that is written to the space with public instance variables whereas with TSpaces accessor methods are used to get and set the tuple Fields.
Although the TSpaces implementation is much longer, it is mostly boilerplate get/set methods. With TSpaces you can also use a Tuple object and not need a user defined tuple class. An example of this is shown later.
The class Helloworld then implements both halves of the message passing logic. It uses a parameter on the command line to identify itself and then sees if it has a message waiting. If so, it replies to it. If not, it sends a message to "World" and then waits for a reply.
The HelloWorld class for JavaSpaces is:
import jsbook.util.SpaceAccessor;
import net.jini.core.lease.Lease;
import net.jini.space.JavaSpace;
public class HelloWorld {
public static void main(String[] args) {
String myName = "World";
boolean needReply = false;
if ( args.length > 0)
myName = args[0];
try {
JavaSpace space = SpaceAccessor.getSpace();
Message template = new Message(myName);
Message msg = (Message)space.takeIfExists(template,null,long.MAX_VALUE);
if ( msg == null) {
msg = new Message("World", myName,"Hello World");
needReply = true;
} else {
msg = new Message(msg.from,myName,"Hi");
}
space.write(msg, null, Lease.FOREVER);
if (needReply) { // Wait for a reply
msg = (Message)space.take(template,null,long.MAX_VALUE);
}
} catch (Exception e) {
e.printStackTrace();
}
} //main
} // classThe HelloWorld class for TSpaces is:
import com.ibm.tspaces.*;
public class HelloWorld {
public static void main(String[] args) {
String myName = "World";
boolean needReply = false;
if ( args.length > 0)
myName = args[0];
try {
TupleSpace space = new TupleSpace();
Message template = new Message(myName);
Message msg = (Message)space.take(template);
if ( msg == null) {
msg = new Message("World", myName,"Hello World");
needReply = true;
} else {
msg = new Message(msg.getFrom(),myName,"Hi");
}
space.write(msg);
if (needReply) { // Wait for a reply
msg = (Message)space.waitToTake(template);
}
} catch (Exception e) {
System.out.println(e);
}
} //main
} //classAs you can see, other than import statements, there are only minor changes required.
The JavaSpace invocation of SpaceAccessor is replaced by a call to the TupleSpace constructor.
The JavaSpaces command write command is replaced with the TSpaces write command with a slightly different signature.
The JavaSpace read and take with a long wait interval are replaced by TSpaces waitToRead and waitToTake commands.
The JavaSpace takeIfExists command is replaced by the TSpaces take command.
You can also just use the Tuple class to replace the need for the Message class. For many applications, this is simpler than defining a SubclassableTuple. We have included below the code for HelloWorld using only the Tuple class to define the Tuples that are written to the space.
package com.ibm.tspaces.examples.helloworld;
import com.ibm.tspaces.*;
import java.io.Serializable;
public class HelloWorldT {
public static void main(String[] args) {
String myName = "World";
boolean needReply = false;
if ( args.length > 0)
myName = args[0];
try {
TupleSpace space = new TupleSpace();
Tuple template = new Tuple(myName,
new Field(String.class),new Field(Serializable.class));
Tuple msg = space.take(template);
System.out.println(msg);
if ( msg == null) {
msg = new Tuple("World", myName,"Hello World");
needReply = true;
} else {
msg = new Tuple((String)msg.getField(1).getValue(),myName,"Hi");
}
space.write(msg);
if (needReply) { // Wait for a reply
msg = space.waitToTake(template);
System.out.println(msg);
}
} catch (Exception e) {
System.out.println(e);
}
}
}There are other differences between JavaSpaces and TSpaces not shown in the example above.
Both have Transaction support but they are implemented quite differently.
TSpaces does not have Lease objects but you can specify an expiration interval for Tuples.
Both have methods for requesting to be notified when a Tuple matching a template is written to the space but the implementation is different.
TSpaces supports many additional commands such as multiWrite(), multiRead(), multiTake(), count(), update() and others that are not implemented in JavaSpaces. Although the same end result can be achieved with JavaSpaces, it is more difficult and often more inefficient since multiple client/server interactions are required.
TSpaces supports the ability to build indexes on the fields in a Tuple and issue query commands against the indexes.
The read and take methods return a single matching tuple. If more than one matches, then it will return the only one of the matching tuples. However there are TupleSpace methods that will handle multiple matches.
countN(template)
This
returns the integer count of the number of Tuples that match the
specified template Tuple.
multiRead(template)
This
returns a Tuple that contains a set of SuperTuples that match the
specified template SuperTuple.
multiTake(template)
This returns a Tuple that
contains a set of SuperTuples that match the specified template
SuperTuple and removes all of these SuperTuples from the server.
Here is some sample code that makes use of countN and multiRead.
Tuple template = new Tuple("Key2",new Field(String.class));
int count = ts.countN(template);
if (count > 0) {
Tuple tupleSet = ts.multiRead(template);
if ( tupleSet != null) {
for( Enumeration e = tupleSet.fields(); e.hasMoreElements(); ) {
Field f = (Field)e.nextElement();
Tuple tuple = (Tuple)f.getValue();
data = (String)tuple.getField(1).getValue();
...
}
}
}First we create a template Tuple that will be used to select the Tuples from the server. The next line issues countN to get the count of matches. If we have 1 or more matches, then we issue the multiRead request to the tupleSpace using the template from earlier. This returns a tuple "tupleset" that represents the set of matching tuples. So "tupleset" contains one or more fields; the value of each field is one of the tuples returned by the multiRead. So the next step is to use an Enumeration to extract each of the returned Tuples. The fields() method returns an enumeration that can be followed to access each individual Tuple. Once the individual Tuple is accessed, the getField(int).getValue() method is used to access the data in the Tuple.
One of the most useful features of TSpaces is the ability to register with the server to be informed when specific types of events occur. For example, you might want to be informed whenever a Tuple that matches a specific template is written to the Server by any client. With TSpaces this is done by using the TupleSpace.eventRegister() method to indicate the type of event you are interested in and specifying the Callback object that you want to have handle the event.
The following are just fragments of the code, for the complete source, look at Example3.java to see the code in context. Also, the whiteboard sample program is an example of a real application that makes use of eventRegister to support multiuser communication.
Tuple template = new Tuple( "Key2", new Field(String.class) ); Example3Callback callback = new Example3Callback(); boolean newThread = true; // default is false int seqNum = ts.eventRegister(TupleSpace.WRITE, template, callback, newThread ); ... ts.eventDeRegister(seqNum);
The above sets up a template Tuple that describes the format of Tuples that we are interested in. The eventRegister() method tells the server to watch for a Tuple being written to the TupleSpace that matches the template Tuple. When this occurs, it should invoke the call() method for the Example3Callback object. The setting of newThread to true indicates that a new Thread should be started to process the callback.
When you no longer want to be informed of Tuple events, you can remove the event registration with the eventDeRegister() method.
class Example3Callback implements Callback {
public boolean
call(String eventName,String tsName,int seqNum,SuperTuple tuple,boolean isException) {
if (! isException) {
... process the tuple passed to this method.
String data = (String)tuple.getField(1).getValue();
} else {
... handle exception
}
return false;
} // call()
} The above defines a class that implements the TupleSpace Callback interface. The Callback interface requires that it implements the call method with the following parameters:
eventName - String that indicates what type of TupleSpace action "Write" or "Delete" caused this call. Note that only events that a cause a Write or Delete to be done are reported. A READ operation at the server is not reported.
tsName - Name of the TupleSpace.
seqNum - Sequence number of the eventRegister call.
tuple - The Tuple that matched the template.
isException - A boolean that if true indicates that there was an exception processing the event. In the case of an exceptional condition occurring on the server, isException will be true and the exception will be inside the tuple, ( i.e., the tuple will contain one field that is a TupleSpaceServerException.)
If the newThread variable in the above eventRegister call had been false, then the method would have been invoked out of the server communication thread. It should do something really quick and return, that is it should not take up lots of time. Typically, it might just verify that isException is not true, and then queue up the tuple and wake up some other thread that will do the real processing. If the newThread variable is set true, then a new Thread will be started to process the response and this Thread has the freedom to issue other TupleSpace commands.
Normally this method would return false. It would return true only if this is the last call this callback class is expecting.
Warning:
You cannot issue TupleSpace operations, (e. g.
ts.write ) inside the call() method or within any methods that are
called by the call() method. unless the newThread option is
set true. Otherwise this will cause the client thread that handles
communication with the server to hang.
Warning:
Although the
SuperTuple that caused the event is passed to you in the callback,
that does not mean that you have "control" of the
SuperTuple. It may have also been passed to other clients which may
delete the Tuple before you have a chance to to do anything with it.
Depending on your purpose for getting the event, you may want to do
a "take" of the SuperTuple to ensure ownership.
Furthermore, there's no guarantee that any one thread will be able
to "take()" the tuple if there are multiple threads registered
for the notification event -- some other synchronization mechanism
is necessary if determinism is needed.
There are a number of other TupleSpace methods that you should know about. Look at Example6.java to see the code in context.
The TupleSpace.getVersion() method returns a String that
indicates the current level of the TSpaces client library. It is in
the form "V.L.M". Where "V" is the Version and
is currently 1; "L" is the Level number; and "M"
is the Modification number.
For example: 1.0.1
The intent is that the "V" version number will change only for a very major change. The "L" Level number will change when new major new features are added and/or incompatible changes are made. The "M" Modification number will change when a new level of compatible code is distributed to fix problems and possibly add minor new functions.
The TupleSpace.status(server) method returns the status of the given server. This is a useful way to verify that the server is active before you try to create a TupleSpace. status() returns a simple Tuple that contains "Running" or "NotRunning" in the first Field. The 2nd Field contains the TSpace Server Version number.
Tuple active = TupleSpace.status(Host,Port);
//
if ( active == null || active.getField(0).getValue().equals(TupleSpace.NOTRUNNING))
System.out.println("Server not running on " + Host+":"+Port );
else
System.out.println("Server Version "+active.getField(1)+" is active on " +Host+":"+Port );
The TupleSpace.exists(Name,Host) method returns true if the named TupleSpace exists on the Server at Host:port.
The delete(SuperTuple template) method will delete the Tuples that match the specified template Tuple.
The deleteAll() method will delete all of the SuperTuple objects in the space. This is equivalent to the following:
Tuple all = new Tuple(); ts.delete(all); SubclassableTuple scall = new SubclassableTuple(); ts.delete(scall);
The Tuple with no Fields acts as a wildcard that matches all Tuple objects.
While we are on the subject of wildcards, if you want to have a wildcard for one of the Fields that will match any object, then you would use the Serializable.class for the Formal class.
ts.write("key","a string");
ts.write("key",new Integer(99));
Tuple template = new Tuple("key",new Field(Serializable.class));
Tuple result = ts.multiRead(template);The above command would pick up both Tuples because the Formal Field matches any Field that contains any Serializable object.
There are takeAll(), readAll(), countAll() commands that have similar scope as deleteAll().
The multiWrite(Tuple tuples) method is an efficient way to write a large number of Tuples to the server. For example, during initialization, you may want to load the server with an initial set of data.
Tuple multi = new Tuple();
for (int i=0; i<10;i++) {
Tuple nextTuple = new Tuple("Test6",new Integer(i),"Tuple# "+i);
multi.add(new Field(nextTuple)); // add Field to Tuple
}
TupleID[] ids = ts.multiWrite(multi);The above code creates the "multi" Tuple and then adds 10 Fields to it, where each Field is a Tuple that is to be written to the TupleSpace. The multiWrite method returns a array of TupleIDs, one for each of the individual Tuples.
Note: each Tuple included in the multiWrite must be unique, The following code would not be equivalent to the previous example. It would write 10 identical copies of the same Tuple.
Tuple multi = new Tuple();
Tuple dupTuple = new Tuple("Test6",new Integer(0),"Tuple#0");
for (int i=0; i<10;i++) {
dupTuple.setValue(1,new Integer(i));
dupTuple.setValue(2,"Tuple# "+i );
multi.add(new Field(dupTuple));
}
TupleID[] ids = ts.multiWrite(multi);The update(tupleId,newTuple) method will update a specific Tuple in the space.
Tuple newTuple = new Tuple("Data1");
TupleID id = ts.write(newTuple);
Tuple updatedTuple = new Tuple("UpdatedData1");
id = ts.update(id,updatedTuple);The above code writes a Tuple to a space. The write() method returns a TupleID that represents the uniqueId of this Tuple. This TupleID is then used to update the original Tuple. The TupleID can also be obtained by calling the getTupleID() method for a tuple that was read from the Space. The TupleID for a Tuple is persistent and is valid across restarts of the Server.
There is also a multiUpdate command that is similar to MultiWrite.
Given an instance of TupleID, one can also use the readTupleById(tupleid) and the deleteTupleById(tupleid) commands to read or delete specific Tuples.
One can specify a number of options at the time a space is created using the ConfigTuple object.
FIFO Boolean
Boolean.TRUE specifies that the results will be returned in FIFO order. The default is that results are returned unordered.
PERSISTENCE Boolean
Boolean.FALSE specifies that the TupleSpace will not be saved across server restarts. The default is that the space will be persistent (assuming that the server has been setup to support persistent spaces).
DESCRIPTION - String
A String that describes the space.
CLASS_SPACE
HANDLER
OPT_KEEPALIVE Boolean
ConfigTuple config = new ConfigTuple();
config.setOption(ConfigTuple.OPT_KEEPALIVE,Boolean.TRUE);
TupleSpace ts = new TupleSpace("mySpace","test.company.com",config);
For more information on TCP Keep-Alive in TSpaces,
please refer to the section,
TCP Keep-Alive in the
TSpaces Administration Guide.
TSDBTYPE String
TupleSpace._DBTYPE_MMDB or TupleSpace._DBTYPE_SMALLDB
Example:
ConfigTuple config = new ConfigTuple();
config.setOption(ConfigTuple.PERSISTENCE, Boolean.FALSE );
config.setOption(ConfigTuple.FIFO, Boolean.TRUE );
config.setOption(ConfigTuple.DESCRIPTION, Sample Space );
config.setOption(ConfigTuple.TSDBTYPE, TupleSpace._DBTYPE_MMDB );
TupleSpace ts = new TupleSpace(MyTsName,host, config);There are also some options that one can assign when configuring the indexes for a space. These options are specified on the CreateIndex command which is optionally issued after you have created a space.
INDEX_UNIQUE Boolean
Boolean.TRUE specifies that the index can only have unique values. Any attempt to write a Tuple with a duplicate index value for the specified index will fail.
NO_INDEX Boolean
Boolean.TRUE specifies that Fields with the specified names are not indexed. This is useful when you want to have a named field in your Tuples but you don't want the overhead of building indexes.
Example:
TupleSpace ts = new TupleSpace(MyTsName,host);
// build index1 as a Unique index
ConfigTuple ct = new ConfigTuple("index1");
ct.setOption(ConfigTuple.INDEX_UNIQUE,Boolean.TRUE);
index = ts.indexCreate("index1",String.class,ct,true);
// specify that "Name" is a named Field not to be indexed
ct = new ConfigTuple("Name");
ct.setOption(ConfigTuple.NO_INDEX,Boolean.TRUE);
ts.indexCreate("Name",String.class,ct,true);
Normally when a client writes a tuple to a space it will stay in the space until some client does a take or delete for the Tuple. However one can specify an expiration time value (in milliseconds) for the Tuple so that after the time expires, the Tuple will be automatically deleted from the space.
Tuple myTuple = new Tuple("Hello","World);
myTuple.setExpire(5*60*1000); // expire in 5 minutes
ts.write(myTuple);
SuperTuple result = ts.read(myTuple); // will return myTuple
Thread.sleep(5*60*1000);
result = ts.read(myTuple); // will return nullWhen the tuple expires, it will no longer be accessable by any read or take commands. However it is not physically deleted at that time. There is an ExpireManager that is invoked at intervals determined by the server configuration options that will physically delete the expired tuples. The default interval is 60 seconds. Note that the deletion is reported to the eventRegister callbacks only when it is physically deleted, not when the tuple expires. It is reported with an event name of TupleSpace.EXPIRE.
NOTE: different operating systems and different implementations of JVMs have varying granulations of timers. You may get inconsistent results for timer values less than 100 milliseconds.Our examples up to this point have all used Tuples with basic Java types like String and Integer. However, subject to certain restrictions, any Java object regardless if defined by the Java Language or by the user can be placed in a Tuple and written to and read from TSpaces.
The restrictions are:
The object must be Serializable.
An equivalent class file for the object must be available to both the clients and the TSpaces server. We will, however, describe how to avoid this restriction.
The following are just fragments of the code, for the complete source, look at Example4.java to see the code in context.
Example4Obj obj = new Example4Obj("User Object 1");
Field f2 = new Field(obj);
ts.write("Key1",f2);
obj = new Example4Obj("User Object 2");
f2.setValue(obj);
ts.write("Key2",f2);
Tuple template = new Tuple("Key2",new Field(Example4Obj.class));
Tuple mytuple = ts.take(template);
Example4Obj objreturned = (Example4Obj)mytuple.getField(1).getValue();The above looks much like the previous example except that previously, the 2nd Field was a String.
Now we will look at the definition of the class:
class Example4Obj implements Serializable, Comparable {
static final long serialVersionUID = -2475757193974756987L;
String userdata;
public String
getData() {
return userdata;
}
public boolean
equals( Object other ) {
if ( (other == null) || ! (other instanceof Example4Obj) )
return false;
if ( userdata == null )
return ( ((Example4Obj)other).getData() == null);
return userdata.equals(((Example4Obj)other).getData());
} // end equals()
public int
compareTo( Object other ) {
if ( (other == null) || ! (other instanceof Example4Obj) )
return +1;
if ( userdata == null )
return ( -1);
return userdata.compareTo(((Example4Obj)other).getData());
} // end compareTo()
}
The things to note in this definition are:
The class is declared to implement Serializable.
An equals() method has been added. This is required if you you plan to use the object as a search field in a template. The equals() method will override the Object.equals(Object other) method. This method will be invoked on the server to match a supplied template (represented by "this") to the instances of Example4Obj that are in the database. You should be careful to handle all the possible cases such as null values.
This shows an example of having specified serialVersionUID. This string is generated by running the serialver program that is distributed with the Java JDK and the class. The resulting output is then cut/pasted into the .java file. The advantage of doing this is that you can make minor changes to the class at the client without having to copy the class file to the ClassPath used by the TSpaces server and restart the Server. The disadvantage is that if you make changes to the client version that are NOT compatible with the server version, you may lose data or get an exception when you try to write or read the object from the TSpaces server.
The class is declared to implement Comparable and a compareTO method has been added. By implementing Comparable, this object could be used in a named field and the index support in TSpaces can be used to search for a match or a range.
In many cases, it is not feasible to ensure that the class definition for a client defined object be available in the TSpaces server. If you are passing the objects between 2 clients, you may only want the 2 clients to understand the classes. The best way to solve this problem is to serialize the object into a byte array prior to sending it to the server and just send the server a field that is defined as byte[]. The FieldPS object is designed to do this Field Pre Serialization. FieldPS is a subclass of Field and acts like a Field object except when handed an Object, it automatically serializes it into a byte[] object and stores that. When requested to return the value of the Field, it automatically returns the deserialized value.
Here is an example of the use of FieldPS. The following are just fragments of the code, for the complete source, look at Example4a.java to see the code in context.
Example4aObj obj = new Example4aObj("User Object 1");
FieldPS f2 = new FieldPS(obj); // <---changed
ts.write("Key1",f2);
obj = new Example4aObj("User Object 2");
f2.setValue(obj);
ts.write("Key2",f2);
Tuple template = new Tuple("Key2",new FieldPS(Example4Obj.class));
Tuple mytuple = ts.take(template);
Example4Obj objreturned = (Example4Obj)mytuple.getField(1).getValue();
Note that only the statement that defines the 2nd Field has changed. All of the rest of the code is identical. It is important to also note that you use FieldPS when you define the template to retrieve the tuple. Also, any attempt to do matching on the FilePS field will probably fail since 2 equal objects may very likely have different serialized representations. But you can have one of the other Tuple Fields be the value that you want to match on. For example, if you wanted to match on the "userdata" field in the Example4Obj object, you could add another Field to the Tuple that would contain the value of userdata;
ts.write("Key1", obj.getData(), new FieldPS(obj));
There are 4 types of Queries: MatchQuery, IndexQuery, AndQuery, and OrQuery.
There is also an XMLQuery, but this is described in the XML section. In the set of sample programs that are provided with tspaces, there is the SuperHeros sample that is a good example of using the query facilities of TSpaces.
If a Tuple is used in a query-type method (read, take, multiRead, or count) that contains Queries in its fields, these Queries will be combined into a single AndQuery by default. If the fields of the tuple are not Queries, the tuple will be converted into a MatchQuery.
Match queries take a SuperTuple, and return everything that matches. For example:
Tuple tup = new Tuple( "tuple1", "3" ); Tuple result = ts.multiRead( new MatchQuery( tup ) );
This code will return all tuples that exactly match the given tuple. The result is the same as if template match was used. (i.e. the following would be equivalent to the above.)
Tuple result = ts.multiRead("tuple1","3");An IndexQuery takes an index name and an Object representing a value to be found in the index:
Tuple result = ts.multiRead(new IndexQuery( "foo", new Integer(8) ));
This code will return all tuples with the Integer value 8 in the field named "foo".
Alternatively, the Object passed to the IndexQuery can be a Range. The Range constructor takes two parameters (upper and lower bound). If neither upper nor lower bound is null, it's a normal exclusive range (i.e. everything strictly greater than the lower bound and strictly less than the upper bound). If either bound is null, it's an open-ended range (e.g. lower bound null will return everything less than the upper bound):
Range myRange = new Range(null, new Integer(8)); Tuple result = ts.multiRead(new IndexQuery( "foo", myRange ));
This example will return all tuples with a value less than 8 in the field named "foo".
The IndexQuery requires the use of named Field objects.
For Example:
Field f = new Field("keyfld",new
Integer(345765))
will define a Field with the name of
"keyfld". All Tuples that contain a "keyfld"
Field will be indexed.
The constructors for AndQuery and OrQuery take two Queries. These queries will both be applied, and the results combined. These can be used to nest queries to arbitrary depths:
ts.multiRead(new AndQuery(
new OrQuery(
new IndexQuery("index1", new Range(new Integer(3), new Integer(7)) ),
new IndexQuery("index2", new Range(new Integer(6), new Integer(9)) )
),
new MatchQuery(new Tuple(new Field(Integer.class), new Field(String.class) )
)
);This query will return all tuples with structure matching (Integer,string) and having either a value (strictly) between 3 and 7 in the "index1" field, or a value strictly between 6 and 9 in the "index2" field. Note that order may be important here. Currently the Query is not optimized such that the "smallest" query is always applied first.
Restrictions:
These query facilities only apply to the MMDB TupleSpaces. If you specify "SMALLDB" in the configuration, you can only use the simple matching described in the previous sections.
You can index any of the following Objects: String, Integer, Long, Short, Byte, Character, Double and Float and any Object that implements the Comparable interface.
In what has been discussed so far, client application programs create their own version of a space and invoke various operations like read, write, blocking read, blocking write etc. In general, a client program consists of a set of operations performed on one or more spaces. It is not hard to think of applications where all operations must appear as one atomic unit to anyone interested in the actions performed by the application. This suggests a transaction model.
Each operation unless specified to be part of a transaction is considered to be a stand-alone transaction. To execute two or more operations as a transaction, the client application creates an instance of a Transaction class. One or more TupleSpace objects are then added to the Transaction object using the addTupleSpace() method of the Transaction class. It would then call the beginTrans() method of the Transaction class to start the Transaction. Any operation performed on a TupleSpace instance that has been added to a Transaction object now belongs to the transaction corresponding to that Transaction object. The commitTrans() method of the Transaction class is called to make the effect of operations in the transaction visible to other transactions. The abortTrans() method can be called to undo all of the operations that belong to the current transaction.
The following is an example of how a client program can make use of the transaction model. In the example, either all 3 of the writes will succeed or none of them will succeed. Also none of the writes are visible to other clients until the commit is processed. You can also look at Example7.java to see a more complete example of how the Transaction support is used.
Transaction trans = new Transaction();
TupleSpace ts = new TupleSpace("testing");
trans.addTupleSpace(ts);
trans.beginTrans();
ts.write("email", "to", "dick@sun");
ts.write("email", "text", "Hello");
ts.write("email", "from", "jane@ibm");
trans.commitTrans();Note that the transaction support is not Thread safe. By this we mean if you have a related Transaction and TupleSpace object and you begin a transaction and then let 2 or more threads issue commands to that TupleSpace instance, when one of the Threads issues a commit, there is no guarentee that the other thread will not have an active command that has not completed. In this case the results are unpredictable. If you are using multiple Threads and Transactions, then you either have to do your own coordination between the threads or you should ensure that each Thread has its own instance of the TupleSpace and builds its own Transaction object.
When you have multiple clients that are using transactions against the same space it is possible that you could have a deadlock occur. For example, transaction A has written Tuple1 and wants to then read Tuple2. Transaction B has written Tuple2 and then wants to read Tuple1. Depending on the timing of these operations, a deadlock could occur. At that point, one of the clients will have a TransactionDeadLockException thrown. Because this is a recoverable error, the client can catch this exception and reissue the command.
while (true) {
try {
trans.beginTrans();
...
trans.commitTrans();
break;
catch (TransactionDeadlockException e) {
}
}
TSpaces supports a number of communication methods to support communication between the TSpaces Client and the TSpaces server. The default and most commonly used is TCPIP Sockets. There is also a Secure Socket Layer (SSL) method, a SOAP comunication path method, a local memory queue method for when the client and server are in the same JVM, and what is known as the SimpleSocket method designed to support non Java clients.
The communication method is chosen by the client calling the setTSCmdImpl() method. The possible values are:
TSCmd.TSCMD_SOCKET_IMPL (default)
TSCmd.TSCMD_SSLSOCKET_IMPL
TSCmd.TSCMD_SIMPLE_IMPL
TSCmd.TSCMD_SOAP_IMPL
TSCmd.TSCMD_LOCAL_IMPL
For Example:
TupleSpace.setTSCmdImpl(TSCmd.TSCMD_SSLSOCKET_IMPL); host = host+":"+TupleSpace.DEFAULT_SSL_PORT; // 8202 TupleSpace ts = new TupleSpace(tsName,host);
Each of the above communication options are described later. The rest of this section applies to the default communication method.
An important factor to keep in mind with the socket interface is that by default, the access to multiple spaces on a server all share a single socket interface. So if your client is using 5 spaces on a specified server there is normally only one socket communication path. Further, this is also true of all other clients that share the same JVM. (applets and servelets are common cases of this) The reason for this default behavior is that each connection from each client uses up a socket on the server and server sockets are a limited resource.
However, you can change the default behavior with the following call:
setNewServerConnection(true);
All subsequent call to the TupleSpace constructor will then result in a new communication path to the server. It then however becomes your responsibility to cleanup these connections when you are done with the space.
The cleanup() method is used to tell the server that you are finished with all of the TupleSpaces that you have created.
TupleSpace.cleanup();
The above will terminate all connections to all of the Servers that you have connected to. It should only be used when you have finished with all TupleSpace activity.
If you have followed the instructions above to force a separate communication path, then you can cleanup the connection for a specific space with the following code:
setNewServerConnection(true); ts = new TupleSpace(name,host); ... TupleSpace.cleanup(ts);
If you want to terminate all the connections to a specific server, you can specify the host name and port.
TupleSpace.cleanup(host,port);
The reconnect() method is used to tell the server that you want to create a new connection to the server for an existing TupelSpace instance.
Where you need to use reconnect() is to have your client be able to recover and continue when a server goes down and then is restarted or when the network connection to the server is broken for a period of time. What you need to do on the client when the server crashes or goes down is to recognise that it has gone down by intercepting the exception in the event register callback or by catching the exception on the next Tuplespace operation. Then you need to make a call to reconnect() to cleanup the existing connection and then create a new connection
So it might look something like this.
TupleSpace ts = null;
Tuple result = null;
ts = new TupleSpace(space,host);
while (result=null) {
try {
Tuple result = ts.waitToTake(template);
} catch(TupleSpaceServerCloseException e) {
System.out.println(e);
System.out.println("Path to Server was closed normally");
break;
} catch(TupleSpaceCommunicationException ce) {
System.out.println(ce);
System.out.println("Server crashed. we will wait for it to restart");
TupleSpace.setConnectionTries(0); // retry forever
TupleSpace.setConnectionWaittime(60); // every 60 seconds
ts.reconnect();
result = null;
} catch(TupleSpaceException tse) {
System.out.println(tse);
System.out.println("Some other Problem");
System.exit(1);
}
}or in a callback.
public boolean
call( String event,String space,int seq,
SuperTuple tuple,boolean isException) {
if ( isException) {
ts.reconnect();
return true;
} else {
...For the programmer that wants to have more control over the connections to the server, the command interface to the server is represented by the TSCmd abstract class. The current implementation of TSCmd can be accessed by the getTSCmd() method of TupleSpace. Refer to the TSCmd javadoc for more information.
???? Note, this section needs to be updated to clarify that the user and password are set for a server connection and not indepentently for each space. Document how to use cleanup to change user. Also someplace we need to document the new facility to have multiple connections to the same server. refer to it here.
A client application can identify the userid that it is associated with. This is done either by:
Using the TupleSpace.setUserName(String) and setPassword(String) methods.
Using additional userid and password parameters on the TupleSpace constructor that allocates a Space.
If specified, then the SHA-1 Secure Hash Algorithm is used to create a hashed password and the userid and hashed password are sent to the host where they will be verified by the server. If the user and password are not specified, then the userid is assigned as "anonymous". For many applications, running as anonymous will be completely satisfactory.
Access control is on a TupleSpace operation level; each operation defined on a TupleSpace has an associated list of AccessAttributes that must be satisfied by any client trying to execute that operation. For example, the take operation requires either the general Read and Write AccessAttributes or the more specific Take AccessAttribute.
The access levels for a TupleSpace are granted to a Principal based on an Access Control List (ACL) for that space. A Principal is any defined User or Group. The Acl contains entries that define a Principal and the permissions that the Principal is granted.
Like most systems, groups are just groupings of users and users can be members of different groups. Permissions that are granted to a group are passed to all the groups and users that are members.
The access rights for a TupleSpace are granted when the TupleSpace is created. If not specified in the TupleSpace constructor, then a default Acl is assigned to the space when it is initially created.
Additional information on setting up access control at the server can be found in Setting up Access Control for the Server section of this document
Let's take a look at an example that is similar to Example1 but makes use of AccessControl. The following are just fragments of the code, for the complete source, look at the source for Example5.java
First we create a Tuple that describes the access permissions that we want to grant to this TupleSpace.
String owner = MyUserid; // some code to fill in the current userid
AclEntry ae1 = AclFactory.createAclEntry("Users",P_READ);
AclEntry ae2 = AclFactory.createAclEntry("anonymous",P_READ);
ae2.setNegativePermissions();
AclEntry ae3 = AclFactory.createAclEntry(owner,new Permission[] {P_READ,P_WRITE});
Acl myacl = AclFactory.createAcl("ExampleTS",ownere,
new AclEntry[] {ae1,ae2,ae3});
Tuple permission = new Tuple((Serializable)myacl);
The above code creates an Access Control List (Acl) that will give:
"Read" access to everyone in the "Users" group.
"Remove "Read" access for the "anonymous" user, who is in the Users group.
"Read" and "Write" access to my userid.
TupleSpace ts = new TupleSpace("ExampleTS,
host,TupleSpace.DEFAULTPORT,null,
permission,
MyUserid,MyPassword);The above TupleSpace constructor now has more parameters than what we have seen in earlier examples. The 5th parameter("permission") is the Tuple that we built above. The 6th and 7th paramters are the userid and password for the user that is running this application. This information would have been obtained by prompting the user for it.
As a result of the above, your userid will be able to read and write to the TupleSpace and all other users except "anonymous" will be able to read from the space. The user "anonymous", which is the default userid when no user is specified, will not have any access to the space.
An alternative method for specifying the userid and password information is the following:
TupleSpace.setUserName(Myuserid);
TupleSpace.setPassword(MyPassword);
boolean exists = TupleSpace.exists("ExampleTS",host);
if (exists) {
ts = new TupleSpace("ExampleTS",host);
} else {
ts = new TupleSpace("ExampleTS",host,TupleSpace.DEFAULTPORT,null,permission);
}This method is preferred when the exists() method is used because the authorization must be set prior to invoking the exists method.
There are a number of special considerations that should be kept in mind when you are using TSpaces inside of an Applet.
Let's take a look at an example Applet. The following are just fragments of the code, for the complete source, look at the source for AppletTst1.java
appletEnvironment()
There are some operations that
TSpaces uses that would cause TSpaces to violate the applet
security constraints. However, you can tell TSpaces to avoid these
by adding the following statement somewhere early in your init()
method.
TupleSpace.appletEnvironment(true);
setNewServerConnection()
The default behavior is
that the communication path to a server from a client running under
a JVM are shared. This is discussed in an earlier
section. Therefore if there is any chance that different
instances of TSpaces Clients might run as applets (or servlets)
then the setNewServerConnection(true) method should be used
to force each path to be unique. But this means that the cleint is
responsible for removing those connections. See the Destroy example
below.
Determining Server location
Because of Applet
security restraints, the TSpaces server must reside on the same
system as the applet. As described in a later section, one can use
the HTTP Server included in TSpaces to
distribute the applet or one can run any standard HTTP server on
the TSpaces host system. In the downloaded applet, one can then
determine the host address for Tspaces by using the applet
getCodeBase() method.
URL url = getCodeBase();
String urlhost = url.getHost();
TupleSpace ts = new TupleSpace("myspace",urlhost)
destroy() method considerations
Because the
Netscape and InternetExplorer browsers contain a single Java
Virtual Machine that runs all applets, class resources that are
allocated by an applet remain allocated for the life of the browser
rather than the life of the applet. For this reason, there is an
applet destroy() method that can be used to deallocate
applet resources. It is important that the destroy() method be used
to free TSpaces resources by calling the TupleSpace.cleanup()
method. Also any eventRegister callbacks should be canceled
with the eventDeRegister() method.
public void
init() {
setNewServerConnection(true);
_ts = new TupleSpace("myspace",urlhost)
...
public void
destroy() {
try {
_ts.eventDeRegister(_seqNum);
} catch (TupleSpaceException tse) {
Debug.out(tse);
}
TupleSpace.cleanup(_ts);
_ts = null;
}
Other considerations
There appears to be a
requirement with Internet Explorer 4 or 5 to have the applet and
any classes required by the applet to either all be class files
named by the CODEBASE parameter or all be in the jar file named by
the ARCHIVE parameter in HTML. Strange!
Through the introduction of a new Field class, RegexField, regular expressions can be used to specify matching patterns for String fields. The regular expressions are evaluated at the TSpaces server. The implementation is based on the new java.util.regex package in Java 2, v 1.4. Please refer to that package's documentation for further information on Java's intrinisic regular expression support and syntax.
The snippet below demonstrates the usage of the new RegexField class:
import com.ibm.tspaces.RegexField;
import com.ibm.tspaces.Tuple;
import com.ibm.tspaces.TupleSpace;
// ...
TupleSpace _ts = new TupleSpace(_tsName, _tsServerName);
// write 2 tuples, each with String fields:
Tuple t1 = new Tuple("abcdefg");
Tuple t2 = new Tuple("10.78.345.77");
_ts.write(t1);
_ts.write(t2);
// match tuple t1 with a regex
Tuple template1 = new Tuple(new RegexField("[a-g]+"));
result = _ts.read(template1);
// match tuple t2, an IPv4 address string, with a regex
Tuple template2 = new Tuple(new RegexField("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"));
result = _ts.read(template2);
XML support has been extended in this release to include XPath 1.0 as a query language for XML fields. In addition, a single tuple may now contain multiple XML fields. The original XQL-based XML support has been retained for backwards compatibility.
TSpaces' XML support requires an XML parser in its classpath which is compliant with JAXP 1.1 or higher. Note that an XML parser is not bundled with TSpaces but rather is assumed to be provided by the JRE or the application deployer.
For the XPath support, TSpaces requires Xalan-Java 2.4.1 or higher in its classpath. Xalan-Java 2 can be downloaded from the Apache Xalan-Java Project. At development time, a standardized Java API for XPath was not available and DOM Level 3, which does provide an XPath API, was not finalized. As a result, the XPath API in Xalan was opted for, with the intention of folding in future XPath standards as they become available.
To write an XML document, create an XMLField, passing the document contents as a String. The following example code writes a trivial XML document into the default TSpace:
TupleSpace ts = new TupleSpace("xmlTest", server);
String xml = "<?xml version="1.0"?><MESSAGE>Hello World!</MESSAGE>";
Field xmlF = new XMLField(xml);
Tuple xmlT = new Tuple("myXML Tuple",xmlF);
ts.write(xmlT);
XPath-based queries are accomplished using the new XPathField class. On the TSpaces server, the queries are evaluated to boolean matches. The example below shows how the XPath expression is passed to the constructor of XPathField:
// create XML field
String xmlString = "<?xml version=\"1.0\" ?><doc><msg><Hello></msg></doc>";
XMLField xmlField = new XMLField(xmlString);
xmlField.setStorageMethod(XMLField.STORE_AS_XML_DOC);
// create tuple, add XMLField, write to tuplespace
Tuple tuple = new Tuple();
tuple.add(xmlField);
ts.write(tuple);
// retrieve the tuple using XPath:
Tuple template = new Tuple();
template.add(new XPathField("//msg = 'Hello'"));
SuperTuple result = ts.take(template);
In the next example, XPath is used to implement a complex barrier condition :
String xpath = "(count(/results/data) > 5) or (/results/status = 'terminated')";
Tuple template = new Tuple(new XPathField(xapth));
Supertuple result = ts.waitToTake(template);
Previous XML support in TSpaces was based on XQL. This has been retained for compatibility but it is recommended that applications use the new XML with XPath support described in the previous section.
TupleSpace ts = new TupleSpace("xmlTest", server);
String xml = "<?xml version="1.0"?><MESSAGE>Hello World!</MESSAGE>";
Field xmlF = new XMLField(xml);
Tuple xmlT = new Tuple("myXML Tuple",xmlF);
ts.write(xmlT);
...Currently, the XMLField constructor requires that you pass in the entire content of the XML document as a single String. This limitation will be fixed in the full release.
We should mention briefly the internals of the XML support. When a new tuple is written to a TSpace containing a XMLField, the XML string content is passed off and converted into a TupleTree. A TupleTree is essentially a tree of tuples, which as a whole mirrors the DOM (Document Object Model) tree of the XML document. Tuples are analogous to a node in the XML DOM tree. Each tuple is an instance of XMLTuple and contains a XTuple object. The XTuple object is an encapsulation of the XML specific data for a given node, and contains TupleID references to the DOM node's parents and siblings. For information on the exact contents of the XTuple object, see the JavaDoc.
To make a query on existing XML documents which have been written to the Space, we use the same query structure that supported AND, OR, and Index queries. For example, this code excerpt executes a simple query on the set of XML documents, assuming the previous code block has been executed:
...
String xql = "/MESSAGE";
Tuple result = ts.multiRead(new XMLQuery(xql));
System.out.println("Result tuples: "+result);
...The Tuples which are returned are the Tuples that contain an XMLField that describes an XML document that has one or more nodes that match the XQL query.
We need to explain here the exact XML query syntax supported by TSpaces. In this version of XML query support, we've decided to support a subset of the XQL language specification. XQL is a path expression based query language proposed to the W3C query workshop. For detailed information on XQL, check out the XQL FAQ. While reading the FAQ, please keep in mind that TSpaces only supports the core subset of XQL functionality, namely, those of Tagnames, Tagvalues, Descendants, and Attribute constraints.
Put another way, some of the more notable XQL functionality that is missing from TSpaces include:
boolean operators connecting multiple conditions (grouping)
sequences and ordering operators(; and ;;)
semi-joins
return operators(? and ??)
wildcard attributes.
To make the TSpace XML queries more
real, here are a couple of sample queries and what they do:
|
/ADDRESS |
Return all occurrences of the ADDRESS tag where it is anchored to the root of the document. |
|
ADDRESS//ZIP |
Return all occurrences of the ZIP tag where it is an eventual descendant of the ADDRESS tag, which occurs in the same document, but is not required to be anchored to the root. |
|
ADDRESS/STREET='CHANNING WAY' |
Return all occurrences of the STREET tag where its content is equal to the 'CHANNING WAY' string, and it is a direct child of the ADDRESS tag. |
|
/*/ADDRESS[@TYPE='temporary']/CITY |
Return all occurrences of the CITY tag where its parent node is ADDRESS, with an attribute of TYPE with value 'temporary'. The ADDRESS tag must have a parent which is anchored to the root of the document. |
In order to use the new XML support, the server needs to have access to a XML parser. We have used the XML Parser that is available on the IBM Alphaworks site. To download the xml4j.jar file that you will require, go to the http://www.alphaWorks.ibm.com/formula/xml site and request the download option. Specify the xml4j_2_0_15 release and download the zip file. Then extract the xml4j.jar file and save it somewhere convenient. To make it accessible to the server, this jar file needs to be in the classpath used when you start the server. If you are using the distributed tspaces.bat file to start the server, you can simply specify the JAR_XML environment variable.
set JAR_XML=c:\xml\xml4j.jar bin\tspaces
A Tuple can only have one XMLField.
XQL Return operators(?,??) are not supported.
The entire
XML document is returned if one or more nodes match the XQL query.
However the XMLField has a getQueryResult() method that
will return a set of TupleID objects that refer to the matching
nodes in the document TupleTree. Refer to the JavaDoc for more
information. An example of creating an XQL-Result document from
the information in the returned XMLField is in the
.../examples/xml/TestXTuple.java example.
If you followed the installation procedures then you have a directory on your system that might look like this:
yourname/
tspaces/
...
lib/
tspaces.jar
tspaces_client.jarLet's assume that this is a Unix system and you installed it in the /u/joe directory. The important file that you need to worry about is then /u/joe/tspaces/lib/tspaces_client.jar which contains the TSpaces client class files.
If you are using the Sun JDK without any IDE, you need to add the above jar file to the Java CLASSPATH environment variable or specify it for both the "javac" and "java" command line. For example:
export CLASSPATH=.:/u/joe/tspaces/lib/tspaces_client.jar
javac yourfile.java
java yourfile
or
javac -classpath .:/u/joe/tspaces/lib/tspaces_client.jar yourfile.java
java -cp .:/u/joe/tspaces/lib/tspaces_client.jar yourfileIf you are using any of the hundreds of IDEs like IBM Visual Age, Symantec Cafe, Kawa etc, then you would follow their instructions for integrating jar files into the environment. However, just because this is distributed in a jar file does not imply that this is setup as a JavaBean, it's not.
Note: There are certain client services such as the AddHandler facility that require access to TSpaces server classes. For those cases, you should add tspaces.jar to the CLASSPATH.