Version 0.1.1
Danny B. Lange and Mitsuru Oshima © 1997
This is a preview of Chapter 5. The text and code fragments in this
Chapter are for the upcoming Alpha 5 release of AWB. However, the referenced
examples have been prepared for the current Alpha 4c release.
Chapter 5
Aglet Messaging
An important property of aglets is that they can communicate with each
other. Inter-aglet communication is supported by a rich framework in which
aglets that do not necessarily "know" each other can interchange messages.
That is, an aglet is often supposed to cooperate with aglets developed
in different organizations. The cooperating aglets in such a collection
are not likely to be developed at the same time or even to be known to
each other at compile time. This demands an interaction and communication
model that is richer, more flexible, and more extensible than that used
in normal method invocation.
The aglet supports an object-based messaging framework that is
-
location-independent
-
extensible
-
rich
-
synchronous/asynchronous
In this chapter you will learn about the basics of aglet messaging. Several
means of inter-aglet communication are supported, and you will learn about
simple messaging with and without reply, advanced message management, and
multicast messaging between aglets.
Simple Messaging
The principal way for aglets to communicate with each other is by message
passing. Inter-aglet messaging is based on a simple callback scheme that
requires an aglet to implement handlers only for the kinds of messages
that it is supposed to understand. The message callback method in the Aglet
class is called handleMessage. This is not a method that you call
directly when to wish to send a message to an aglet. Instead, you invoke
either the sendMessage or the sendAsyncMessage method
on the proxy, which serves as a message
gateway for the aglet. One of the benefits of using the proxy is that it
provides you with a location-independent interface for sending messages
to aglets. In other words, it really does not matter whether you are using
a remote proxy (a proxy for a remote aglet) or a local proxy to send a
message; the interface (sendMessage) is the same.
The sendMessage method of the proxy takes a message object as
argument and sends the synchronous message to the aglet for which the proxy
is acting as a gateway, and returns an object as a reply.
On the other handl, the sendAyncMessage method sends the
message to the aglet in a asynchrnous manner, and returns
a FutureReply object that the sender of the message can use to
retrieve a possible (future) reply.
public Object sendMessage(Message message)
|
Sends a message object to the aglet, waits until the handling is completed
and returns the reply from the aglet.
|
public FutureReply sendAsyncMessage(Message message)
|
Sends a message object to the aglet and returns a handle for a future
reply from the aglet.
|
Below, a simple message object of the "Hello" kind is created
and send to the aglet. In this example we are not interested in the reply
to the message we sent, and will therefore simply ignore the object returned
by sendMessage():
Message msg = new Message("Hello");
someProxy.sendMessage(msg);
The handleMessage method of the Aglet class is one of
the key callback methods that the aglet programmer is supposed to override.
It enables the aglet to respond to messages sent to it. Typically, the
implementation of this method will consist of a switch statement that tests
for the kind of the incoming message. Notice that the handler is supposed
to return a Boolean value; true if the message kind was understood
and handled, and false if the message kind was neither understood
nor handled.
public boolean handleMessage(Message message)
|
Handles an incoming message object.
|
This simple message handler accepts and handles messages of the "Hello"
kind. To acknowledge that it has handled such a message, it returns the
Boolean value true. It will reject all other kinds of message
by returning the value false:
public boolean handleMessage(Message msg) {
if ("Hello".equals(msg.kind)) {
doHello();
return true; // Okay, I handled this message.
} else
return false; // No, I do handle this kind of message.
}
Full source code: SimpleMessageExample.java
and SimpleMessageChild.java.
The Message Class
Messages are objects. A message object is characterized by its kind.
This string property is used to distinguish messages from each other. The
Message class supports a range of constructors that all have kind
as a mandatory argument. Message objects also contain an optional argument
field for data associated with a particular message. The argument field
can be either atomic (String, int, etc.) or tabular (Hashtable).
Message Creation
The many message constructors represent shortcuts for the initialization
of the argument field.
public Message(String kind)
|
Constructs a message of the specified kind. Creates by default an argument
hash table that can hold a set of key-value pairs. The setArg
method can be used to initialize the argument hash table.
|
public Message(String kind, Object arg)
|
Constructs a message of the specified kind with an initial binding
of the Object argument.
|
public Message(String kind, int arg)
|
Constructs a message of the specified kind and with an initial binding
of the int argument.
|
public Message(String kind, long arg)
|
Constructs a message of the specified kind and with an initial binding
of the long argument.
|
public Message(String kind, float arg)
|
Constructs a message of the specified kind and with an initial binding
of the float argument.
|
public Message(String kind, double arg)
|
Constructs a message of the specified kind and with an initial binding
of the double argument.
|
public Message(String kind, char arg)
|
Constructs a message of the specified kind and with an initial binding
of the char argument.
|
public Message(String kind, boolean arg)
|
Constructs a message of the specified kind and with an initial binding
of the boolean argument.
|
We will now look at a few examples of typical message objects. The first
shows the construction of a simple message of the " Hello" kind:
Message msg = new Message("Hello");
The second example shows the creation of a "Greeting" message
with the string "Happy Birthday" as argument:
Message msg = new Message("Greeting", "Happy Birthday");
The final example of a simple message object is:
Message msg = new Message("Height", 175);
The constructors initialize three public fields in the message object.
The first field is kind, a String field that indicates
the kind of the message and can be obtained by getKind() accessor
method. The second field is arg, an Object
field that hold the argument object of the message, and can be obtained by
getArg() method. The final public field is timeStamp,
a long field that holds a time stamp automatically
set at the creation of the message, and can be obtained by
getTimeStamp() method.
public String getKind()
|
Indicates the kind of the message.
|
public Object getArg()
|
Holds the argument object of the message.
|
public long getTimeStamp()
|
Holds the time stamp of the message.
|
There is the method, sameKind(String) which can be used to
compare the given kind with the message. Say you have a message object named
msg;
then the kind can be compared by msg.sameKind("kind"),
the argument and timestamp can be accessed by msg.getArg()
and msg.getTimeStamp() respectively. As an example, let us take the
message handler below. It implements a switch statement that enables it
to recognize and handle three kinds of message, "Hello", "Greeting",
and "Height":
public boolean handleMessage(Message msg) {
if (msg.sameKind("Hello"))
System.out.println("Hello")
if (msg.sameKind("Greeting"))
system.out.println("Greeting: " + msg.getArg());
else if (msg.sameKind("Height")) {
int height = ((Integer)msg.getArg()).intValue();
system.out.println("Height: " + height);
}
return true;
}
Full source code: MessageExample.java
and MessageChild.java.
The Message class has two methods for handling messages with non-atomic
arguments. The reason is that messages often need to carry multiple arguments
to the receivers. Such arguments are most effectively handled as key-value
pairs. The setArg and getArg methods are convenient methods for
organizing multiple arguments into a hash table.
public void setArg(String key, Object value)
|
Maps the specified key to the specified value in the argument hash
table.
|
public Object getArg(String key)
|
Gets the value to which the key is mapped in the argument hash table;
this value is null if the key is not mapped to any value in the
argument hash table.
|
Say you want to send a "Location" message to an aglet. This message
obviously requires two arguments (Horizontal and Vertical)
to advertise the location. We use setArg() to create two key-value
pairs in the message object:
Message msg = new Message("Location");
msg.setArg("Horizontal", new Integer(40));
msg.setArg("Vertical", new Integer(60));
The message handler can now retrieve the advertised location by means of
the two keys:
public boolean handleMessage(Message msg) {
if ("Location".equals(msg.kind)) {
int h = ((Integer)msg.getArg("Horizontal")).intValue();
int v = ((Integer)msg.getArg("Vertical")).intValue();
System.out.println("Location: (" + h + "," + v + ")");
}
}
Full source code: MessageHashExample.java
and MessageHashChild.java.
Replying to Messages
The last group of methods in the Message class that will be described
in this section is aimed at message handlers that want to reply to incoming
messages. The message handler uses the incoming message object to deliver
a reply to the message. The sendReply method of the message object
is used to return a reply to the sender of the message.
There are several sendReply methods. The first of them does not take any
arguments. It is used to send a reply without any specific value. The others
all take one argument, namely the reply.
public void sendReply()
|
Sends a reply without any specific value.
|
public void sendReply(Object reply)
|
Sends an object reply.
|
public void sendReply(int reply)
|
Sends a numeric reply.
|
public void sendReply(long reply)
|
Sends a numeric reply.
|
public void sendReply(float reply)
|
Sends a numeric reply.
|
public void sendReply(double reply)
|
Sends a numeric reply.
|
public void sendReply(char reply)
|
Sends a character reply.
|
public void sendReply(boolean reply)
|
Sends a Boolean reply.
|
It is time for an example that demonstrates how a message handler actually
responds to an incoming message. When the message handler below receives
the "What is your height?" message, it is supposed to respond
to that message with an integer reply. If its "height" is, say,
175, it can create a reply with the numeric value 175. The way that the
(possibly remote) sender of the message will receive and process that reply
will be covered in a few moments.
public boolean handleMessage(Message msg) {
if ("What is your height?".equals(msg.kind)) {
msg.sendReply(myHeight);
return true;
}
return false;
}
What if the message handler fails while handling a message? So far we have
treated message handling in a black-and-white manner: a message is either
handled or it is not handled. Obviously, message handling can also lead
to exceptions being thrown. In that case, the message handler should not
just return the Boolean value false to indicate that the message
was not handled, but instead reply with an exception stating the cause
of the failure. For this purpose the sendReply method is paired
with the sendException method.
public void sendException(Exception exception)
|
Replies with an exception.
|
This example of a message handler accepts "Set the height" messages
carrying an integer argument. It is reasonable for the message handler
to reject negative integer values. If a negative height is given, the handler
will respond to the message with an exception:
public boolean handleMessage(Message msg) {
if ("Set the height".equals(msg.kind))
if (((Integer)msg.arg).intValue() < 0)
msg.sendException(new Exception("Illegal value"));
else ...
return true;
}
Getting the Reply
We will now continue to describe how the sender of a message receives and
processes a reply from the message handler. As noted at the beginning of
this chapter, the proxy's sendMessage method returns an object
of the FutureReply class. This future object serves as
a handle for the expected reply to the message. The object is called a
future because it is returned immediately to the sender of the message,
bearing a promise of some future reply. The concept of a future
is a very flexible one that covers synchronous as well as asynchronous
messaging. In synchronous messaging, the sender of the message suspends
its execution until a reply has arrived. In asynchronous messaging, the
sender continues its execution and subsequently retrieves the reply. We
also call this "non-blocking messaging."
Synchronous Messaging
Let us start with the case of synchronous inter-aglet messaging.
Having received the future object from the proxy's sendAsyncMessage(),
you can immediately retrieve the reply from the future by using its
getReply method. This method will block until it can return a
reply.
public Object getReply()
|
Gets the reply to the message. This method will block until a reply
can be returned.
|
In this example, we send the message "What is your height?" to
the proxy of an aglet. We receive the future object and immediately
attempt to retrieve the expected reply. Since the getReply method
blocks until a reply actually has arrived, we can think of this as a case
of synchronous messaging:
Message msg = new Message("What is your height?");
FutureReply future = proxy.sendAsyncMessage(msg);
int height = ((Integer)future.getReply()).intValue();
Full source code: ReplyExample.java and ReplyChild.java.
The getReply method throws two different kinds of exception: MessageNotHandledException
and MessageException. An exception of the first kind, MessageNotHandledException,
is thrown when the message handler returns a false instead of true result
to indicate that it did not handle a given message:
public boolean handleMessage(Message msg) {
if ("Hello".equals(msg.kind)) {
...
return true; // Yes, I did handle this message.
} else
return false; // No, I did not handle this message
}
An exception of the second kind, MessageException, is thrown when
the message handler replied with an exception by calling sendException()
on the message object:
msg.sendException(new Exception("Illegal value"));
The example code below demonstrates how to catch the two different exceptions
thrown by the getReply method. In the first part of the example
below, a message of an unknown kind is sent to an aglet. The message handler
of that aglet does not recognize "Unknown", and returns false.
As a result, getReply() throws the MessageNotHandledException
exception. In the second part, a message with an invalid argument value
is sent to the aglet. This time, the message handler recognizes the message
kind but finds the value of its argument invalid. It responds by sending
an exception. The getReply method will receive the reply and throw
a MessageException exception. Notice that it is necessary to explicitly
invoke getReply() to know whether the target aglet is really handling
the message, since the sendAsyncMessage method itself does not throw
that kind of exception.
void sendSomeMessages() {
try { // Sends an unknown message.
proxy.sendAsyncMessage(new Message("Unknown")).getReply();
} catch (MessageNotHandledException e) {
System.out.println("Message not handled...");
}
try { // Sends a message with an invalid argument.
FutureReply future = proxy.sendAsyncMessage(new Message("Height", -100)).getReply();
} catch (MessageException e) {
System.out.println("Message replied by an exception...");
}
}
Full source code: ExceptionExample.java
and ExceptionChild.java.
Asynchronous Messaging
The group of methods in the FutureReply class that we cover in
this section are related to non-blocking retrieval of the reply to a message.
Non-blocking or asynchronous messaging allows you to continue execution
while the reply has not yet arrived. The first method, isAvailable(),
allows you to test whether the reply has come. It returns true if the reply
is ready and can be retrieved without blocking for continued execution.
The waitForReply method allows the current thread to wait a specified
period of time for a reply.
public boolean isAvailable()
|
Checks whether a reply has arrived.
|
public void waitForReply(long duration)
|
Waits for a reply to arrive. If the reply does not arrive within the
specified period of time, the thread will continue its execution.
|
Public void waitForReply()
|
Waits for a reply to arrive before it continues execution.
|
The example below is to demonstrate how the aglet can proceed with a given
task from the moment a message is sent until a reply has arrived. In this
example, we have created a small loop that uses the isAvailable
method to test whether a reply has arrived. As long as no reply has arrived,
the body of the loop (doIncrement()) will be repeatedly executed:
FutureReply future = proxy.sendAsyncMessage(msg);
while (!future.isAvailable()) // Checks whether a reply has arrived.
doIncrement(); // If not: do incremental work...
String reply = (String)future.getReply(); // Gets the reply.
Another typical case of asynchronous messaging is found in situations where
we wish to avoid being trapped in a call to getReply that may
never return. This can happen when the message handler goes into a non-terminating
loop or when a deadlock occurs. Below, we will show how to wait a limited
period of time for a reply. The example uses the waitForReply
method, which takes a timeout argument. The waitForReply method
will block until either a reply has arrived or the timer expires. Since
one cannot tell why waitForReply() returns, an additional call
to isAvailable() is needed:
FutureReply future = proxy.sendAsyncMessage(msg);
future.waitForReply(4000);
if (future.isAvailable()) {
String reply = (String)future.getReply();
...
} else
System.out.println("Timeout after 4 seconds: Aglet not responding.")
Full source code: TimeoutExample.java
and TimeoutChild.java.
Message Management
Each aglet has a guard called the message manager that allows deterministic
message handling by inserting incoming messages into a queue. It forwards
these messages one at a time to the aglet's message handler, in the same
order as they were received. It also ensures that the next message is not
forwarded until the current message has been handled. Technically, the
message manager serializes message handling.
The message manager features a number of methods that enable the aglet
to customize the behavior of the message manager. This set of methods includes
the ability of the aglet
-
to serialize messages,
-
to prioritize messages by their kind,
-
to allow parallel message handling,
-
to temporarily interrupt the handling of a message,
-
to suspend message handling, and
-
to destroy the message manager.
Serialized Message Handling
Let us start our coverage of the message handler by taking a look at message
serialization. The following example demonstrates serialized message handling.
One aglet (MsgManagerExample) sends three consecutive messages
("One", "Two", and "Three") to another aglet
(MsgManagerChild):
proxy.sendAsyncMessage(new Message("One"));
proxy.sendAsyncMessage(new Message("Two"));
proxy.sendAsyncMessage(new Message("Three"));
Messages are handled deterministically. That is, the MsgManagerChild
aglet will not start handling one message before it has completed the handling
of the previous message. In this particular example, the aglet will not
start handling message "Two" until it has finished handling message
"One", and it will not start handling message "Three"
until it has finished handling message "Two":
public boolean handleMessage(Message msg) {
if ("One".equals(msg.kind)) {
// Print to the console: Starting...
pause();
// Print to the console: Finished.
return true;
} else if ("Two".equals(msg.kind)) {
// Print to the console: Starting...
pause();
// Print to the console: Finished.
return true;
} else if ("Three".equals(msg.kind)) {
// Print to the console: Starting...
pause();
// Print to the console: Finished.
return true;
}
return false;
}
Full source code: MsgManagerExample.java
and MsgManagerChild.java.
The following interaction chart visualizes the execution trace of this
example:
Message Priorities
The rest of this section covers the advanced use of the message manager.
As a first step, the aglet needs to access its message manager. This is
done through the getMessageManager method defined in the aglet's
own interface.
public final MessageManager getMessageManager()
|
Gets the message manager.
|
Now that we have access to the message manager, we will demonstrate how
it allows you to set the priority of specific kinds of messages. Setting
a high priority for a given kind of message will ensure that it is placed
in the message queue ahead of messages with a lower priority. The default
priority of a message is 5. A user-defined priority can be set
by using the setPriority method in the MessageManager
class.
public void setPriority(String kind, int priority)
|
Sets the message's priority. The default priority for messages is 5.
|
To demonstrate the effect of this method, we will create a variant of the
above-mentioned message handling aglet that raises the priority of the
message "Three". The objective is ensure that the message "Three"
is handled before the messages "One" and "Two". The default
priority of a message is 5, so let us raise the priority of the
message "Three" to 9. We conveniently raise the priority
in the message-handling aglet's (MsgPriorityChild) onCreation
method:
public void onCreation(Object obj) {
try {
getMessageManager().setPriority("Three", 20);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
Full source code: MsgPriorityExample.java
and MsgPriorityChild.java.
The execution trace of this example is visualized by the following interaction
chart:
Parallel Message Handling
The message manager also allows messages to be handled in parallel. Calling
the exitMonitor method enables the aglet to continue the current
message-handling thread while simultanenously allowing a new message-handling
thread to be started.
public void exitMonitor()
|
Exits the current monitor.
|
In the MsgParallelChild aglet's handling of the message "Two",
we explicitly allow other messages to be handled in parallel. When the
aglet starts handling the message "Two", it immediately exits
the monitor (exitMonitor()). While it continues to handle the
message "Two", it will concurrently start handling the next message
in the queue, "Three".
public boolean handleMessage(Message msg) {
if ("One".equals(msg.kind)) {
// Print to the console: Starting...
pause();
// Print to the console: Finished.
return true;
} else if ("Two".equals(msg.kind)) {
// Print to the console: Starting...
try {
getMessageManager().exitMonitor();
} catch (Exception e) {
System.out.println(e.getMessage());
}
pause();
// Print to the console: Finished.
return true;
} else if ("Three".equals(msg.kind)) {
// Print to the console: Starting...
pause();
// Print to the console: Finished.
return true;
}
return false;
}
Full source code: MsgParallelExample.java
and MsgParallelChild.java.
This figure shows the aglet simultaneously handling the messages "Two"
and "Three":
Interrupt Message Handling
If the handling of a message for some (possibly external) reason cannot
proceed, the current thread can be suspended until further notice. In this
way, it will not block for the handling of subsequent messages. For this
purpose, the message manager defines three methods: waitMessage(),
notifyMessage(), and notifyAllMessages().
public void waitMessage()
|
Suspends the execution of this message-handling thread. The thread
waits until further notice.
|
Public void notifyMessage()
|
Notifies a single waiting message thread in this message manager.
|
Public void notifyAllMessages()
|
Notifies all waiting message threads in this message manager.
|
This example aglet, MsgWaitingChild, suspends the execution of
the message-handling thread of the message "Two". This allows
the aglet to handle the message "Three", which subsequently will
instruct the message manager to resume execution of the waiting message-handling
thread.
public boolean handleMessage(Message msg) {
if ("One".equals(msg.kind)) {
// Print to the console: Starting...
pause();
// Print to the console: Finished.
return true;
} else if ("Two".equals(msg.kind)) {
// Print to the console: Starting...
try {
getMessageManager().waitMessage();
} catch (Exception e) {
System.out.println(e.getMessage());
}
// print to the console: Continuing...
pause();
// Print to the console: Finished.
return true;
} else if ("Three".equals(msg.kind)) {
// Print to the console: Starting...
pause();
try {
getMessageManager().notifyMessage();
} catch (Exception e) {
System.out.println(e.getMessage());
}
pause();
// Print to the console: Finished.
return true;
}
return false;
}
Full source code: MsgWaitingExample.java
and MsgWaitingChild.java.
The interaction chart for this example is shown below.
Suspending Message Handling
The aglet may cease to accept any messages by invoking the destroy
method on its message manager. This call effectively makes the aglet non-responding.
All messages sent to the aglet will be rejected.
public void destroy()
|
Destroys the message manager. After invocation of this method, the
message manager is no longer valid, and all queued and incoming messages
will be rejected.
|
Multicasting
So far we have dealt only with peer-to-peer messaging where the message
sender has to know the identity of the receiver. You will often find this
a limitation when you plan to coordinate activities among multiple aglets.
In such cases, each aglet may not be aware of the identities of the other
aglets. Just imagine multiple aglets originating from different sources
that meet in a specific context. How does an incoming aglet advertise its
arrival? Fortunately, the context supports message multicasting within
a single context. Message multicasting provides a powerful ways for aglets
to interact and collaborate. The basic principle is that aglets (1) subscribe
to one or more multicast messages and (2) implement handlers for these
messages.
Aglets subscribe to specific multicast messages by invoking the subscribeMessage
method with the message kind as argument. Aglets can subscribe to multiple
multicast messages. To unsubscribe, the aglet has to invoke unsubscribeMessage()
for the specific message kind. It can also unsubscribe to all multicast
messages by invoking unsubscribeAllMessages(). Anyway, when an
aglet leaves its current context it will automatically unsubscribe to all
messages.
...
public final void subscribeMessage(String kind)
|
Subscribes to one kind of multicast message.
|
public final boolean unsubscribeMessage(String
kind)
|
Unsubscribes to one kind of multicast message.
|
public final void unsubscribeAllMessages()
|
Unsubscribes to all kinds of multicast messages.
|
In this example, an aglet automatically subscribes to "Hello Everybody"
when it is created:
public void onCreation(Object o) {
subscribe("Hello Everybody");
}
The aglet also implements a handler for the "Hello Everybody"
multicast message:
public boolean handleMessage(Message msg) {
if ("Hello Everybody".equals(msg.kind)) {
...
return true;
}
}
Now, let us switch to the context class that defines the multicastMessage
method in the AgletContext class. This method takes a message
object as argument and sends it to subscribers to the multicast message
in the current context. The message object for multicast messaging is identical
to the one used for normal messaging. A notable difference between multicast
messaging and normal aglet messaging is that multicast messaging is local
to a context. In other words, multicast messaging is not location-transparent.
public void multicastMessage(Message messaqe)
|
Sends a multicast message to the subscribers in the current context
|
This code example shows how an aglet sends "Hello Everybody" as
a multicast message in its current context:
Message msg = new Message("Hello Everybody");
getAgletContext().multicastMessage(msg);
Full source code: MulticastExample.java
and MulticastSubscriber.java.