By Qusay H. Mamoud, 12, 2005
Building distributed applications is difficult because you must take into account several issues, such as partial failure, increased latency, distributed persistence, and language compatibility.
The JavaSpaces technology is a simple and powerful high-level tool for building distributed and collaborative applications. Based on the concept of shared network-based space that serves as both object storage and exchange area, it provides a simple API that is easy to learn and yet expressive for building sophisticated distributed applications.
This article provides a fast-track tutorial to the JavaSpaces technology, including
Distributed computing is about building network-based applications as a set of processes that are distributed across a network of computing nodes (or hosts) that work together to solve a problem. The advantages of building applications using this approach are many, including performance, resource sharing, scalability, and fault tolerance. But using distributed technologies does not guarantee these advantages. The developer must take special care in the design and implementation or distributed applications in order to achieve such benefits.
The network environment on top of which you build distributed applications introduce complexities that are not of concern when you write stand-alone applications. The most obvious complexity is the varied architecture of machines. However, Java technology's platform independence and its virtual machine allow for applications that you write once and run anywhere. Other issues that have significant impact on designing and implementing distributed applications include latency, synchronization, and partial failure.
Several technologies can be used to build distributed applications, including low-level sockets, message passing, and remote method invocation (RMI). The JavaSpaces technology model is different in that it provides persistent object exchange areas (or spaces) through which remote Java technology processes coordinate actions and exchange data. Such an approach can simplify the design and implementation of sophisticated distributed applications, and it enables you to deal with the challenges of designing and implementing distributed applications.
The JavaSpaces technology is a high-level tool for building distributed applications, and it can also be used as a coordination tool. A marked departure from classic distributed models that rely on message passing or RMI, the JavaSpaces model views a distributed application as a collection of processes that cooperate through the flow of objects into and out of one or more spaces. This programming model has its roots in Linda, a coordination language developed by Dr. David Gelernter at Yale University. However, no knowledge of Linda is required to understand and use JavaSpaces technology.
The JavaSpaces service specification lists the following design goals for the JavaSpaces technology:
As mentioned earlier, a space is a shared network-accessible repository for objects: The data you can store there is persistent and later searchable. But a JavaSpaces service is not a relational or object database. JavaSpaces services are not used primarily as data repositories. They are designed for a different purpose than either relational or object databases.
Although a JavaSpaces service functions somewhat like a file system and somewhat like a database, it is neither. The key differences between JavaSpaces technology and databases are the following:
Application components (or processes) use the persistent storage of a space to store objects and to communicate. The components coordinate actions by exchanging objects through spaces; the objects do not communicate directly. Processes interact with a space through a simple set of operations.
You can invoke four primary operations on a JavaSpaces service:
write()
: Writes new objects into a spacetake()
: Retrieves objects from a spaceread()
: Makes a copy of objects in a spacenotify
: Notifies a specified object when entries that match the given template are written into a spaceBoth the read()
and take()
methods have variants: readIfExists()
and takeIfExists()
. If they are called with a zero timeout, then they are equivalent to their counterpart. The timeout parameter comes into effect only when a transaction is used.
Each operation has parameters that are entries. Some are templates, which are a kind of entry. The write()
operation is a store operation. The read()
and take()
operations are a combination of search and fetch operations. The notify
method sets up repeated search operations as entries are written to the space. If a take()
or read()
operation doesn't find an object, the process can wait until an object arrives.
Unlike conventional object stores, objects are passive data. Therefore, processes do not modify objects in the space or invoke their methods directly. In order to modify an object, a process must explicitly remove, update, and reinsert it into the space.
How can we build sophisticated distributed applications with only a handful of operations? The space itself provides a set of key features.
A JavaSpaces service holds entries, each of which is a typed group of objects expressed in a class that implements the interface net.jini.core.entry.Entry
. Once an entry is written into a JavaSpaces service, it can be used in future look-up operations. Looking up entries is performed using templates, which are entry objects that have some or all of their fields set to specified values that must be matched exactly. All remaining fields, which are not used in the lookup, are left as wildcards.
There are two look-up operations: read()
and take()
. The read()
method returns either an entry that matches the template or an indication that no match was found. The take()
method operates like read()
, but if a match is found, the entry is removed from the space. Distributed events can be used by requesting a JavaSpaces service to notify you when an entry that matches the specified template is written into the space. Note that each entry in the space can be taken at most once, but two or more entries may have the exact same values.
Using JavaSpaces technology, distributed applications are modeled as a flow of objects between participants, which is different from classic distributed models such as RMIs. Figure 1 indicates what a JavaSpaces technology-based application looks like.
Figure 1: A Typical JavaSpaces Technology Application
As you can see, a client can interact with as many JavaSpaces services as needed. Clients perform operations that map entries to templates onto JavaSpaces services. Such operations can be singleton or contained in a transaction so that all or none of the operations take place. Notifications go to event catches, which can be either clients or proxies for clients.
To get a flavor of how to implement distributed applications using a handful of JavaSpaces operations, consider a multiuser chat system. All the messages that make up the discussion are written to a space that acts as a chat area. Participants write message objects into the space, while other members wait for new message objects to appear, then read them out and display their contents. The list of participants can be kept in the space and updated whenever someone joins or leaves the discussion. Because the space is persistent, a new member can read and view the entire discussion.
You can implement such a multiuser chat system in RMI by creating remote interfaces for the interactions discussed. But by using JavaSpaces technology, you need only one interface.
All operations are invoked on an object that implements the net.jini.space.JavaSpace
interface. A space stores entries, each of which is a collection of typed objects that implements the Entry
interface. Code Sample 1 shows a MessageEntry
that contains one field: content
, which is null
by default. Information on how to compile and run the sample application appears later in this article.
Code Sample 1: MessageEntry.java
import net.jini.core.entry.*;
public class MessageEntry implements Entry {
public String content;
public MessageEntry() {
}
public MessageEntry(String content) {
this.content = content;
}
public String toString() {
return "MessageContent: " + content;
}
}
MessageEntry
space
JavaSpace space = getSpace();
MessageEntry msg = new MessageEntry();
msg.content = "Hello there";
space.write(msg, null, Lease.FOREVER);
null
Transaction
The write()
operation places a copy of an entry into the given JavaSpace service, and the Entry
passed is not affected by the operation. Each write()
operation places a new Entry
into the space even if the same Entry
object is used in more than one write()
.
Entries written in a space are governed by a renewable lease. If you like, you can change the lease (when the write()
operation is invoked) to one hour as follows: >
space.write(msg, null, 60 * 60 * 1000);
write()Lease
Once the entry exists in the space, any process with access to the space can perform a read()
on it. To read an entry, a template is used, which is an entry that may have one or more of its fields set to null
. An entry matches a template if (a) the entry has the same type as or is a subtype of the template and (b) if for every specified non- null
field in the template, their fields match exactly. The null
fields act as wildcards and match any value. The following code segment shows how to create a template and perform a read()
on the space:
MessageEntry template = new MessageEntry();
MessageEntry output = (MessageEntry) space.read(template, null, Long.MAX_VALUE);
null MessageEntry Long.MAX_VALUE read() take() readIfExists()
Code Sample 2 shows the client that discovers the JavaSpace service, writes a message into the space, and then reads it. Instructions on how to compile and run this sample application appear later in this article.
Code Sample 2: SpaceClient.java
import net.jini.space.JavaSpace;
public class SpaceClient {
public static void main(String argv[]) {
try {
MessageEntry msg = new MessageEntry();
msg.content = "Hello there";
System.out.println("Searching for a JavaSpace...");
Lookup finder = new Lookup(JavaSpace.class);
JavaSpace space = (JavaSpace) finder.getService();
System.out.println("A JavaSpace has been discovered.");
System.out.println("Writing a message into the space...");
space.write(msg, null, 60*60*1000);
MessageEntry template = new MessageEntry();
System.out.println("Reading a message from the space...");
MessageEntry result = (MessageEntry) space.read(template, null, Long.MAX_VALUE);
System.out.println("The message read is: "+result.content);
} catch(Exception e) {
e.printStackTrace();
}
}
}
The JavaSpaces API uses the package net.jini.core.transaction
to provide basic atomic transactions that group multiple operations across multiple JavaSpaces services into a bundle that acts as a single atomic operation. Either all modifications within the transactions will be applied or none will, regardless of whether the transaction spans one or more operations or one or more JavaSpaces services. Note that transactions can span multiple spaces and participants in general.
A read()
, write()
, or take()
operation that has a null
transaction acts as if it were in a committed transaction that contained that operation. As an example, a take()
with a null
transaction parameter performs as if a transaction was created, the take()
was performed under that transaction, and then the transaction was committed.
The Jini Technology Starter Kit comes with the package com.sun.jini.outrigger
, which provides an implementation of a JavaSpaces technology-enabled service. You can run it two ways:
com.sun.jini.outrigger.TransientOutriggerImpl
.com.sun.jini.outrigger.PersistentOutriggerImpl
.TransientOutriggerImpl
PersistentOutriggerImpl
To compile and run the sample application in this article, do the following:
MessageEntry.java
) using javac
as follows:
prompt> javac -classpath <pathToJiniInstallation\lib\jini-ext.jar> MessageEntry.java
Note that you need to include the JAR file jini-ext.jar
in your classpath
. This JAR file comes with the starter kit and is in the lib
directory of your installation.
SpaceClient.java
). Note that this code makes use of a utility class called Lookup to locate or discover a JavaSpace space. Therefore, before you compile SpaceClient.java
, you should download Lookup.java
and then compile both classes using javac
as shown in step 2. Note that you should include the directory that contains MessageEntry.class
in your classpath
when compiling SpaceClient.java
.
Launch-All
, which is in the installverify
directory of your Jini installation directory. This will start a service browser (as shown in Figure 2) and six contributed Jini network technology services, one of which is the JavaSpace service.
Figure 2: Jini Network Technology Service Browser
SpaceClient
application using the java
command as follows. Here I assume that your Jini installation directory is C:\Jini2_1beta
and that the classes you compiled earlier are at C:\Jini2_1beta\myclasses
.
C:\Jini2_1beta\myclasses> java -classpath .\;
..\lib\jini-ext.jar;..\lib\reggie.jar;..\lib\outrigger.jar SpaceClient
If all goes well, you will see the output shown in Figure 3.
Figure 3: SpaceClient Sample Output
The JavaSpaces technology provides services and tools for building sophisticated distributed applications. This technology is designed to work with applications that can model themselves as flow objects through one or more servers. If your application can be modeled this way, JavaSpaces technology will provide you with many benefits, such as a reliable distributed storage system for the objects. In addition, JavaSpaces technology handles concurrent access, storing and retrieving entries atomically.
Special thanks to John McClain of Sun Microsystems, whose feedback helped me improve this article.