Mobile agents are ubiquitous in today's software applications—from e-commerce to network management to data warehousing. Mobile agent developers implement these solutions in Java for several reasons: First and foremost, Java's built-in object-oriented language features are conducive to agent technology. Second, developers can be extremely productive using Java. Essentially, Java provides tools that simplify and expedite complex software development tasks.

Jini is one such Java tool that allows us to build distributed applications with relative (and I emphasize relative) ease. This article introduces a mobile agent framework based on the Jini architecture. Jini provides a powerful tool in a Java developer's toolbox. Just as robots automate many aspects of manufacturing a computer, Jini automates and abstracts distributed applications' underlying details. These details include the low-level functionality (socket communication, synchronization, and so on) necessary to implement the high-level abstractions (such as service registration, discovery, and use) that Jini provides. Let's begin by looking briefly at agents.

Note: In this article, I do not introduce the Jini framework (see Resources for links to introductory material); I will just review the basics.

Agents

Agent is an overloaded industry buzzword—10 different gurus would give you 10 different definitions of the term. I prefer to talk about agents in terms of high-level functionality. I like the way Stan Franklin and Art Graesser view agents as discussed in their research paper "Is It an Agent, or Just a Program? A Taxonomy for Autonomous Agents" (1996). (Todd Sundsted references this paper in his article "An Introduction to Agents" (JavaWorld, June 1998), an article that makes good on its title's promise.) Instead of providing a rigid (and hence potentially restrictive) definition, Franklin and Graesser identify the following agent properties that enable a classification methodology:

  • Reactive
  • Autonomous
  • Goal-oriented
  • Temporally continuous
  • Communicative
  • Intelligent
  • Mobile

The authors note that an agent might not be a software entity at all, but possibly a robot or even a schoolteacher. In the context of this article and Java development, we can consider an agent a software entity that exhibits some combination of the previous properties.

What can we use mobile agents for? Mobile agents come in a variety of flavors and perform numerous functions:

  • An information agent searches for information residing on remote nodes and reports back to the source
  • A computation agent seeks underutilized network resources to perform CPU-intensive processing functions
  • A communication agent couriers messages back and forth between clients residing on various network nodes

Let's now look at a mobile agent framework that employs the Jini architecture.

Mobile agent framework

This mobile agent framework consists of two main components. The first component is the mobile agents themselves; that is, entities with some job to do. The second component is the mobile agent host(s), the service that provides the mobile agents' execution platform. In a distributed environment, we can have one-to-many agent hosts as well as one-to-many agents. To be an active agent platform, a given node in the system must have at least one active agent host. Figure 1 depicts the framework components.

Figure 1. Agent framework components

These two components map quite nicely to the Jini model. Jini, at the highest level, provides the infrastructure that enables clients to discover and use various services. Jini also provides a programming model for developers of Jini clients and services. In the context of this mobile agent framework, the agent host(s) provides Jini services. The mobile agent(s) is the Jini client.

Jini services register with one or more Jini lookup services by providing a service proxy for perspective clients. In turn, clients query the lookup service(s) for particular services that might be of interest. Figure 2 depicts that process.

Figure 2. Service registration and discovery. Click on thumbnail to view full-size image.

We will first look at building the agent host.

Agent host construction

The first step in building the agent host is to create a remote interface, the service template that agents will look for via the Jini lookup service. The AgentHostRemoteInterface provides one method, acceptAgent(), which agents call to travel to the implementing agent host:

public interface AgentHostRemoteInterface extends Remote {
   public void acceptAgent (AgentInterface ai) throws
      RemoteException;
}

The beauty of Jini is that objects can publish several interfaces—that is, provide multiple services. For instance, if we had a distributed data warehouse, we might have an agent host that provides a local data access service. In this instance, a data-mining agent might look for a host that provides the data access service and move to that host to perform localized mining operations. Therefore, we can have agents with different missions share hosts that provide multiple services.

The second step in building the agent host is to provide an implementation of this remote interface that is the actual Jini service. I have provided a MobileAgentHost class (see Resources to download complete code listings) that implements the AgentHostRemoteInterface.

Figure 3 shows the class diagram for MobileAgentHost.

Figure 3. Mobile agent host class diagram

The class extends the java.rmi.server package's UnicastRemoteObject class, which allows clients to obtain a remote reference and call its methods. The MobileAgentHost also implements the ServiceIDListener interface, which is passed a unique ServiceID object via the serviceIDNotify() method when the service first registers with a Jini lookup service. The MobileAgentHost constructor is shown below:

public class MobileAgentHost extends UnicastRemoteObject 
   implements AgentHostRemoteInterface, ServiceIDListener {
   private JoinManager myJoinManager = null;
   private ServiceID myServiceID = null;
   private LeaseRenewalManager myLeaseManager = null;
   private Object myAgentObject = null;
   private LookupDiscoveryManager myLDM = null;
   public MobileAgentHost(Object agentObject) throws IOException {
      myAgentObject = agentObject;
       
      // First parameter: Publish service to all groups.
      // Second parameter: Specifies a lookup locator 
      //     to perform unicast discovery at a known host/port
      //     (we don't use this, just multicast, which is automatic).
      // Third parameter: DiscoveryListener for callbacks when a lookup 
      //     service is discovered or discarded. This service
      //     registration will be handled by the JoinManager. 
      myLDM = new LookupDiscoveryManager(LookupDiscovery.ALL_GROUPS, 
         null, null);
      // First parameter: This service.
      // Second parameter: Attribute sets (none at this time).
      // Third parameter: ServiceIDListener implementor, which is notified 
      //     when a serviceID is assigned.
      // Fourth parameter: DiscoveryManagement implementor.
      // Fifth parameter: LeaseRenewalManager.
      myJoinManager = new JoinManager(this, null, this, myLDM, 
         new LeaseRenewalManager());
      }
   ...
}

This MobileAgentHost constructor takes the agentObject object reference stored as member data and passed to arriving agents via the doWork() method. The constructor itself performs two basic functions. First, it creates a LookupDiscoveryManager to locate a Jini lookup service(s). Second, it creates a JoinManager, with the LookupDiscoveryManager as a parameter, to add this lookup service to the Jini service federation. As of Jini 1.1, the JoinManager simplifies service management by encapsulating the functionality required for both registering with and withdrawing from a dynamic lookup service pool.

In the acceptAgent() method's implementation shown below, the MobileAgentHost binds an incoming agent to an AgentThread:

public void acceptAgent(AgentInterface ai) throws RemoteException {
   AgentThread at = new AgentThread(ai);
   at.start();
}

In turn, an inner class instance, AgentThread, is created to run the bounded agent by calling its doWork() method, passing the LookupDiscoveryManager and agent object references (I'll discuss the doWork() method more in the following section):

private class AgentThread extends Thread {
   private AgentInterface myAgent = null;
   AgentThread(AgentInterface ai) {
      myAgent = ai;
   }
   public void run() {
      myAgent.doWork(myLDM, myAgentObject);
   }
}

In this implementation, a new thread is created for each arriving agent. You could enhance the implementation by binding incoming agents to an AgentThread from a pre-existing thread pool.

Agent construction

Now let's look at the mobile agents. The first step in building an agent is to create an interface for agents. For this, we create an AgentInterface that extends the Serializable interface. The Serializable interface marks the implementer as a serializable entity, or one that can be sent across the wire:

public interface AgentInterface extends Serializable {
   public void doWork(LookupDiscoveryManager ldm, Object workObject);
}

The AgentInterface consists of a doWork() method that is called when an agent arrives on a given host. This method takes two parameters. The first parameter is a reference to the LookupDiscoveryManager maintained by the current host. The agent uses this reference if and when it decides to look for new service providers, such as when it wants to travel to a new agent host. The second parameter is an optional (possibly null) object parameter, which contains data necessary for the agent to complete its job. For example, an agent that must communicate with other agents currently residing on this host might be passed a collection of agent references. The aforementioned data-mining agent might be passed a reference to a local database.

The second step in agent construction is providing an AgentInterface implementation -- for which I created an abstract MobileAgent class. This class's constructor builds a service template that locates services of type MobileAgentHostInterface. It also provides three additional methods: doWork(), moveToRandomHost(), and getMobileAgentHosts(), discussed below:

  1. To perform an agent-specific task, subclasses override the abstract doWork() method.
  2. When the agent wants to move, subclasses call the moveToRandomHost() method, which performs the following three steps:

    1. Gets a list of the currently available mobile agent hosts with a call to getMobileAgentHosts().
    2. Randomly selects a host from this list.
    3. Moves to a new host by calling the acceptAgent() method. If the call on the selected host fails, select a new host.
  3. To obtain a list of currently available agent hosts, subclasses call the getMobileAgentHosts(). This process requires the following steps:

    1. Call getRegistrars() to obtain a current list of lookup services.
    2. Iterate through each lookup service to find services that match the desired template; in this case, AgentHostRemoteInterfaces. Note that we might have duplicates—the same agent host registered with multiple lookup services. The myMAHServiceTemplate object, a ServiceTemplate class instance, passed to the lookup() method initializes in the MobileAgent constructor.
    3. Add each matching service to a vector of AgentHostRemoteInterfaces.

The following code illustrates these steps:

private Vector getMobileAgentHosts(LookupDiscoveryManager ldm) {
   // Find available lookup services
   ServiceRegistrar[] lookupServices = ldm.getRegistrars();
   // Find matching services (in our case, 
   // AgentHostRemoteInterfaces). 
   // Register with each lookup service.  
   Vector mobileAgentHosts = new Vector();
   ServiceMatches agentHosts = null;
   for (int i = 0; i < lookupServices.length; i++) {
      try {
         agentHosts =
            lookupServices[i].lookup(myMAHServiceTemplate, 
Integer.MAX_VALUE);
      }
      catch (RemoteException re) {
         System.out.println(re);
         continue;
      }
      for (int j = 0; j < agentHosts.totalMatches; j++) {
         mobileAgentHosts.addElement(agentHosts.items[j].service);
      }
   }                                                                       
               
   return mobileAgentHosts;
}

Figure 4 depicts the interactions between the MobileAgent, MobileAgentHost, and the Jini lookup service.

Figure 4. Sequence diagram for interactions between the MobileAgentHost, MobileAgent, and Jini lookup service

The final step in building the agent: create a concrete mobile agent implementation. I chose to implement a RouteMapAgent that extends MobileAgent. This agent randomly travels to various agent hosts and logs its route. In this implementation, the object passed to the doWork() method by the AgentHost is the local hostname. The agent records this name in its route map. Figure 5 depicts the RouteMapAgent class diagram.

Figure 5. RouteMapAgent class diagram. Click on thumbnail to view full-size image.

RouteMapAgent's doWork() method, called when an agent arrives at a host, is implemented as follows:

  1. Display the route taken to get to this host
  2. Record the current host in the route table
  3. Search for and travel to a new agent host (call moveToRandomHost())

The code below demonstrates these steps:

Page 2 of 2
public void doWork(LookupDiscoveryManager ldm, Object hostName) {
   // Notify of arrival
   System.out.println(this + " just arrived!");
   // Display route (last 10 hosts)
   if (myRoute.size() > 0) {
      System.out.println("Here is my list of recently visited hosts:");
      Iterator iter = myRoute.iterator();
      int i = 1;
      while (iter.hasNext()) {
         System.out.println("Host " + i + " : " + iter.next());
         i++;
      }
      System.out.println();
   }
   // Maintain a list of the last 10 hosts visited
   if (hostName != null) {
      myRoute.addFirst(hostName);
      if (myRoute.size() > 10) {
         myRoute.removeLast();
      }
   }
   // Rest for a while
   int sleepTime = (int)Math.floor(Math.random() * 5000);
   try {
      Thread.sleep(sleepTime);
   }
   catch (InterruptedException ie) {
      System.out.println(ie);
   }                                     
   // Move on to the next host
   moveToRandomHost(ldm);
}

The RouteMapAgent performs a fairly simple task. Alternate extensions of the MobileAgent class, as discussed previously, could provide more complex functionality.

The RouteMapAgent possesses several of the agent properties that Franklin and Graesser identify in their paper. First, it is autonomous: It is completely independent, coming and going as it pleases. It controls its own actions by choosing where and when to move. Second, although the objective is simple, the agent is clearly goal-oriented. Its duty: travel to various hosts and record its route. Third, the agent is temporally continuous, that is, a constantly running entity. Finally, it is mobile, moving among various hosts in the network. This agent clearly lacks intelligence, but it does have the communicative property because the agent displays its route upon arrival at each host. Essentially, this agent talks, but cannot listen.

Sometimes an agent might wish to specify a destination host, as opposed to merely random selections. In this scenario, you can modify the MobileAgentHost to register its service and a corresponding globally unique name. You would then need to modify the getMobileAgentHosts() method to return a name/service template pair that each agent can select from.

Compile and run the code

Let us now take a look at running the agent application. In my experiences, the biggest headache with Jini is actually getting it up and running. I recommend browsing "Tips and Tricks for Debugging Jini Technology-Enabled Devices" (ADTmag.com, June 2000) prior to using Jini. It can save you a tremendous amount of time. Joshua Fox's "Deploy Code Servers in Jini Systems" (JavaWorld, December 2001) is a great article on deploying code servers in Jini systems. For those new to Jini, an encouraging word: After using Jini for several weeks, it will become second nature; but you must overcome an initial learning curve.

The following command lines are for Unix-based systems. Windows and other operating systems' path specifications will differ slightly. Please see Resources for additional information.

To compile and run the code:

  1. Create two directories (agent and agent_host) for the respective agent and agent host .java files (see Resources for the source code). To resolve compile-time dependencies, you must include the interfaces in both directories:

    Include interfaces in two directories
    Agent Directory Agent Host Directory
    AgentHostRemoteInterface.java AgentInterface.java
    AgentInterface.java AgentHostRemoteInterface.java
    MobileAgent.java MobileAgentHost.java
    RouteMapAgent.java StartAgentHost.java
    StartAgent.java  

    In the remaining steps, the agent_dir corresponds to the absolute path of the agent directory you created. The agent_host_dir corresponds to the agent host directory's absolute path.

    Note: When typing the following commands on a single line, do not insert a space at the line breaks below that follow a : or =, but do insert a space at all other line breaks).

  2. Compile the Java files in the agent directory, where $JINI_HOME is set to your Jini distribution's top-level directory (requires Jini 1.1 or higher):

    javac -classpath $JINI_HOME/lib/jini-core.jar:
    $JINI_HOME/lib/jini-ext.jar:$JINI_HOME/lib/sun-util.jar:
    agent_dir *.java
    
  3. Compile the Java files in the agent host directory:

    javac -classpath $JINI_HOME/lib/jini-core.jar:
    $JINI_HOME/lib/jini-ext.jar:$JINI_HOME/lib/sun-util.jar:
    agent_host_dir *.java
    
  4. Create the rmi stub for the MobileAgentHost service:

    rmic -v1.2 -classpath $JINI_HOME/lib/jini-core.jar:
    $JINI_HOME/lib/jini-ext.jar:$JINI_HOME/lib/sun-util.jar: 
    agent_host_dir -d agent_host_dir MobileAgentHost
    
  5. Start an HTTP server for clients to download Jini class files on the node where the Jini lookup service will run:

    java -jar $JINI_HOME/lib/tools.jar 
    -port 8081 -dir $JINI_HOME/lib -verbose&
    
  6. Start an HTTP server that will provide the agent class files; this server must start on every node in which an agent initializes:

    java -jar $JINI_HOME/lib/tools.jar 
    -port 8082 -dir agent_dir -verbose&
    
  7. Start an HTTP server that will provide the agent host class files; this server starts on every node in which an agent host runs:

    java -jar $JINI_HOME/lib/tools.jar 
    -port 8083 -dir agent_host_dir -verbose&
    
  8. Start the RMI (Remote Method Invocation) activation daemon:

    rmid 
    -J-Djava.security.policy=$JINI_HOME/example/books/policy.all&
    
  9. Start the Jini lookup service. Note that because Jini uses multicast for discovery, your network must therefore support it too. Also note that the policy file specified is wide open (no restrictions) which certain applications might not permit:

    java -Djava.security.policy=$JINI_HOME/example/lookup/policy.all 
    -jar $JINI_HOME/lib/reggie.jar http://piedome:8081/reggie-dl.jar 
    $JINI_HOME/example/lookup/policy.all /tmp/reggie_log public&
    

    The hostname parameter, piedome, in my case, is the name of the server machine on which the Jini HTTP server started. The final parameter, /tmp/reggie_log, is a directory (which can differ from the one I chose) for the lookup service log files.

  10. Start one or more MobleAgentHosts. Execute this command for each host:

    java -Djava.security.policy=$JINI_HOME/example/books/policy.all 
    -classpath $JINI_HOME/lib/jini-core.jar:$JINI_HOME/lib/jini-ext.jar:
    $JINI_HOME/lib/sun-util.jar:agent_host_dir -Djava.rmi.server.codebase=
    http://piedome:8083/ StartAgentHost nameOfHost
    

    The hostname parameter, piedome, in my case, is the name of the server machine on which the agent host HTTP server started; don't confuse the agent and agent host port numbers. The nameOfHost is a command-line parameter you can use to name the host whatever you like, which proves useful if you start multiple host processes on the same machine.

  11. Start one or more RouteMapAgents. Execute this command for each agent:

    java -Djava.security.policy=$JINI_HOME/example/books/policy.all 
    -classpath $JINI_HOME/lib/jini-core.jar:$JINI_HOME/lib/jini-ext.jar:
    $JINI_HOME/lib/sun-util.jar:agent_dir -Djava.rmi.server.codebase=
    http://piedome:8082/ StartAgent nameOfAgent
    

    The hostname parameter, piedome, in my case, is the name of the server machine on which the agent HTTP server started. The nameOfAgent is a command-line parameter you can use to name this particular agent whatever you like.

If you are working on a single node, start several MobileAgentHosts (one per process/JVM). If you are lucky enough to have access to a distributed network, you can start an agent host on each node. After starting several hosts, start a RouteMapAgent. You should be able to watch the agent bounce randomly among the various hosts by examining RouteMapAgent's output as it arrives at each host.

Jini reduces agent complexity

In addition to being fun to work with, mobile agents play an ever-increasing role in today's advanced software systems. With the advent of distributed computing and the power and flexibility it provides, complexity is at the forefront. You can incorporate Jini to mitigate complexity; it encapsulates the underlying functionality (sockets, messaging protocols, synchronization issues, fault tolerance, and so on) required for a successful distributed application. I like using Jini because I have less networking code to debug and hence am more productive because I can develop distributed applications faster. I can adhere to a flexible client-service model, concentrating my efforts on providing services and/or clients that use those services. Jini provides a powerful tool for distributed applications and maps quite nicely to a mobile agent architecture.

Jason Byassee is a software architect performing advanced research and development for TRW Systems in Aurora, Colo. His work involves implementing leading edge technologies using the Java platform; applications include machine-learning algorithms and knowledge management systems based on distributed, agent-based architectures.

Learn more about this topic