Summary: This tutorial explains JavaSpaces basics and shows how to build a pure JavaSpaces application with GigaSpaces.

Overview

This basic tutorial will introduce you to the JavaSpaces standard and the JavaSpaces API. Throughout the tutorial, you will implement a simple, pure JavaSpaces application that could run on any JavaSpaces implementation. The tutorial will show you how to lookup a space, write Entries to the space and then read and take those Entries, using leases and transactions.

JavaSpaces is a service specification from Sun Microsystems that provides a simple yet powerful infrastructure for building distributed applications. The JavaSpaces specification defines the space – a distributed, shared, memory-based repository for objects – and provides support for distributed transactions, events and leasing. In the JavaSpaces programming model, applications are viewed as a group of processes, cooperating via the flow of objects into and out of spaces.

The JavaSpaces API

The JavaSpaces API consists of four main methods:

  • write() – writes an Entry to a space.
  • read() – reads an Entry from a space.
  • take() – reads an Entry and deletes it from a space.
  • notify() – registers interest in Entries arriving at a space.

In addition, the snapshot allows applications to provide optimization hints to the space.

This minimal API set reduces the learning curve when adopting JavaSpaces. The API is also natural to Jini developers, since it is built on top of Jini specifications (Entries, leases, service, etc.). Jini itself is built on Java and RMI, so there is no need to understand complex distributed Java technologies, like EJB or CORBA.

Some JavaSpaces Terms

In order to understand the basics of JavaSpaces, you will need to understand a few terms first:

  • Entry – an object that implements net.jini.core.entry.Entry. This interface needs to be implemented for every object that is written into a space. It is a marker interface, which means there are no methods to implement. An Entry implementation must have a public no-args constructor and its fields must be non-primitive, public, non-static, non-transient, and non-final.
  • Template – a template is an Entry that is used to look for similar Entries in a space. read and take operations look for Entries that are of the same type as the template, and have the same values in their fields. A field with a null value in a template is used as a wildcard, meaning that Entries of the same type with any value in that field are considered a match to the template.
  • Lease – leases are objects used to define time-to-live of Entry objects written to a space. When a lease of an Entry expires, the Entry is automatically removed from the space. Leases can be renewed or cancelled by the user application.
  • Block time – the JavaSpaces read and take operations can block for a user-defined period of time, in case no matching Entry is found in the space. This allows application instances to wait for data to be written by other instances. Defining a limited block time prevents the application from being blocked indefinitely in case the data does not arrive or the space is in deadlock.
  • Notifications - JavaSpaces notifications allow applications to register interest for Entries in a space. An application registers for a notification, providing a template that indicates which Entries it wants to be notified about. The space uses a callback method to notify the application when the required data enters the space (or when it is deleted or modified).
  • Jini Transaction Manager (Mahalo) - JavaSpaces enables full use of transactions, leveraging the default semantics of the Jini Distributed Transactions model. A transaction can span more than one space. The Jini Transaction Manager runs separately from the application and the space; the applications using the space discover it, and use it to start transactions, commit them or abort them.

Writing the Code

To start with, you'll need to implement an Entry object, which the application will write to the space. You'll also need to write mechanisms to discover a space and to create transactions. Finally, you can write Entries to the space and read them back, under a Jini distributed transaction.

Entry Object

This is the Entry object that the application will write to the space. For the sake of simplicity, create a Message object that implements the Entry interface, with two attributes, an integer ID and a string for the content of the message. Remember that according to the standard your Entry implementation must have a public, no-args, constructor and all its fields should be public and non-primitive.

Your object should look something like this:

package com.gigaspaces.examples.tutorials.plainjavaspaces;

import net.jini.core.entry.Entry;

public class Message implements Entry {
    public String content;
    public Integer ID;
    
    public String toString(){
    	return "Message ID:" + ID + " ,Message content: " + content;
    }
    public Message(){} // No args constructor is mandatory in an Entry class
}

Note that all you need to import is net.jini.core.entry.Entry.

Discovering the Space

The standard Jini approach to discovering a space is much more cumbersome than the way we do it at GigaSpaces (as you'll see in the next tutorial) but to stick to the standard, we'll now do it the hard way. A method that receives the space name, performs the lookup and returns a reference to a space might look like this:

import java.rmi.RMISecurityManager;
import net.jini.core.discovery.LookupLocator;
import net.jini.core.entry.Entry;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceTemplate;
import net.jini.lookup.entry.Name;
import net.jini.space.JavaSpace;
[...]
static public JavaSpace findSpace(String spaceName) throws Exception {
	// Get a RMI security manger, if doesn't exist set a new one.
	// The security policy is passed to this security manager using the -Djava.security.policy flag.
	if ( System.getSecurityManager() == null )
		System.setSecurityManager( new RMISecurityManager() );

	//Create a template to lookup the JavaSpaces service.
	Class [] classes = new Class[]{net.jini.space.JavaSpace.class};
	Name sn = new Name( spaceName );
	ServiceTemplate tmpl = new ServiceTemplate(null,classes,new Entry[] { sn } );

	// Locate the JavaSpaces service and create a JavaSpace proxy attached to it.
	LookupLocator locator = new LookupLocator("jini://localhost");
	ServiceRegistrar sr = locator.getRegistrar();
	JavaSpace space =  (JavaSpace)sr.lookup(tmpl);

	return space;
}

Transactions Mechanism

Creating Jini transactions can be implemented as the class below:

package com.gigaspaces.examples.tutorials.plainjavaspaces;

import java.io.IOException;
import java.net.MalformedURLException;
import java.rmi.RMISecurityManager;
import java.rmi.RemoteException;

import net.jini.core.discovery.LookupLocator;
import net.jini.core.entry.Entry;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceTemplate;
import net.jini.core.transaction.Transaction;
import net.jini.core.transaction.TransactionFactory;
import net.jini.core.transaction.server.TransactionManager;

public class TransactionHelper {
	private static TransactionHelper me = null;
	private TransactionManager trManager=null;
	public TransactionHelper() {
	}

	public static TransactionHelper getInstance(){
		if(me==null){
			me = new TransactionHelper();
		}
		return me;
	}

	// getJiniTransaction
	// 	Returns a transaction manager proxy.
	// Args:
	// 	timeout - The length of time our transaction should live before timing out.
	//
	public Transaction.Created getJiniTransaction(long timeout) throws Exception {
		if (null == trManager) {
			trManager = findTransactionManager();
		}
		Transaction.Created tCreated = TransactionFactory.create(trManager,timeout);
		return tCreated;
	}

	private TransactionManager findTransactionManager(){
		if ( System.getSecurityManager() == null ){
			System.setSecurityManager(new RMISecurityManager());
		}

		// Creating service template to find transaction manager service by matching fields.
		Class [] classes = new Class[]{net.jini.core.transaction.server.TransactionManager.class};
		ServiceTemplate tmpl = new ServiceTemplate(null,classes,new Entry[] { } );

		// Creating a lookup locator.
		LookupLocator locator = null;
		try {
			locator = new LookupLocator("jini://localhost");
		} catch (MalformedURLException ex) {
			System.out.println(ex.getMessage());
			ex.printStackTrace();
		}

		ServiceRegistrar sr = null;
		try {
			sr = locator.getRegistrar();
		} catch (ClassNotFoundException ex1) {
			ex1.printStackTrace();
		} catch (IOException ex1) {
			ex1.printStackTrace();
		}

		TransactionManager tm = null;
		try {
			tm = (TransactionManager) sr.lookup(tmpl);
		} catch (RemoteException ex2) {
			ex2.printStackTrace();
		}
		return tm;
	}
}

Writing Entries to the Space

Once you discover the space (as shown above), you can create instances of your Entries and write them into the space. Using the Message Entry showed above, writing Entries to the space might look like this:

[...]
Message message = new Message();
message.ID = 1;
message.content = "My first PlainJavaSpaces Message!";
space.write(message, null, Lease.FOREVER);
[...]

Note that the write method receives three parameters – Entry to write to the space, transaction (no transaction in this case) and a Lease in milliseconds. In the code above the message Entry is written to the space, and stays there indefinitely or until it is explicitly removed.

Reading or Taking Entries from the Space

Once you have written Entries to the space, you can read or take those Entries, one by one. To do this, you must define a template, which will be used to look for relevant Entries. A template is just an instance of your Entry implementation; matching is done by the type of the template as well as its field values.

For example, assume there are two Message objects in the space, one with ID = 1 and the other with ID = 2. Before you call the read or take operations, you must instantiate another Message object and set the ID of the Entry that you want to find in the space. Leaving the content field null means that this field is used as a wildcard – the space returns Entries of this type regardless of the value in the field.

Message template = new Message();
template.ID = 1;
Message result = (Message)space.read(template, null, JavaSpace.NO_WAIT);
[...]
template.ID = 2;
result = (Message)space.take(template, null, JavaSpace.NO_WAIT);

Again, the read and take methods take three parameters – the template to match, transaction (none in this case) and a timeout value that indicates the maximum amount of time to wait when no matching Entries are found. When the timeout value is greater than 0, the thread is blocked until the timeout expires or a matching Entry enters the space and is caught by this thread.

Writing or Reading Under Transaction

Try performing operations on the space under a transaction. Using the TransactionHelper shown above, you can do this as follows:

[...]
Transaction.Created txn = TransactionHelper.getInstance().getJiniTransaction(60 * 1000);
space.write(message, txn.transaction, Lease.FOREVER);
Message result = (Message)space.read(template, 
				 txn.transaction, JavaSpace.NO_WAIT);
				 txn.transaction.commit(1000);
[...]

Reference Source Code

You can use the full tutorial example package, including execution scripts.
You can find it at your <GigaSpaces Root>\examples\tutorials\javaspaces\plain_javaspaces.

Compiling the Code

To compile this code you will need to include two JAR files in your classpath. Assuming %JSHOMEDIR% is defined as your GigaSpaces installation root directory, the two JARs are:

  • %JSHOMEDIR%/lib/jini/jsk-platform.jar
  • %JSHOMEDIR%/lib/jini/jsk-lib.jar

If you keep your sources under the src folder and put your generated classes under the classes folder, your compilation command would then be similar to this (when executed from the application folder):

javac -classpath %JSHOMEDIR%/lib/jini/jsk-lib.jar;%JSHOMEDIR%/lib/jini/jsk-platform.jar 
	-d .\classes .\src\com\gigaspaces\examples\tutorials\plainjavaspaces\*.java

Running Your Application

Starting the Space – a JavaSpaces Service

Before you can run your application, you need to start a JavaSpaces service. This service is the space on which your application performs its operations.

%JSHOMEDIR%\bin\gsInstance "/./myspace?schema=javaspace"

In the command above the gsInstance script starts the GigaSpaces server. In GigaSpaces every space runs within and a container and the /./myspace argument instructs the server to start a new space with the name myspace and a default container name (in this case myspace_container).

Starting the Jini Transaction Manager (Mahalo)

If you wish to use Jini transactions in your application, you must also run the Jini Transaction Manager, called Mahalo. To start it, execute:

%JSHOMEDIR%\bin\startJiniTX_Mahalo

Running the Application

Once the space is up (and optionally the Transaction Manager) you can run your application. Running our own example would look like this (one line):

java -Djava.security.policy=%JSHOMEDIR%\policy\policy.all -classpath 
	%JSHOMEDIR%/lib/jini/jsk-lib.jar;%JSHOMEDIR%/lib/jini/jsk-platform.jar;.\classes 
	com.gigaspaces.examples.tutorials.plainjavaspaces.PlainJavaSpaces myspace

Note that now, besides the jars, the classpath also includes our compiled classes. You also need to set a system property for the policy that the RMI Security Manager uses. Finally, the last argument is the space name you wish to connect to; it should be identical to the space name you chose when you started the space.

That's it!

What's Next?

!Images^lozenge_interfaces_javaspaces.gif!
  JavaSpaces Tutorial B -- Order Management
This tutorial shows how to implement a Master-Worker pattern, using OpenSpaces processing units, in order to perform parallel processing.
 

Further Reading

GigaSpaces.com - Legal Notice - 3rd Party Licenses - Site Map - API Docs - Forum - Downloads - Blog - White Papers - Contact Tech Writing - Gen. by Atlassian Confluence