The Aglet abstract class defines the fundamental methods (for example, dispatch(URL)) used to control the mobility and life cycles of mobile agents. All mobile agents defined in Aglet have to extend this abstract class. The Aglet.dispatch(URL) primitive causes an aglet to move from the local machine to the destination specified as its argument. The Aglet.deactivate(long time) primitive allows an aglet to be stored in secondary storage, and the Aglet.clone() primitive spawns a new instance of the aglet that has the same state as the original aglet. Note that the object returned by the clone primitive is not an Aglet object but an AgletProxy object.
The Aglet class is also used to access the attributes associated with an aglet. The com.ibm.aglet.AgletInfo object, which can be obtained by the Aglet.getAgletInfo() primitive, contains an aglet's inherent attributes, such as its creation time and codebase, as well as its dynamic attributes, such as its arrival time and the address of its current context.
The following table shows some primary methods and their semantics.
Method | Behavior |
dispose() | Dispose of the aglet. |
dispatch(URL) | Dispatch the aglet to the destination specified in the URL. |
deactivate(long duration) | Instruct the aglet to store itself into a persistent medium. |
getAgletInfo() | Get information on the aglet |
All aglet instances have their own unique identities that are immutable throughout the life cycle of the aglet. An identity may consist of several attributes such as a user's id, the type of an agent system, and some large number. The AgletID is an object that keeps an unique identifier for the given agent, while encapsulating its representation details.
The AgletProxy interface object acts as a handle of an aglet and provides a common way of accessing the aglet behind it. Since an aglet class has several public methods that should not be accessed directly from other aglets for security reasons, any aglet that wants to communicate with other aglets has to first obtain the proxy object, and then interact through this interface. In other words, the aglet proxy acts as a shield object that protects an agent from malicious agents. When invoked, the proxy object consults the SecurityManager to determine whether the caller is permitted to perform the method. Another important role of the AgletProxy interface is to provide the aglet with location transparency. If the actual aglet resides at a remote host, it forwards the requests to the remote host and and returns the result to the local host.
The AgletProxy can be obtained in the following ways:
The AgletContext class provides an interface to the runtime environment that occupies the aglet. Any aglet can obtain a reference to its current AgletContext object via the Aglet.getAgletContext() primitive, and use it to obtain local information such as the address of the hosting context and the enumeration of AgletProxies, or to create a new aglet in the context. Once the aglet has been dispatched, the context object currently occupied is no longer available, and the destination context object is attached instead when arrived.
The runtime library is responsible for providing the implementation of this interface; thus, aglet programmers do hot have to implement this interface.
Aglet objects communicate by exchanging objects of the Message class. A message object has a String object to specify the kind of the message and arbitrary objects as arguments. A message can be sent to the aglet by calling Object AgletProxy.sendMessage(Message msg) , FutureReply AgletProxy.sendAsyncMessage(Message msg) , or void AgletProxy.sendOnewayMessage(Message msg) and it is passed as an argument to Aglet.handleMessage(Message msg). Please see the section on messaging for more details.
A Ticket object is used to specify both of a destination and a quality of transfer. In other words, it defines the way in which an aglet is transferred. It may include the destination, the protocol to be used, and quality such as timeout, the level of integrity or confidentiality that must be secured. This object is used where URL were used as the way to specify the destination.
An object of the FutureReply interface is returned by the asynchronous message-passing and used as a placeholder to receive the result later asynchronously. With this interface, the receiver can determine whether a reply is available, and can wait for the result with a specified timeout value so that it can continue its execution if a reply was not returned within the specified time.
This method is called only once during its lifecycle, when it is created. Aglet programmers have to use onCreation(Object init) to initialize an aglet, because the Aglet API (e.g. dispatch(URL)) is not ready in the constructor.
This method is called just after the dispose() method is called. The aglet should release any resources previously allocated. It can perform additional actions in response to its own disposal.
The run() method in the Aglet class is called whenever an instance is created or resumed. Because this method is called whenever it occupies the context, this is a good place to define the common task.
All messages sent to the aglet are passed to the handleMessage method. Aglet programmers can check whether the incoming message is a known message, and can perform the task according to the kind of message.
public class HelloAglet extends Aglet { public void onCreation(Object init) { System.out.println("created!"); } public void run() { System.out.println("hello!"); } public boolean handleMessage(Message msg) { if (msg.sameKind("sayHelloAgain") { System.out.println("hello!"); return true; } return false; } public void onDisposing() { System.out.println("bye!"); } }
Once created, an aglet object can be dispatched to and/or retracted from a remote server, deactivated and placed in secondary storage, then activated later.
Example: atp://aglets.ibm.com:1434/context_nameDispatching causes an aglet to suspend its execution, serialize its internal state and bytecode into the standard form and then to be transported to the destination. On the receiver side, the Java object is reconstructed according to the data received from the origin, and a new thread is assigned and executed.
Aglets can be persistent. Since a mobile aglet needs to be serializable into a bit-stream, all mobile aglet can be persistent in nature. The Aglet.deactivate(long timeout) primitive causes an aglet to be stored in secondary storage and to sleep for a specified number of milliseconds. After the given time has passed or another program has requested its activation, the aglet is activated within the same context where as that in which it was deactivated.
Unlike normal Java objects, which are automatically released by garbage collector, an aglet object, since it is active, can decide whether or not to die. If you call the dispose() method to kill the aglet, onDisposing() is called to perform the finalization suitable for the current state of the aglet. (Note that this is different from Java's finalizer(), which is invoked when the object is garbage-collected.) Aglet programmers are responsible for releasing allocated resources such as file descriptors or DB connections, because these may not be released automatically.
import com.ibm.aglet.Aglet; import com.ibm.aglet.event.MobilityEvent; import com.ibm.aglet.event.MobilityListener; class MyListener implements MobilityListener { public void onDispatching(MobilityEvent l) { closeWindow(); closeFile(); } public void onReverting(MobilityEvent l) { openWindow(); donNextJob(); } public void onArrival(MobilityEvent l) { } } public class MyAglet extends Aglet { public void onCreation(Object init) { MobilityListener listener = new MyListener(); addMobilityListener(listener); } }
The onDispatching() method of the MyListener object is called before an aglet is actually dispatched, and the onArrival() method is called after it arrives at its destination. If an aglet has multiple listeners, they are called in the order in which they were attached. In this way, an aglet programmer can implement an action such as onDispatching that should be taken in response to the following events, regardless of when or by whom an aglet is dispatched.
When | Event Object | Listener | Method called |
---|---|---|---|
Just before cloning | CloneEvent | CloneListener | onCloning |
When clone is created | CloneEvent | CloneListener | onClone |
After creation of clone | CloneEvent | CloneListener | onCloned |
Just before dispatch | MobilityEvent | MobilityListener | onDispatching |
Just before retraction | MobilityEvent | MobilityListener | onReverting |
After arrival at the destination | MobilityEvent | MobilityListener |
onArrival |
Just before deactivation | PersistencyEvent | PersistencyListener | onDeactivating |
After activation | PersistencyEvent | PersistencyListener | onActivation |
public class Updater extends MobilityAdapter { AgletProxy _finder; Message _update_msg = new Message("update"); public Updater(AgletProxy finder, String name) { _finder = finder; _update_msg.setArg("name", name); } public void onArrival(MobilityEvent ev) { try { _update_msg.setArg("proxy", ev.getAgletProxy()); _finder.sendMessage(_update_msg); } catch (Exception ex) { } } public void removeFromRegistry() { try { Message remove = new Message("remove"); remove.setArg("name", _update_msg.getArg("name")); _finder.sendMessage( remove ); } catch (Exception ex) { } } } public class Registry extends Aglet { Hashtable _registry = new Hashtable(); public boolean handleMessage(msg msg) { if (msg.sameKind("Lookup") { msg.sendReply(_registry.get(msg.getArg("name"))); } else if (msg.sameKind("Update") { _registry.put(msg.getArg("name"), msg.getArg("proxy")); } else if (msg.sameKind("Remove") { _registry.remove(msg.getArg("name")); } else return false; return true; } }One thing programmers should bear in mind is that these listener objects are called in sequence. Therefore, the listener object being added last may not be called, depending on the behavior of the former listeners. For example, if a listener object disposes of the aglet in the onArrival, subsequent listeners are never invoked.
class ListingAglet extends Aglet { // result and its contents are transferred. private Vector result = new Vector(); transient InputStream in = new InputStream(); // will not be transferred }
Through the serialization/deserialization process, ordinary objects that are a part of its state are moved by value. One of the consequences of this is that once serialized, an object shared by multiple aglets is copied and is no longer shared after the dispatch, clone and deactivate/activate operations.
When an aglet proxy is transferred, on the other hand, it keeps the aglet id and its address, and uses that information to restore the correct reference to the original aglet. Therefore, AgletProxy objects can keep the reference to the actual aglet even if the proxy is transferred to a remote host or deactivated, as long as the aglet resides at the same location.
class Transferrable implements java.io.Serializable { Hashtable hash; // Hashtable is also serializable } class NotTransferrable { int dummy; } class MyClass extends Aglet { transient FileDescriptor fd; // never get transferred. int value; // moved by value String str; // moved by value Object transferable = new Transferrable(); // move by value Object not_transferable = new NonTransferable(); // throws NotSerializableException. AgletProxy proxy; // moved by reference }
Since a class variable is not a part of an object, the values of class variables are never serialized. Thus, class variables are local to their class, and the aglet may obtain a different value when it arrives at a new destination.
public class MyAglet { static int class_variable = 0; public void onCreation(Object init) { class_variable = 10; dispatch("atp://next.place"); } public void run() { if (class_variable != 10) { System.out.println("Class variable never get transferred!"); } } }
The remote interface of RMI is used to define remote objects whose methods can be invoked remotely. A client-side RMI object (stub) works well with the Aglets library without modification. That is, when an aglet is dispatched along with a remote object, the remote object is automatically rebound to the server object when the aglet is unmarshaled.
However, Aglets cannot handle server objects. Transfer of an RMI server object results in the creation of another copy of the server object at the destination site.
public class MyAglet extends Aglet { RMIHelloImpl impl = new RMIHelloImpl(); String naming = "//naming.com/hello"; public void onCreation(Object init) { java.rmi.Naming.rebind(naming, impl); addMobilityListener(new MobilityAdapter() { void onArrival(MobilityEvent ev) { java.rmi.Naming.rebind(naming, impl); impl.hello(); // calling local method // It cannot be Stub anyway because it's impl! } }); } }This is a limitation of RMI in JDK1.1. (Note: JDK1.2 supports "unexporting" of a remote object, which solves the above problem.)
Aglet programmers who want to customize writeObject/readObject methods on serializable objects should be aware that onDispatching() is called while the aglet is running, while writeObject() is called after the aglet has been suspended. Similarly, readObject() is called while the aglet is being unmarshaled, which means that Aglet primitives such as getAgletContext() do not work. On the other hand, onArrival() is called after the entire aglet has been restored and activated successfully, so you can call the Aglet API.
Currently, the AgletProxy cannot keep track of roaming aglets. Once an aglet has been dispatched, the proxy previously referencing the aglet is no longer valid. A mechanism for preserving the reference will be provided in the future.
MyAglet extends Aglet { public boolean handleMessage(Message msg) { if (msg.sameKind("doJob")) { doJob(); } else if (msg.sameKind("shutdown")) { deactivate(0); } } }Aglets supports the following types of message passing:
String answer = proxy.sendMessage(new Message("question")); System.out.println(answer);
FutureReply future = proxy.sendAsyncMessage(new Message("question")); int num_task = 10; // do private job at most 10 times while waiting for the result. while(future.isAvailable() == false && num_task-- >0) { doPrivateJob(); } System.out.println( (String)future.getReply() );Note: If an aglet sends a message to itself, the message is not put at the tail of the queue. Instead, it is placed at the head of the queue and executed immediately, to avoid deadlock.
proxy.sendOnewayMessage(new Message("question"));
public boolean handleMessage(Message msg) { if (msg.sameKind("sayHello")) { System.out.println("Hello"); return true; // i know this message... } return false; // false, otherwise }If it returns false, the sender of the message receives a NotHandledException and thus knows that the message has not been handled. There is no way of knowing whether a one-way message has been handled or not.
Future future = proxy.sendAsyncMessage(); ... try { Object reply = future.getReply(); } catch (NotHandledException ex) { // the receiver didn't handled the message } catch (MessageException ex) { // an exception has been thrown in the receiver's handleMessage() System.out.println(ex.getException()); }Messages in Aglets also support acknowledge-type replies, whereby the receiver can send the reply (result) even before completing the handling of the message. When you have a return value, you need to use this interface to return the value. Once you send a reply via the Message.sendReply() method, you cannot send a reply again, and the return value of handleMessage will be ignored.
public boolean handleMessage(Message msg) { if (msg.sameKind("accessDB")) { openDB(); // pseudo code Object reply = accessDB(msg.getArg()); msg.sendReply(reply); closeDB(); return true; // i know this message... } return false; // false, otherwise }
queue [A] -> {}: Aglet [B] -> {[A]}: Aglet [C] -> {[B][A]}: Aglet {[C][B]}: Aglet ( handling [A] ) {[C]}: Aglet ( handling [B] ) {}: Aglet ( handling [C] )You can specify the priority associated with kinds of messages. The messages with high priority will be handled before messages with lower priority. Because the message is handled concurrently, giving the priority does not guarantee the order. It is simply to increase the likelihood that a certain message is handled earlier than the other.
Suppose that there are three messages in the queue, with the priorities 3, 5, and 6, and that you send two messages with priorities 4 and 7 asynchronously. Even if you sent the message with priority 4 first, the message with priority 7 will be handled first.
{[3][5][6]}: Aglet [4] -> {[3][5][6]}: Aglet [7] -> {[3][4][5]}: Aglet (handling [6]) {[3][4][5][7]}: Aglet (handling [6] cont'd) {[3][4][5]}: Aglet (handling [7])Aglets also supports a NOT_QUEUED priority, whereby the message is not queued. Suppose the Message [D] has priority NOT_QUEUED; then that message will be passed to an aglet and handled immediately in parallel.
[A] -> {}: Aglet [B] -> {[A]}: Aglet [D] -> {[B][A]}: Aglet {[B]}: Aglet ( handling [A] ) ( handling [D] ) {}: Aglet ( handling [B] ) ( handling [D] ) {}: Aglet ( handling [D] )You can set priority of the message by the primitive MessageManager.setPriority(String, int), and a MessageManager object of the agent can be obtained via Aglet.getMessageManager() primitive.
If another thread that is handling another message instructs the waiting thread to resume the waiting thread by calling Aglet.notifyMessage(), it wakes up and resumes execution. If it is not instructed to resume, it will wait forever until it is stopped. On the other hand, the waitMessage(long timeout) is used to wait at most for timeout milliseconds, and wakes up when the specified timeout expires.
queue waiting {[D][C]}: Aglet {[A][B]} {[D]}: Aglet {[A][B]} (handling[C] calls notifyMessage()) {[D][C]}: Aglet {[B]} (handling[A])Aglet.notifyAllMessages() causes all waiting threads to wake up one by one. These threads are placed at the head of the message queue and the oldest waiting thread is immediately resumed. The caller thread is placed at the next position of these thread and will be resumed alter these resumed thread has completed.
queue waiting {[D][C]}: Aglet {[A][B]} {[D]}: Aglet {[A][B]} (handling[C] calls notifyAllMessages()) {[D][C][B]}: Aglet (handling[A])
queue {[C][B]}: Aglet (handling[A]* calls exitMonitor()) {[C]}: Aglet (handling[B]*) (handling[A]) Note: * indicates the owner of the monitorWith this API, you can implement a background task while receiving the message.
public void SiteWatcherAglet extends Aglet { public void run() { exitMonitor(); // now the next message would start. while( condition == true) { watchSite("http://home/index.html"); } } }
public StackAglet extends Aglets { static int capacity = 10; Object stack[] = new Object[capacity]; int num = 0; public handleMessage(Message msg) { if (msg.sameKind("push")) { push(msg); } else if (msg.sameKind("pop")) { pop(msg); } else if (msg.sameKind("isFull")) { msg.sendReply( num == capacity); } else if (msg.sameKind("isEmpty")) { msg.sendReply( num == 0 ); } else return false; return true; } private void push(Message msg) { while (num == capacity) { waitMessage(); } stack[num++] = msg.getArg(); if (num==1) { notifyMessage(); // wake up "pop" message } } private void pop(Message msg) { while(num==0) { waitMessage(); } msg.sendReply(stack[--num]); if (num == (capacity -1)) { notifyMessage(); // wake up "push" message } } }
The Aglets architecture consists of two layers, and two APIs that define interfaces for accessing their functions.
The Aglets runtime itself has no communication mechanism for transferring the serialized data of an aglet to destinations. Instead, the Aglets runtime uses the communication API that abstracts the communication between agent systems. This API defines methods for creating and transferring agents, tracking agents, and managing agents in an agent-system- and protocol-independent way.
The current Aglets uses the Agent Transfer Protocol (ATP) as the default implementation of the communication layer. ATP is modeled on the HTTP protocol, and is an application-level protocol for transmission of mobile agents. To enable remote communication between agents, ATP also supports message-passing.
// MAF IDL module MAF { ..... struct Name { Authority authority; Identity identity; AgentSystemType agent_system_type; }; .... interface MAFAgentSystem { // Agent creation and transfer Name create_agent(in Name agent_name, ...) raises (..); void receive_agent(in Name agent_name, in AgentProfile agent_profile, in octet_string agent, ...) raises (..); // Agent management void get_agent_status(in Name agent_name) raises (..); void list_all_agents(in Name agent_name) raises (..); void suspend_agent(in Name agent_name) raises (..); void resume_agent(in Name agent_name) raises (..); void terminate_agent(in Name agent_name) raises (..); // etc... } }Although MASIF interfaces are intended for CORBA objects, the interfaces actually defined in Aglets are not CORBA-based. In fact, they are defined as normal Java classes, interfaces or abstract classes that act as common wrappers for the protocols actually being used. Thus, it is possible and easy to use various kinds of protocol other than CORBA/IIOP such as ATP and RMI.
We chose this approach because relying on the specific transport protocol or the specific transport mechanisms has at least two disadvantages. First, it would be technically inadequate to require mobile agent systems to use a specific protocol unless it became pervasive and widely supported. One of the benefits of mobile agents is in their ability to hide the existence of network communication. Second, some Java environments such as PersonalJava have neither RMI nor CORBA as their core API. Therefore, it would be more practically desirable if the runtime could choose the communication mechanisms in accordance with its system requirements.
The following figure shows the architecture of the communication layer. com.ibm.maf.MAFAgentSystem is an abstract class that defines a set of methods equivalent to the MASIF interface. There are two kinds of class that extend this abstract class. One is an implementation class that provides the agent system facility, and the other is a stub object that transfers a request to a destination.
On the other hand, an aglet server has an implementation of
MAFAgentSystem that actually handles the requests. It is the
agent-system provider's responsibility
to provide the implementation of MAFAgentSystem. Aglets has the com.ibm.aglets.MAFAgentSystem_AgletsImpl
class as an implementation. Furthermore, a server has one or more daemons
to accept requests from a sender. A server may support multiple protocols
by having multiple daemons to handle each protocol. When a daemon accepts
requests, it then forward these requests to the MAFAgentSytem_AgletsImpl.
Note:
CORBA-based transport layer will be provided in a future release
of Aglets.
An AgletRef object is an internal representation of an aglet object. It has all necessary components for the aglet a MessageManager to control incoming messages, and a ResourceManager to manage resources consumed by the aglet and to manage its related resources such as security information and the AgletInfo object. It is also the implementation of the abstract methods defined in Aglet class. It implements most of the functionality to be provided by the com.ibm.aglet.Aglet class. The AgletRef object is what the aglets framework deals with. The latter has a reference table to store the mapping from the AgletID to the actual aglet instance. An AgletRef object is created and inserted into this table when an aglet is created or arrives, and removed from it when the aglet is dispatched or disposed of.
An aglet has a MessageManager object, which governs all incoming messages sent to the aglet. The MessageManager manages and controls the order and concurrency of these messages. See the Messaging section for more details.
An aglet uses local resources such as files or threads to carry out its tasks during its lifetime. It may open a dialog window to interact with users, or create a new thread to perform some concurrent task.
These resources are managed by a ResourceManager object, which is allocated for each aglet. Currently all threads and windows created by the aglet are managed by this ResourceManager. When an aglet is dispatched, deactivated, or disposed of, these two resources are immediately stopped and disposed of. Other kinds of resources such as file and socket, however, are not captured by this manager. It is left to the garbage collector to decide whether or not such resources will be released, and if so when. Therefore, the programmer must release them manually to ensure their immediate release.
Unlike normal Java objects, aglets are never garbage-collected (GC) automatically, because an aglet is active and has its own threads of control. An aglet programmer needs to explicitly dispose of an aglet.
When an aglet has been dispatched, deactivated, or disposed of, the AgletRef object is removed from the reference table. In addition, the internal reference to that aglet and associated components such as MessageManager object or properties are set to null so that the garbage collector can sweep up these dangling objects.
This means that if you have a live reference to this aglet elsewhere, it will not be GCed. For example, if you have a reference to the static variable of the class, it will not be GCed.
public MyAglet extends Aglet { static MyAglet aglet = null; // the previous one may be GCed. public void onCreation(Object init) { aglet = this; // keep the reference } }
In mobile agent systems, classes for an agent need to be available at the server on which the agent is running. The class of an agent needs to be available at the server at the time of creation, and to be available at the destination to which it moves. Therefore, a mobile agent system needs to have a facility for loading bytecode on demand, or for transferring the bytecode along with the agent.
Aglets supports two schemes for transferring bytecode more efficiently ways, and also make use of a cache to reduce unnecessary downloading of classes. It is important for aglet programmers to understand class mobility, how a class is loaded, and when a class is transferred. The rules of class mobility are explained in the following sections:
Java has a special class, called ClassLoader, that is capable of defining a new class from bytecode. Once a class has been defined by the class loader, all requests for new classes within that class are handled by that class loader. Aglets has a dedicated subclass of ClassLoader, called AgletClassLoader. Each aglet is associated with exactly one class loader, and all classes required by the aglet are managed by that class loader. (Note that one class loader may manage multiple aglet instances.) Suppose there is an aglet
Ex.1 class MyAglet extends Aglets { MyDialog dialog = null; public void onCreation() { dialog = new MyDialog(); } }and that both MyAglet and MyDialog classes are placed in the codebase below:
atp://aglets.codeserver.com/publicIn this example, both MyAglet and the MyDialog classes are managed by one AgletClassLoader.
There may be several sources of the bytecode of a given class. It is therefore worth understanding which class definition is actually chosen by the class loader and used. AgletClassLoader maintains a cache table for classes formerly defined by the class loader, and it first looks up a definition in that cache table. If a definition is not found in the cache , it asks the system class loader to load it from CLASSPATH (for security). If this is not successful either, it defines a new class by extracting bytecode from CacheManager if this is available, or by loading from the codebase otherwise. Once the new class has been defined, it is cached in the cache table and will be reused by other aglets later.
Ex.2 class MyAglet2 extends Aglets { MyDialog dialog = null; String str = "Hi"; // LocalData is installed on the CLASSPATH LocalData data = new LocalData(); public void onCreation(Object init) { dialog = new MyDialog(); } }If MyAglet2 is loaded by the same classloader of MyAglet, the cached class is used to resolve the MyDialog class. If it is loaded by a different classloader, it will use the bytecode in the CacheManager to define a new MyDialog class in the class loader. Since both the java.lang.String class and the LocalData class are locally available on the CLASSPATH, they will be loaded by the system class loader and then become system classes.
As mentioned before, there are two possible ways of bringing bytecode to the server. The first is to download and define a class on demand after an aglet moves, and the second is to transfer bytecode as an agent moves. Aglets supports these two schemes, and in some case uses a combination of the two. This subsection illustrates the rules according to which classes are transferred.
Aglets classifies Java classes into four categories, according to where they came from, and manages them in different ways:
If no archive is specified, only classes loaded from the codebase are transferable along with the aglet. By this we mean that all classes located in the aglet's codebase can be transferred when it moves except when a class with the same name exists on the CLASSPATH. Such system classes are never transferred. In that of Ex.2, the bytecode of the LocalData class would never be transferred while that of MyDialog might be. The set of classes to be transferred are determined at runtime in the serialization process. That is, the classes of all objects visited during the serialization are collected and transferred.
Note that the class of a reference with null value is never transferred. So what happens if such a reference exists and the class is used after the aglet is dispatched? Let's take a look at an example:
Ex.3 class MyAglet extends Aglet { MyDialog dialog = null; public void onCreation(Object init) { addMobilityListener(new MobilityAdapter() { public void onArrival(MobilityEvent ev) { // create MyDialog after arriving at the destination. dialog = new MyDialog(); } }); dispatch(....); } }Since the reference "dialog" is null, the MyDialog class is not transmitted when the aglet is dispatched. Instead, the aglet is sent without the MyDialog class and then searches for the MyDialog class after it arrives. Because it tries to load the class by following the class loading rule explained above, it may load the class definition from its codebase, or it may use the cached class, if any exists.
If you want to make sure that such classes are also transferred, you may include the class instance as its state, like this:
Ex.4 class MyAglet extends Aglet { MyDialog dialog = null; Class class_used_later = MyDialog.class; }This forces the system to transmit MyDialog along with the aglet.
Finally, if an aglet attempts to transfer an object whose class loader is different from the aglet's class loader, it fails with a SecurityException. This is because Aglets does not allow an aglet either to load a class from two different codebases or to extract the bytecode from other aglets, for security reasons.
Since ClassLoader cannot define two classes with the same name, the old class already stored in the cache may be used even if the updated class has been sent. This would cause a problem in deserializing objects because the serialized data might not compatible with it. Even worse, the class will behave differently.
Ex.5 // old class class MyAglet extends Aglet { MyDialog dialog = new MyDialog(); } // revised class class MyAglet extends Aglet { MyDialog dialog = new MyDialog(); public void onCreation(Object init) { dialog.show(); // I forgot to show the dialog.. } } # MyAglet must be used at the destinationEven if you fix the source code (Ex.5), you may get the same result as before because it might be cached. Therefore, a class definition must be forcibly updated as it evolves, while the cost of transmitting and defining new classes must be reduced. The Aglets solves this problem by allocating different class loaders for different sets of classes. In the following section, we describe how a class loader is chosen and when one is newly created.
To avoid the version conflict problem, Aglets sends the information on the version of a class as well as its name along with the class definition. AgletClassLoader also maintains this information in the cache. This version information is taken from the MANIFEST file in a JAR archive or computed at runtime.
With this information, the receiver can find out what classes are used within the aglet even before receiving all the bytecode. Thus, it can determine the class loader that matches the information in the "ClassLoader cache". Note that only classes sent along with the aglet are evaluated in searching the class loader. If an aglet requires another class that has not appeared in this version table since the aglet arrives (MyDialog in Ex.3), the same problem may occur.
Note that Aglets does not support the automatic version detection of aglets created from plain codebase (without JAR). You may therefore get an old version of classes. We recommend you to use a JAR archive if you want to make sure that the correct version is used for creation.
The current Aglets, however, sends as much bytecode as possible, because the above scheme has several drawbacks and the *best* scheme is not yet established. For example,
Furthermore, it is impossible for an aglet to dispatch itself back into the intranet through the firewall. To get back inside a firewall, an aglet has a unique primitive, called retract, which lets a client "pull" the dispatched aglet from a remote site. This allows you to dispatch an aglet outside the firewall and get it back into the intranet.
Aglets uses an organizational approach whereby all agent systems in a certain domain are deemed trustworthy, and evaluates the authenticity of the agent depending on the domain in which it has been roaming around. A user first authenticates himself/herself to the system, and the system then issues the credentials of the user's agent. The agent system then evaluates the authenticity of the credentials, to determine whether or not they were issued within the same domain. It may downgrade the authenticity or simply deny access, depending on conditions such as where the agent has traveled and so forth. Host authentication is used to identify the domain to which the communicating host belongs.
Although the current Aglets does not fully support these services because of the limited support for encryption in JDK, it does provide a reasonable level of security to make it safe to use mobile agent applications. The following security features are supported in the latest Aglets runtime:
Aglet servers are able to authenticate whether the contacting server belongs to a certain domain. All servers that belongs to one domain share a secret key, and can authenticate the contacting server by means of that secret key using MAC (Message Authentication Code: a secure hash value computed from a content and nonce value). The advantage of this is that MAC does not have to be signed by means of encryption algorithms, and it can simply be implemented on top of vanilla JDK.
After the authentication between servers has been established, the credentials of the aglet are sent along with the aglet. The receiver will then decide how much it trusts the credentials sent by the server on the basis of the information obtained by the host authentication. In Aglets, the server simply trusts the credentials if they were sent from the server in the same domain.
To use the domain authentication, each user (or server administrator) needs to obtain the secret key of the domain from the domain authority. The domain authority is responsible for generating a domain key for a specific server. The shared secret key is signed with the user's password, and thus the user is required to give the correct user id and password to make it effective. This key file must be kept secret, because it is not encrypted.
One disadvantage is that it cannot identify and verify the communicating
hosts. Once the shared key is stolen from a server in the domain,
there is no way of distinguishing valid servers and the server from which the
key was stolen. As a result, all servers in the same domain are exposed
to dangers.
java.io.FilePermission : File read/write/execute java.net.SocketPermission : Socket resolve/connect/listen/accept java.awt.AWTPermission : showWindowWithoutWarningBanner, accessClipboard java.util.PropertyPermission : Java property java.lang.RuntimePermission : queuePrintJob, load library java.security.SecurityPermission : getPolicy, setSystemScope java.security.AllPermission : all other permissions com.ibm.aglets.security.ContextPermission : context property, start, shutdown com.ibm.aglets.security.AgletPermission : dispatch, deactivate, etc. com.ibm.aglets.security.MessagePermission : messaging
The principal name is the user name of the target aglet (e.g., "moshima", "guenter", "this", or "anonymous"), and the operation name is the method name of the Aglet class (e.g., "dispatch", "dispose");
The principal name is the user name of the target aglet (e.g., "moshima", "guenter", "this", or "anonymous"), and the kind of the message is the kind of message to be sent. The kind of message should be prefixed with "message" (e.g., "message.show", "message.getResult");
grant codeBase "http://trusted.com", ownedBy "oshima" { permission java.io.FilePermission "C:\\temp", "read"; permission com.ibm.aglets.security.ContextPermission "property.*", "write"; permission com.ibm.aglets.security.AgletPermission "oshima", "dispose"; permission com.ibm.aglets.security.MessagePermission "oshima", "message.getResult"; }This policy file is created at $HOME/.aglets/security/aglets.policy on an idividual user basis. A domain-based policy is not yet supported. Once supported, the domain authority should be able to specify domain-wide permissions. Once code signatures are supported, "signedBy" will be available as a means of specifying the manufacturer of an aglet.
The left field "Code Base" indicates the triples of codeBase phrase, signedBy phrase and ownedBy phrase in policy file. The right field indicates permissions related to the codebase selected in the left field. For each codebase/signers/owner of aglets, the access control list specified in the left field shall be used. The menu at the top of right field shows the selected kind of permission. The selected kind "FileSystem" means "java.io.FilePermission". And the field "FileSystem" shows FilePermissions for selected codebase.
With this configuration, an incoming aglet normally gains access to services via message passing. Then, it can leave for the next server along with the result obtained in the server, or send it to the home server by remote message passing and die. Message passing is also under the control of the security manager and a receiver aglet can also deny a request.
The server can be also configured so that it allows a limited set of aglets to access the local resources and services. In this case, for example, an aglet may open a direct connection to a database and issue a SQL request. This gives an aglet great flexibility in an using the server resources, while this also creates the risk that an aglet may misuse or abuse these resources, and eventually may cause the system to misbehave or even crash.
For example, a administration tool would provide comprehensive ways of creating service aglets, monitoring visiting aglets, and disposing of them if necessary. Furthermore, the viewer can provide service for an incoming aglet in response to its arrival. For example, a gateway server may offer an incoming aglet the itinerary object for a further trip inside the gateway when it arrives.
On the other hand, the viewer can be customized for users or specific services. For example, a desktop-like interface may use an icon for aglets and drag and drop operations to manipulate them for people who is familiar with PCs, while a kiosk for novice users will define a single-click, Web style interface.
The Aglets Server API allows developers to embed and bootstrap the aglets server in their applications, and to configure the server's SecurityManager, Persistence, Viewer, and so on.
Furthermore, an application may not want to accept incoming aglets while it is creating an aglet or dispatching and retracting it to obtain a service. The Server API allows an application to run the Aglets runtime library without daemon capability for this purpose.
For example, the console of a massive network management system may not have to install the server facility. The console application, which typically has a client capability, can create a monitor aglet on a machine, and let a detective aglet roam multiple machines and send information back to the console.
The ContextEvent class and ContextListener interface allow you to monitor the activities of aglets in the context and to take actions in response to these activities. The typical application of this API is "AgletViewer" which displays a list of aglets running in the context and lets you control them by, for example disposing of them or dispatching them.
The Aglets Server interface makes it possible to write an application capable of hosting, receiving, and dispatching aglets. With this interface, you can take advantage of mobile agent technology in your application without running an independent, separate server program such as Tahiti.
The other methods are for receiving notification of the activities of aglets. It is guaranteed that when an event occurs, the proxy of an aglet given as a parameter in the ContextEvent will still be available via the getAgletProxies() primitive. If an aglet is being disposed of, however, the instance of the aglet is in the middle of an invalidating process and may have an intermediate state. Therefore checking methods such as isValid(), may not return correct values.
package com.ibm.aglet.system; public interface ContextListener { public void contextStarted(ContextEvent ev); public void contextShutdown(ContextEvent ev); public void agletCreated(ContextEvent ev); public void agletCloned(ContextEvent ev); public void agletArrived(ContextEvent ev); public void agletActivated(ContextEvent ev); public void agletReverted(ContextEvent ev); public void agletDisposed(ContextEvent ev); public void agletDispatched(ContextEvent ev); public void agletDeactivated(ContextEvent ev); public void agletStateChanged(ContextEvent ev); public void showDocument(ContextEvent ev); public void showMessage(ContextEvent ev); }agletStateChanged() is called when an aglet changes its state by, for example, calling the Aglet.setText(String) primitive. showDocument() is called when an aglet calls the Aglet.showDocument(URL) primitive to request that a specified document be shown. It is left to the context implementor to decide whether or how to implement the showDocument method. The default implementation of Tahiti launches an external Web browser, but another implementation may use an internal browser written in Java to load and display an HTML document.
package com.ibm.aglet.system; public class ContextEvent extends AgletEvent { public static final int CONTEXT_FIRST = 1000; public static final int CONTEXT_LAST = 1014; public static final int STARTED = CONTEXT_FIRST; // 1000 public static final int SHUTDOWN = CONTEXT_FIRST + 1; // 1001 public static final int CREATED = CONTEXT_FIRST + 2; // 1002 public static final int CLONED = CONTEXT_FIRST + 3; // 1003 public static final int DISPOSED = CONTEXT_FIRST + 4; // 1004 public static final int DISPATCHED = CONTEXT_FIRST + 5; // 1005 public static final int REVERTED = CONTEXT_FIRST + 6; // 1006 public static final int ARRIVED = CONTEXT_FIRST + 7; // 1007 public static final int DEACTIVATED = CONTEXT_FIRST + 8; // 1008 public static final int ACTIVATED = CONTEXT_FIRST + 9; // 1009 public static final int STATE_CHANGED = CONTEXT_FIRST + 10; // 1010 public static final int SHOW_DOCUMENT = CONTEXT_FIRST + 12; // 1012 public static final int MESSAGE = CONTEXT_FIRST + 13; // 1013 public static final int NO_RESPONSE = CONTEXT_FIRST + 14; // 1014 public Object arg = null; public AgletContext getAgletContext(); public AgletProxy getAgletProxy(); public String getMessage(); public String getText(); public URL getDocumentURL(); public ContextEvent(int id, Object context, AgletProxy target, Object arg); public ContextEvent(int id, Object context, AgletProxy target); }
The com.ibm.maf.MAFAgentSystem is an abstract class that defines
a set of method for creating, transferring, and managing agents. The interface
is (almost) equivalent to methods defined in the MASIF standard specification,
but not identical to the MASIF IDL, because this is not CORBA interface,
as mentioned before. See the Architecture
of Communication Layer section for more details of the architecture.
The getMAFAgentSystem primitive returns the instance of MAFAgentSystem
for the given address. The AgletsRuntime then uses this object to communicate
with the remote agent system. The returned object may be a stub object for
remote access, or a local server object if the address is its own.
package com.ibm.maf.MAFAgentSystem; public class MAFAgentSystem { static public MAFAgentSystem getMAFAgentSystem(Ticket ticket); static public MAFAgentSystem getMAFAgentSystem(String address); static public MAFAgentSystem getLocalMAFAgentSystem(); static public void initMAFAgentSystem(MAFAgentSystem runtime, Name name); static public void startMAFAgentSystem(MAFAgentSystem runtime, String protocol); // MASIF interfaces public Name create_agent(Name name, AgentProfile profile, byte[] agent, Object[] arguments, ClassName[] class_names, String code_base, MAFAgentSystem class_provider); public abstract void receive_agent(Name agent_name, AgentProfile agent_profile, byte[] agent, String place_name, ClassName[] class_names, String code_base, MAFAgentSystem class_sender); public abstract byte[][] fetch_class(ClassName[] class_name_list, String code_base, AgentProfile agent_profile); public abstract String find_nearby_agent_system_of_profile(AgentProfile profile); public abstract MAFFinder get_MAFFinder(); public abstract AgentStatus get_agent_status(Name agent_name); public abstract AgentSystemInfo get_agent_system_info(); public abstract AuthInfo get_authinfo(Name agent_name); public abstract Name[] list_all_agents(); public abstract Name[] list_all_agents_of_authority(byte[] authority); public abstract String[] list_all_places(); public abstract void resume_agent(Name agent_name); public abstract void suspend_agent(Name agent_name); public abstract void terminate_agent(Name agent_name); }
The com.ibm.aglet.system.AgletRuntime abstract class provides an interface for creating and managing the context. The class actually used and the instance are automatically chosen and created by the framework. Applications should not try to create a runtime object by themselves. Instead, an application must use the getAgletRuntime() primitive to obtain a reference to the runtime object.
ThecreateAgletContext(String name) primitive creates a new instance of the runtime with the given name. If there is an existing context with the same name, the primitive throws an IllegalArgumentException. These contexts created in the AgletRuntime object can be obtained by either the getAgletContext(String name) or the getAgletContexts() primitive. removeAgletContext(AgletContext cxt) removes the specified context from the internal context table. A living AgletContext can be removed from the runtime table without shutdown. Such a context becomes invisible, and cannot be accessed either remotely or locally. This can be used to create a sort of "private" context into which no aglet be dispatched.
class AgletRuntime { AgletRuntime getAgletRuntime(); public AgletContext createAgletContext(String name); public AgletContext getAgletContext(String name); public void removeAgletContext(AgletContext cxt); public AgletContext[] getAgletContexts(); }
class Aglets { public static AgletProxy createAglet(String contextAddress, URL codebase, String classname, Object init); public static AgletProxy getAgletProxy(String contextAddress, AgletID id); public static AgletProxy[] getAgletProxies(String contextAddress); }You are not required to do the bootstrapping for client applications.
public void main(String args[]) throws Exception { String contextAddress = "atp://host.name:434/contextName"; AgletProxy proxy = Aglets.createAglet(contextAddress, null, "your.Aglet", null); proxy.sendMessage(new Message("buy", "jewel")); AgletProxy proxies[] = Aglets.getAgletProxies(contextAddress); // dispose all aglets for (int i=0; i < proxies.length; i++) { proxies[i].dispose(); } }
public class MyServer { static private Opt options[] = { Opt.Entry("-protocol", "maf.protocol", null), }; static public void main(String args[]) { Opt.setopt(options); AgletRuntime runtime = AgletRuntime.init(args); runtime.authenticate(username, password); Name system_name = new Name(username.getBytes(), null, (short)1); MAFAgentSystem maf_system = new MAFAgentSystem_AgletsImpl(runtime); MAFAgentSystem.initMAFAgentSystem(maf_system, "atp"); Tahiti.installFactories(); // // Creates a named context. To dispatch to this context, you have to // specify a destination such as "atp://aglets.trl.ibm.com:434/test" // AgletContext cxt = runtime.createAgletContext("test"); ContextListener listener = new ContextAdapter () { public void agletArrived(ContextEvent ev) { AgletProxy proxy = ev.getAgletProxy(); try { System.out.println("Aglet is arriving."+ proxy.getAgletInfo()); } catch (InvalidAgletException ex) { ex.printStackTrace(); } } public void agletDispatched(ContextEvent ev) { AgletProxy proxy = ev.getAgletProxy(); try { System.out.println("Aglet is leaving."+ proxy.getAgletInfo()); } catch (InvalidAgletException ex) { ex.printStackTrace(); } } } cxt.addContextListener(listener); Tahiti.installSecurity(); // // Start a context // cxt.start(); MAFAgentSystem.startMAFAgentSystem(maf_system, protocol); // AgletProxy myAglet = cxt.createAglet(null, "MyAglet", null); myAglet.sendMessage(new Message("startTrip")); } }
Property Name | Description | Example |
---|---|---|
aglets.class.path | Specifies the default lookup path for a codebase. This is used when a null value is given as a codebase in the AgletContext.createAglet(..) primitive. | /usr/local/AWB/Aglets/public:/myhome/public C:\AWB\Aglets\public:D:\myaglets\public |
aglets.viewer | Specifies the class name that is used for the server.
The class must implement a ContextListener interface. |
com.ibm.aglets.tahiti.Tahiti |
Property Name | Description | Example |
---|---|---|
atp.useHttpProxy | Switch on and off the http proxy. | true/false |
atp.http.proxyHost | Host | firewall.ibm.com |
atp.http.proxyPort | Port number | 8080 |
atp.noProxy | The proxy is not used for the hosts whose addresses start with the string specified here. | ibm.com |
Property Name | Description | Example |
---|---|---|
tahiti.browser_command | openurl | Specifies the command to launch the browser |