Advanced Features

[back]

This section deals with the advanced features the SQLSpaces have. In constrast to the operations described earlier these features are mostly not actually features that were contained in the first releases of Linda and therefore are not a common denominator of all Tuplespace implementations. However, some of these features were also added in some other implementations to increase the possibilities without changing the blackboard character of the Tuplespace idea.

Callbacks

In the standard model the Tuplespace server is a purely passive component of the system, which only acts on direct queries. This can sometimes be a bit annoying, e.g. if you want to get every tuple that is written into a space, but think a design that uses waitToTake is too inelegant. Of course you could use an infinite loop that takes away each tuple as soon as it arrives at the server. However, you often do not want to take away these tuples, but rather let them in the space. Indeed you could use waitToRead, but after one tuple is written into the space, the infinite loop would constantly fetch this tuple again, because after each loop it still matches the given template. Of course, this problem could be solved by using certain incrementing counters in the tuple fields that each new tuple should have, so after each loop you waitToRead for the next counter. However, this design obviously looks a bit complicated and there is another means called callbacks that satisfies such requirements.

A callback can be compared with a listener that is registered on server side. This registration contains a template tuple, a listener that should react in case of a matching operation and also a type of operation. This type is one of the following: write, delete, update, all. This listener needs to implement the interface Callback, that contains one method named call. To register a callback, you need to invoke the method eventRegister in the class TupleSpace. The return value of this method is an integer, that identifies this registration. It is called sequence number and it is needed if you want to cancel a registration (by use of the method eventDeRegister).

TupleSpace ts = new TupleSpace();
Tuple tmp = new Tuple(String.class, String.class);
Callback cb = new MyCallback();
int seqNo = ts.eventRegister(Command.WRITE, tmp, cb, false);

The last parameter defines whether the client should fork a new thread to process this callback or not. If it is clear that the procession is quite fast, this should be set to false, otherwise new threads should be created in order to not block the receiving thread of the client for too long. The class MyCallback may look like this:

static class MyCallback implements Callback {
        public void call(Command c, int seq, Tuple aT, Tuple bT) {
        System.out.println("Action " + c.toString() + " occured!");
                System.out.println("BeforeTuple:" + bT.toString());
                System.out.println("AfterTuple:" + aT.toString());
        }
}

The assignment of the variables afterTuple and beforeTuple is depending on the operations:

afterTuple beforeTuple
deletion (take) of tuple X insertion (write) of tuple X updationg X to Y null X Y X null X

[top]

Non-primitive datatypes

Normally only primitive datatypes are allowed in fields of tuples. The SQLSpaces support here all these primitive Java datatypes: boolean, byte, short, int, long, float, double, char and String (of course, String is not really a primitive datatype in Java, but despite many programming languages consider it as one ...). In addition to these SQLSpaces also support two complex datatypes: binary and xml.

Storing binary data in a field is done via byte arrays. These are then encoded into strings using the Base64 algorithm.

URL url = new URL("http://www.collide.info/logo.jpg");
InputStream stream = url.openStream();
byte[] imageBytes = new byte[stream.available()];
stream.read(imageBytes);
ts.write(new Tuple("Collide-Logo", imageBytes));

XML data can be stored as org.w3c.dom.Document instances. At the moment this only is more comfortable to do the string conversion manually, but it is planned to implement special matching features for these xml fields like XPath or XQuery.

[top]

Expiration

Often tuples are used as messages. However, in many cases such messages have a lifetime, after which they are invalid, outdated or somehow other worthless. In SQLSpaces tuples can also have a so-called expiration time, which defines when the server should delete them. This time is set in milliseconds which are counted from the moment of writing the tuple into the server. When a tuple expires it is deleted, which of course also triggers callbacks, that wait for matching delete events. By the use of expiration tuples can be used to facilitate regular "heartbeats" of all agents that participate in a system.

Tuple t = new Tuple("I am alive!", "temperature agent 1");
t.setExpiration(60 * 1000); // 60 seconds
ts.write(t);

[top]

Performance Tips for Scenarios with much data

If you have very large amounts of tuples (over one million), the default operations will do what they should do, but maybe slower that necessary for your case. If you e.g. have many many tuples that should be fetched by one single readAll command, it is not unprobable that you get memory problems, on the server as well as on the client. To prevent this, there is a readAll method that takes additionally to the template a windowsize and returns a tuple iterator. This iterator won't fetch all tuples but only windowsize tuples at the same time. The developer won't notice this flow control mechanism, but it will use less memory and the first results will come in much quicker.

Although this readAll might uses several database queries, it is guaranteed that the tuples the iterator returns represent the state of the SQLSpaces server at the time the iterator was created. So for instance it won't return matching tuples, that were written in the space after the readAll call!

for (Tuple t : ts.readAll(templateTuple, 100)) {
        ts.write(t);
}

Furthermore, it maybe the case, that you don't need the whole response from the server, e.g. if you just want to delete tuples or if you want to know, how many matches there are. In these cases there are the responseless equivalents of take, takeAll and readAll that are called delete, deleteAll and count.

// delete everything, faster than taking everything
ts.deleteAll(new Tuple());

The last point affects the operations, that return one tuple, i.e. read, take, waitToTake, waitToRead, delete. These operations internally use randomization to guarantee that when done iteratively they will hit eventually all matches. This randomization can be disabled so that you always get only the first hit. The methods for that have all a trailing "first" like readFirst, takeFirst, etc.

// only get the first tuple in the table
ts.readFirst(new Tuple(String.class));
// later calls will all return the same tuple, if there are no changes in the space

[top]

Transactions

Since SQLSpaces are based on a relational database management system, it is easy to support transactional operations. Therefore the class TupleSpace has the three methods beginTransaction(), commitTransaction() and abortTransaction(). After starting a transaction, all following operations of this connection will be handled transactionally. If then the transaction is aborted, all operations contained in it will be undone, and if it is commited all effects of the operation will be visible for other users.

TupleSpace ts = new TupleSpace();
ts.beginTransaction();
// do some stuff
if (errorOccured) {
        ts.abortTransaction();
} else {
        ts.commitTransaction();
}

Note: Please be aware, that the exact semantics of the transaction are depending on the underlying database. Especially the isolation level has to be considered. In MySQL (i.e. InnoDB) the default transaction isolation level is REPEATABLE READ, whereas HSQL only uses READ UNCOMMITTED. If you are unsure what that means, please refer to the corresponding Wikipedia article.

[top]

Advanced Matching

The basic matching algorithm with templates of formal and actual fields is extended by some other features that are described in the following:

  • Nulltuple: A nulltuple query is characterized by an empty template (new Tuple();) and matches all tuples in the space. A nulltuple query can be used to dump the whole space or just to delete everything.
  • ID-Query: If a template tuple has a set TupleID, this only matches a tuple in the space with this id.
  • Bounded Queries: Formal fields of a template can be supplied with borders, that values have to fit in to match this field. E.g. it is possible to query for tuples that have integer values between 5 and 10. All primitive datatype support interpretation of borders, which is then mapped to the Java compareTo method, i.e. also strings can be alphabetically bounded.
ts.write(new Tuple("temperature", 3));
ts.write(new Tuple("temperature", 6));
ts.write(new Tuple("temperature", 8));
ts.write(new Tuple("temperature", 12));

Field f1 = new Field("temperature");
Field f2 = new Field(Integer.class);
f2.setLowerBound(new Integer(5));
 f2.setUpperBound(new Integer(10));
Tuple template = new Tuple(f1, f2);
Tuple[] tuples = ts.readAll(template);
for (Tuple t : tuples) {
    System.out.println(t);
}
  • Wildcard matching: SQLSpaces are also able to deal with dynamic templates, that have variable number of fields and undetermined field types. Therefore it is possible to create so-called wildcard fields, that match any type and any number of fields. To create a wildcard field the static method Field.createWildCardField() is to be used. It is also possible to have more than one wildcard field in a template. Wildcard fields also match an empty field. So if a wildcard field is abbreviated by "*" the template "String, *, Integer" would match tuples with the signatures "String, Boolean, Integer", "String, Integer", String, String, Integer, Integer", but not "String, String" or "Boolean, String, Integer".
  • Semi-formal fields: It is possible to use fields that are not completely formal, but only half-formal. Currently these fields can only be used in combination with the type String. Such semi-formal string fields can contain asterisks (*) indicating that this location in the string should be interpreted variably. A semi-formal field "key" for instance would match fields "keyboard", "hockey", "monkey", etc. To create a semi-formal field the static method Field.createSemiformalField(String pattern) is to be used.
  • Inverse Fields: It is also possible to define a field that contains a value, that should not be in matched tuples. Currently these fields can also only be used in combination with the type String. Such inverse string fields are created by setting a normal String field to an inverse field by calling the method setInverse(true).
  • Reverse structured naming: Normally all tuples in the space consist of actual fields and only template tuples can also contain formal fields. However, in SQLSpaces it is also possible to write tuples with formal fields into the space. The matching algorithm then compares each field independly whether it is a template tuple or a tuple in the space in both directions. With this technique it is possible to broaden the matches of a tuple in the space. If e.g. tuples contain messages in the format (Author, Recipient, Message), it would be possible to write a global message by setting the recipient to a formal field, so every participant of the system would match.

An overall view of the matching facility is shown in this figure:

[top]

Multiple connections

A SQLSpaces server consists of several spaces, which are disjoint tuple containers, and normally each client needs to have a logical connection to one of these. However, it is also possible to have a connection to several spaces. Such a connection is established by passing more than one space name to the constructor of TupleSpace. Queries over such a connection are interpreted as queries over the union of all corresponding spaces. In this case write and update commands need also the space name, so the server is able to assign the command to the correct space. If this space is ommitted, an exception is thrown.

TupleSpace ts = new TupleSpace("one space", "another space");

// this is executed on both spaces
Tuple t = ts.read(new Tuple(String.class));

// writes somethin in the second space
ts.write(new Tuple("Hello", "Adam"), "another space");
// no clear space assignment --> exception!!
ts.write(new Tuple("Hello", "Stefan"));

[top]

User management

In the delivery state the SQLSpaces have only a very basic user management. This means each connection call (i.e. call of the TupleSpace constructor) can have a username and password, which are interpreted like this: If a username is new, the credentials are simply stored, but if a username is already known, the password is checked. Once a user got a connection he has full access to the server.

It is also possible to constrict the rights of single users, if the rightsmanagement is enabled in the global configuration file (see chapter Configuration). Then the rights model consists of five different rights:

  • read: A space-based right, that enables the rightholder to read all tuples of a space. This makes the use of read, readAll, waitToRead and callbacks possible.
  • write: A space-based right, that enables the rightholder to change all tuples of a space and also to write new ones. This makes the use of write, update, take, takeAll and waitToTake possible.
  • manage: This space-based right enables the rightholder to share read and write rights with other users. Only the creator of a space holds this right initially, but he can also share this right itself with others.
  • create: This global right allows the rightholder the creation of new spaces. All users except the pre-defined spectators (see chapter Configuration) have this right automatically.
  • admin: This global right gives the rightholder all rights. Only users defined as admins (see chapter Configuration) have this right.

If a user tries to do an action, that he is not allowed to do, a TupleSpaceException is thrown. If a user has manage rights over a space, he is able to set space-based rights for "his" space for other users using the setUserRights method of the class TupleSpace. Therefore he needs to be connected to "his" space with this instance of TupleSpace.

[top]

Versioning

A normal tuplespace has no memory of prior states of it, i.e. there is no history saved and no undo function is available. In order to save a certain state of a space it is possible to create version of this space. The versioning systems is closely related to the idea of the versioning system Subversion. By calling the method createNewVersion of a TupleSpace instance, a new version of the space the instance is currently connected to is created. All versions have a major and minor version number (starting with 1.0) and so a new version will have an incremented minor version by default.

For handling the versions, the class TupleSpace has several methods, that all should speak for themselves: createNewVersion, editSpaceVersion, getAllVersions, getAllUserVersions, getCurrentVersion, getCurrentUserVersion and switchToVersion. If there are several versions of a space, you always connect by default to the latest one. If you want to access a specific version, you first have to switch to this one.

Since the semantics of merging two versions is quite domain-dependent, there is no generic support for merging, so this has to be implemented by the programmer himself.

Since version 4 it is possible to deactivate versioning. This makes sense, if you really don't need versioning and you need the last tiny percents of performance you can get. This means that all version-related operations that are executed will throw an exception. It is also possible to switch versioning off and on for an already running server. Just change the settings in the configuration and restart the server. It will restructure the database at the next start accordingly, i.e. for deactivating versioning, all previous versions are deleted and the latest version is the new "only" version and for reactivating it means that the current state is becoming the first version.

[top]

Web based Investigation

Often it is necessary to have a direct look inside the server. In this case it is not recommended to inspect the database tables itself, because the technical representation of the tuples and spaces are not really designed for maximum human readability. However, an SQLSpaces user can use a web-based UI for investigating the contents of the SQLSpaces. Therefore the built-in webserver has to be enabled (see chapter Configuration) and the investigator.war has to be deployed, i.e. it needs to be in the SQLSpaces folder during startup. By default this server runs on port 8080 and to access the so-called SQLSpaces Investigator the URL http://localhost:8080/investigator is used.

The first view of the investigator should be similar to the following:

Another representation of the SQLSpaces server is called Vizard and can be accessed by clicking on the "Vizard" mode in the combo box on the page. The Vizard is more a synchronous visualization that displays in real-time the communication between the connected clients in an animated way. Since it is based on JavaFX, you need a Java-Plugin installed in your browser. Note: The use of the Vizard is not recommended if you have many clients and really many tuples, since it easily gets slow and confusing. You should have less than 10 clients and less than 500 tuples as a rule of thumb.

[top]