1.4. JavaSpaces Technology Overview

Now we are going to dive into our first example by building the obligatory “Hello World” application. Our aim here is to introduce you to the JavaSpaces programming interface, but we will save the nitty-gritty details for the next chapter. We are going to step through the construction of the application piece by piece, and then, once it is all together, make it a little more interesting.

1.4.1. Entries and Operations

A space stores entries. An entry is a collection of typed objects that implements the Entry interface. Here is an example “message” entry, which contains one field—the content of the message:

public class Message implements Entry {
  public String content;

  public Message() {
  }
}

We can instantiate a Message entry and set its content to “Hello World” like this:

Message msg = new Message();
msg.content = "Hello World";

With an entry in hand, we can interact with a space using a few basic operations: write, read and take (and a couple others that we will get to in the next chapter). The write method places one copy of an entry into a space. If we call write multiple times with the same entry, then multiple copies of the entry are placed into the space. Let's obtain a space object and then invoke its write method to place one copy of the entry into the space:

JavaSpace space = SpaceAccessor.getSpace();
space.write(msg, null, Lease.FOREVER);

Here we call the getSpace method of the SpaceAccessor class, which returns an instance of an object that implements the JavaSpace interface (which we will refer to as a “space object” or “space” throughout this book). We then call write on the space object, which places one copy of the entry into the space. We will define a SpaceAccessor class and cover the details of the write method in the next chapter; for now, it's enough to know that getSpace returns a JavaSpace object and that write places the entry into the space.

Now that our entry exists in the space, any process with access to the space can read it. To read an entry we use a template, which is an entry that may have one or more of its fields set to null. An entry matches a template if the entry has the same type as the template (or is a subtype), and if, for every specified (non-null) field in the template, their fields match exactly. The null fields act as wildcards and match any value. We will get back to the details of matching in the next chapter, but for now let's create a template:

Message template = new Message();

That was easy. It is important to point out that the content field of the template is by default set to null (as Java does with all noninitialized object fields upon creation). Now let's use our template to perform a read on the space:

Message result =
 (Message)space.read(template, null, Long.MAX_VALUE);

Because the template's content field is a wildcard (null), the template will match any Message entry in the space (regardless of its contents). We're assuming here that this space is our own private space and that other processes are not writing or reading Message entries. So, when we execute our read operation on the space, it matches and returns a copy of the entry we wrote there previously and assigns it to result. Now that we have our entry back, let's print its content:

System.out.println(result.content);

Sure enough, we get:

Hello World

For the sake of completeness, the take operation is just like read, except that it withdraws the matching entry from the space. In our code example, suppose we issue a take instead of a read:

Message result =
 (Message)space.take(template, null, Long.MAX_VALUE);

We would see the same output as before. However, in this case, the entry would have been removed from the space.

So, in just a few steps we've written a basic space-based “Hello World” program. Let's pull all these code fragments together into a complete application:

public class HelloWorld {
  public static void main(String[] args) {
    try {
        Message msg = new Message();
        msg.content = "Hello World";

        JavaSpace space = SpaceAccessor.getSpace();
        space.write(msg, null, Lease.FOREVER);

        Message template = new Message();
        Message result =
         (Message)space.read(template, null, Long.MAX_VALUE);
        System.out.println(result.content);
    } catch (Exception e) {
       e.printStackTrace();
    }
  }
}

In this code we've kept things simple by wrapping the code in a try / catch statement that catches all exceptions. We also left out the implementation of the SpaceAccessor. We will return to both of these topics in the next chapter. However, note that you can find the complete source for each example at the book's web site http://java.sun.com/docs/books/jini/javaspaces.

Before moving on, let's step back a bit—with a small bit of simple code, we've managed to send a message using spaces. Our HelloWorld places a message into a space, in effect broadcasting a “Hello World” message to anyone who will listen (everyone with access to the space who is looking for Message entries). Right now, HelloWorld is by design the only listener; it reads the entry and prints out its contents. In the next section we will change that.

1.4.2. Going Further

Let's take our example and make it a little more interesting. In doing so, you'll get a glimpse of the key features that make the JavaSpaces technology an ideal tool for building distributed applications.

We'll begin by modifying the Message class to hold not only a message but also a count of how many times it has been read. So far, our HelloWorld application is the only process reading the message entry, so we'll also create a HelloWorldClient to read the entry. We will also enhance our HelloWorld application so that it can monitor the entry's popularity by keeping track of how many times it has been read. Let's start with the new Message entry:

public class Message implements Entry {
  public String content;
  public Integer counter;

  public Message() {
  }
  public Message(String content, int initVal) {
    this.content = content;
    counter = new Integer(initVal);
  }

  public String toString() {
    return content + " read " + counter + " times.";
  }

  public void increment() {
    counter = new Integer(counter.intValue() + 1);
  }
}

We've added an Integer field called counter, and a new constructor that sets the content and counter fields to values passed in as parameters. We've also added a toString method, which prints the values of the fields, and a method called increment, which increments the counter by one.

Note that in all our examples, we've been violating a common practice of object-oriented programming by declaring our entry fields to be public. In fact, fields of an entry must be public in order to be useful; if they are instead declared private or protected, then processes that take or read the entry from a space won't be able to access their values. We'll return to this subject in the next chapter and explain it more thoroughly.

Now let's modify the HelloWorld class to keep track of the number of times the Message entry has been read by other processes:

public class HelloWorld {
  public static void main(String[] args) {
    try {
       Message msg = new Message("Hello World", 0);

       JavaSpace space = SpaceAccessor.getSpace();
       space.write(msg, null, Lease.FOREVER);

       Message template = new Message();
       for (;;) {
          Message result = (Message)
           space.read(template, null, Long.MAX_VALUE);
          System.out.println(result);
          Thread.sleep(1000);
       }
     } catch (Exception e) {
        e.printStackTrace();
     }
  }
}

Following along in our main method, we first make use of our Message entry's new constructor, which takes a String parameter and an initial counter value and assigns them to the content field and the counter field respectively. Next we obtain a space object and write the Message entry into the space. As in the previous version of HelloWorld, we then create a template (with null fields) to match the entry.

Now things become more interesting: we enter a for loop that continually reads the message entry from the space using template. Each time we read a Message entry, we print out the value of its counter by calling println, which implicitly calls the message's toString method. The loop then takes a short breather by sleeping for one second before continuing. If we now run this version, it will continually print a counter value of zero because we are waiting for other processes to read the entry and, so far, there are none.

So, let's write a HelloWorldClient that will take Message entries, increment their counters and place them back in the space:

public class HelloWorldClient {
  public static void main(String[] args) {
    try {
       JavaSpace space = SpaceAccessor.getSpace();

       Message template = new Message();
       for (;;) {
        Message result = (Message)
         space.take(template, null, Long.MAX_VALUE);
        result.increment();
        space.write(result, null, Lease.FOREVER);
        Thread.sleep(1000);
       }
     } catch (Exception e) {
        e.printStackTrace();
     }
  }
}

Just as in the HelloWorld application, HelloWorldClient first creates a template using the default constructor (both fields are set to null and act as wildcards). Rather than reading (as in HelloWorld) a message from the space, we take it out of the space and assign it to result. We then call result's increment method, which increments the counter by one, and write the result back into the space. Like HelloWorld, we then sleep for one second and repeat the entire process.

So let's now run HelloWorld and then start up a few HelloWorldClients. The output for a typical run might look something like:

Hello World read 0 times.
Hello World read 1 times.
Hello World read 5 times.
Hello World read 10 times.
.
.
.

Let's trace through the whole scenario to understand exactly what has happened. First, we started up HelloWorld, which deposits its Message entry into the space and enters a loop that reads the entry and prints the value of the counter. The first time through the loop, the counter field's value is still zero (no other processes have yet updated the counter). We also start up several HelloWorldClient applications, which each begin searching the space using a template of type Message (with both its fields set to null to act as wildcards). Since the system is asynchronous, the HelloWorldClients access the Message entry in an unpredictable order. If a client tries to take the entry but it is has already been removed, then the client simply blocks and waits until the entry shows up in the space. Once a client manages to take an entry, it calls the entry's increment method to update the counter, and then it returns the modified entry to the space.

Our output indicates that, by the second time HelloWorld reads the counter, one client process has accessed the entry and incremented its counter. By the third time, four more clients have managed to access the entry. Finally, by the fourth time, five more clients have accessed it. In general, the more clients we add, the faster counter gets incremented (although only so many processes can take and write the entry in the one-second interval).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset