The ABLE

Agent Platform

 

ABLE 2.0

June 30, 2003

com.ibm.able.platform


Table of Contents

The following topics are discussed in this document:

·        Introduction, page 3

·        Platform Services, page 4

·        Platform Agents, page 5

·        Platform Security, page 5

·        Creating a Platform Agent, page 6

·        Configuring the Platform, page 7

·        Configuration Basics, page 8

·        Platform Name, page 8

·        Cryptography Parameters, page 8

·        Principals, page 8

·        Trust Levels, page 9

·        Agent Pools, page 9

·        Services, page 10

·        Permitted Agents, page 11

·        Setting Java Permissions, page 13

·        Starting the ABLE Platform, page 13

·        Running Platform Agents, page 14


Introduction

The com.ibm.able.platform package makes it easy to set up an environment wherein Agents and Agent Services can be created and distributed across many disparate computer systems.  Of course, the systems involved must be able to run Java and be connected to a network.  This loose collection of computer systems, with each system running one or more Java Virtual Machines (JVMs) each at a different port, and each JVM containing zero or more agent services and zero or more agents, comprise an agent platform.  Figure 1 shows two overlapping Able platforms, Platform A and Platform B:

Figure 1.

In the figure, Platform A consists of Systems A, B, C, D, E, and F.  Platform B consists of Systems A, B, C, G, H, and J.  Systems A, B, and C belong to both platforms.  As shown, each system can have one or more JVMs and each JVM is addressable by its system name and unique port number through a Java RMI Registry.  Not shown in the figure are agents and agent services running within the JVMs.

Within a platform, agents can freely communicate with other agents in two ways: by direct remote method invocation, and by sending and receiving JAS Transport Messages. Agents can also interact with agent services, and this is always done by direct method invocation.


Platform Services

The platform package does not manage agents itself, per se, but it does provide services that agents can make use of.  Services provided with the platform package include:

·        Verifiable Agent Naming Service

The verifiable agent naming service acts as a certificate authority, handing out a globally unique, verifiable Agent Name to each agent. Agents can use the naming service to verify that agent names sent to them have not been tampered with.

·        Verifiable Agent Directory Service

An agent can use the directory service to obtain an empty Agent Description, fill out the description with pertinent information about itself, and then register the description with the directory service. All agents can search the directory to find other agents with which they might want to communicate. An agent can update its own description at any time.

·        Agent Lifecycle Service

The agent lifecycle service can be used to create and initialize instances of agents anywhere within the platform.  Agents can be suspended, resumed, and terminated, but only by other agents with the proper authority. Agent Lifecycle can create only those agents that have been predefined to the platform, so that rogue agents cannot be easily introduced into the platform.

·        Platform Persistence Service

The persistence service allows other services and agents to persist information to an external database so that vital information can be recovered in the event of a crash and restart.

All of the above services implement an Able Platform Service Event Generator interface, so that other Java entities can register and deregister as service event listeners with the services.  Listeners must implement the AblePlatformServiceEventListener interface.

·        Message Transport System

The message transport system lets agents send Java Agent Services (JAS) Transport Messages to, and receive Transport Messages from, other agents.

Platform Agents

In addition to all the above services, the platform package provides a base class, an AblePlatformDefaultAgent, from which other platform agents can be derived. The AblePlatformDefaultAgent contains no special logic to perform any unique task (that must be added in derived agents), but it does make it easy to perform the following functions:

·        obtain access to all the platform services

·        obtain a unique, verifiable agent name from the agent naming service

·        register a description with the Agent Directory Service so that other agents can find it

All this can happen automatically when the ABLE platform agent is initialized.

The AblePlatformDefaultAgent also provides methods to make it easy to send JAS Transport Messages to and receive JAS Transport Messages from other platform agents. At least one JAS Locator is placed into each agent’s description, and this Locator can be used by other agents to route messages to the Locator’s owning agent. An AblePlatformDefaultAgent, if it configures itself so, can have many Locators in its agent description, allowing it to receive Transport Messages at many different access points.

Note that an AblePlatformDefaultAgent can also place another type of Locator into its agent description. This special, Able-defined Locator contains an RMI reference to the agent so that other agents can directly invoke remote methods on the agent.

Platform Security

The Able platform can operate with security on or off; off is the default. When security is off, the platform behaves as in the JAS reference implementation, and any agent can do anything to any other agent or service, including spoofing other agents’ names and changing other agents registered agent descriptions.

When security is on, the Verifiable Naming Service takes on the role of a certificate authority, and Able uses Verifiable Agent Names (or VANs), AbleSecureKeys, defined Kerberos principals, defined trust levels, and other mechanisms to authenticate and authorize one agent to another.

To control the security setting for the platform, use a text editor to edit the file able.preferences and change the Security= entry.

Note that when security is on, Able requires access to the Java Generic Security Services (JGSS) API, which must be installed in the Java environment. JGSS in turn requires Kerberos. JGSS is not shipped with Able.

To make it easy to add security checking to Java entities that want to interact with the Able platform, Able has created an AbleSecuritySupport object that can be plugged into any Java object. AbleSecuritySupport knows how to create and decode AbleSecureKeys used on secure method calls, and use the naming service to verify agent names. When security is on, an AblePlatformDefaultAgent will automatically acquire an AbleSecuritySupport object for itself.

Because AblePlatformDefaultAgent is derived from AbleDefaultAgent, many of the old methods can be used just as before. However, many of the methods will no longer work in a secure environment. Instead, new methods, that take an AbleSecureKey, must be used in their place.

Creating a Platform Agent

Creating a platform agent is simple; as already mentioned, just derive the new agent from AblePlatformDefaultAgent.  Everything the agent needs to operate in the platform is already provided in this base class.  For example, when the agent is constructed, it will automatically obtain addressability to the available agent services and then obtain a globally unique verifiable Agent Name from the Agent Naming Service.  When the agent is initialized, it will create an Agent Description for itself and then, by default, register the description with the Agent Directory Service, but the agent can be customized so that it doesn’t automatically register with this service.

In the usual Able paradigm, an agent is first constructed, then it is customized by calling the agent’s methods, and then the agent is initialized so that it begins running. In the platform world, agents are typically self-customizing (from within their constructor methods) and may also be self-initializing (also from within the constructor methods). This is because it is not wise to let “outside,” untrusted code configure an agent. Note that the Agent Lifecycle Service, when used to create an instance of an agent, can pass predefined customization parameters to an agent’s constructor method, and that it can also pass predefined initialization parameters to an agent’s initialization method.

Of course, it is possible to define new secure methods for an agent that may only be used by other Java objects with the proper authority. Because all ABLE platform agents are also RMI remote objects, you can add secure remote methods to your agent by first defining an interface that your agent can implement; then implement those methods in your agent; and finally compile the agent with rmic. To be secure, these methods should take an AbleSecureKey object as a parameter.

You can customize your agent so that it will register with an RMI registry, but by default this will not happen since the default agent is setup to register with the Agent Directory Service instead.

By default, an AblePlatformDefaultAgent receives JAS Transport Messages at its receiveMessage(TransportMessage) method. This method, in turn, simply calls the old standby process(Object) method, which does nothing but simply log the message.  Either of these methods, as desired, can be overridden to do any special processing that is required.

Review the API documentation of AblePlatformDefaultAgent for further details on any of these and other agent behaviors.

Configuring the Platform

Before you can start any piece of the platform, or run any agents in the platform, the platform must be configured. Configuring the platform means deciding

·        What computer systems you want to use in the agent platform

·        How many Java Virtual Machines (hereafter called Agent Pools) are available on each system

·        Which agent services run in which Agent Pools

·        Which agents are allowed on the platform

·        Which agents are permitted to run in which Agent Pools

Furthermore, if security is on, you will need to define

·        Which Kerberos principals are allowed in the platform

·        Trust levels for each principal and Agent Pool

To begin, you need to find the ableplatform.preferences file in your ABLE installation’s examples directory and copy it to the directory specified by your ABLE_HOME environment variable, or, if that variable is not set, to your ABLE installation’s bin directory. Then, using any text editor, edit the file using the comments in the file and the instructions here to guide you.

Configuration Basics

In the preference file, lines that begin with the ‘#’ character are comments.  You may freely add comments to the file.  Except for blank lines, all other lines are configuration parameters.

Some parameters are specified in numbered groups.  When adding, changing, or deleting groups, you must make sure that the numbers begin with 1 and are consecutiveABLE configuration code will not “see” any groups after a gap in the numbering sequence.  For example, if you are configuring AgentPools and you have AgentPool.1, AgentPool.2, AgentPool.3, and AgentPool.5, ABLE will not know about AgentPool.5 since AgentPool.4 isn’t specified.

Some of the configuration parameters might be for a future enhancement, and even though they are documented here, they may have no effect on the operation of the platform.

The following tables describe the configuration parameters.

Platform Name

PlatformName

Any string that is meaningful to you; Currently not used by ABLE code.

Cryptography Parameters

CryptographyAlgorithm

The name of a cryptography algorithm available in your Java environment. The default is DSA.

CryptographyProvider

The provider of the cryptography algorithm specified above.

 

Principals

In a secure platform, principals are used to tie agents, services, and requests to actual computer users.  Principals are made up of two parts: an Alias and an actual KERBEROS principal.  You may define as many Principals as you wish.  Just remember to number each pair of statements consecutively.

Principal.N.Alias

Any string that you will use to tie this principal to other configuration entries.  For example,

Principal.1.Alias=johnDoe

Principal.N.Principal

An actual KERBEROS principal.  For example,

Principal.1.Principal=jd/binford.com@KERBDOMAIN

Trust Levels

In a secure platform, trust levels can be established for each specified principal.  Trust levels are completely arbitrary and you can define as many as you wish, giving them whatever names you wish.  TrustLevel.1 is the most trusted; TrustLevel.2 is less trusted than TrustLevel.1, and so on.  When security is on, trust levels are examined in order to determine whether requests from one agent or service to another are to be honored.  This can help prevent less trustworthy agents from doing bad things to agents running in your critical Agent Pools.

TrustLevel.N.Alias

Any string that you will use to tie this trust level definition to other configuration entries.  For example,

TrustLevel.1.Alias=MostTrusted

TrustLevel.N.Principals

A comma delimited list of principal aliases that you defined with Principal.N.Alias entries.  For example,

TrustLevel.1.Principals=johnDoe,maryDoe

Agent Pools

You must define the Agent Pools (Java Virtual Machines, or JVMs) that will contain your agents and agent services.  The Agent Pools can be scattered across a network, and each Agent Pool, through association to a Principal, can be running with a different authorization and security level.  Later on, you can allocate agents and services to these Agent Pools, perhaps placing less trustworthy agents in Agent Pools that are restricted in some way, while trusted agents are placed into Agent Pools that are completely unrestricted.  (You may want to become familiar with the java.policy file and how it can be used to restrict Java code.)

AgentPool.N.Alias

Any string that you will use to tie this Agent Pool definition to other configuration entries.  For example,

AgentPool.1.Alias=host1

AgentPool.N.IpAddress

The IP address of the system where this Agent Pool is running.  The address may be given as a name (e.g. tools.binford.com), a numeric address (e.g. 192.169.1.100), or localhost.  Example:

VM.1.IpAddress=tools.binford.com

AgentPool.N.Port

The port on which this Agent Pool will be listening.  See also Setting Java Permissions.  Example:

AgentPool.1.Port=55551

AgentPool.N.Principal

The alias of a principal that you defined as described in the section Principals.  In a secure platform, the specified principal must be the one who starts the Agent Pool.  Example:

AgentPool.1.Principal=johnDoe

Services

The ABLE platform requires that certain agent services always be available whenever the platform is available.  The Services statement identifies all services that you want to run when your platform is started.  You can add your own services to the list, and then, for each service you add, enter another statement that specifies where (in which Agent Pool) the service is to run, and what Java class knows how to create the service (this is known as the factory).  If you write your own service, you should derive it from AbleBasicService.

Services

Services and the following statements specify the services that are available to the platform and where the services reside.  You do not need to change anything on the Services statement.

However, if you are creative, you can replace any service with one of your own (specify the fully qualified class name of the service’s factory) or add additional services to the platform.

The following services are required:

Agent-Directory-Service
Agent-Naming-Service
Message-Transport-System
Agent-Life-Cycle-Service

The following service is optional:

Persistence-Service
 

Agent-Directory-Service

Agent-Naming-Service

Message-Transport-System

Agent-Life-Cycle-Service

You do not need to change anything on these statements except to specify in which Agent Pool each service runs.  You can place all services in one Agent Pool or place each service in its own Agent Pool.

You can specify whether persistence is on or off, but if on, the Persistence-Service must be running.

You can replace an IBM supplied service with one of your own, but this is not recommended. If security is on and you replace a service, your platform will not operate correctly.

Permitted Agents

Agent Lifecycle Service will create only those agents that have been identified to it.  You identify agents to the Agent Lifecycle Service by entering information about them into your preferences file.  For each agent, you specify where (in which Agent Pools) the agent is allowed to run, and who is allowed to create and run it.

Agent.N.Alias

Any string that you will use to tie this agent definition to other configuration entries.  For example,

Agent.1.Alias=spiffyAgent

Agent.N.AutonomyLevel

Any string that is meaningful to you; not used by ABLE code; for documentation only.

Agent.N.ClassName

The fully qualified Java class name of the agent.  For example, if you work for the Binford Tool Company and you have an agent implemented by SpiffyAgent.java, the class name might be:

com.binford.tools.SpiffyAgent

This class must be in the Java class path whenever the Agent Lifecycle Service is asked to create an agent of this type.

Agent.N.ConstructorArgs

Either empty or a comma delimited list of arguments to pass to the agent’s constructor method when the agent is created by the Agent Lifecycle Service.

Each argument is of the form

type:defaultValue 

where type is one of String, Boolean, Byte, Character, Double, Float, Integer, Long, or Short and defaultValue is a value of the specified type. For example, if your agent’s constructor takes a boolean and a double as arguments, you might code:

Agent.1.ConstructorArgs=Boolean:false,Double:12.34

Agent.N.EligiblePrincipals

A comma delimited list of Principal aliases that you have defined (see Principals) and specifies those principals that are allowed to run the agent.  The agent will inherit the trust level (see Trust Levels) associated with whichever principal starts the agent.

Agent.N.EligibleAgentPools

A comma delimited list of Agent Pool aliases that you defined (see Agent Pools) and specifies which of those Agent Pools the agent is allowed to run in. For example, if you defined Pool-1, Pool-2, and Pool-3 as Agent Pool aliases, then you might code

Agent.1.EligibleAgentPools=Pool-1,Pool-3

which indicates that the Agent Lifecycle Service is allowed to create instances of this agent in Agent Pools Pool-1 and Pool-3, but not in Pool-2.

Agent.N.InitArgs

Either empty or a comma delimited list of arguments to pass to the agent’s initialization method when the agent is initialized by the Agent Lifecycle Service. The format of this statement is exactly the same as for the ConstructorArgs statement above.

Agent.N.LastChangedDate

Any string that is meaningful to you; not used by ABLE code; for documentation only.

Agent.N.Type

Any string that is meaningful to you; not used by ABLE code; for documentation only.

Agent.N.Vendor

Any string that is meaningful to you; not used by ABLE code; for documentation only.

Agent.N.Version

Any string that is meaningful to you; not used by ABLE code; for documentation only.

Setting Java Permissions

Before you can start up the platform you may need to modify your java.policy file to allow communications on the ports you configured in your ableplatform.preferences file. To do this, find the java.policy file that is supplied with your Java installation and, using any text editor, add lines similar to the following to it.

permission java.net.SocketPermission "tools.binford.com:55551-55553", "connect,accept,listen,resolve";
permission java.net.SocketPermission "9.10.84.20:55551-55553", "connect,accept,listen,resolve";
permission java.net.SocketPermission "localhost:55551-55553", "listen,resolve";

If you need to, add a statement for every system that you have specified in your ableplatform.preferences file, and the port numbers you enter here must also match the ports you specified there.

You can use the java.policy file together with the java.security file to restrict the capabilities of each Java VM (Agent Pool) you have defined in your preference file.

Starting the ABLE Platform

Before starting the platform, check that, for every system identified in your ableplatform.preferences file, you have

·        Installed ABLE

·        Made all necessary JARs and classes available to the Java runtime environment

·        Placed a copy of your updated ableplatform.preferences on the system, either in the place specified by the ABLE_HOME environment variable, or in ABLE’s bin directory.

·        Modified your java.policy file, if needed

·        If security is on, you will need a valid Kerberos ticket for the principal you log in as

Now you are able to start the platform. Again, for every system that will participate in the platform, you will need to open a command prompt, change to ABLE’s bin directory and run the startPlatform command, supplying as a parameter the port number used on that particular system.  If a system will run more than one JVM, each with a separate port number, you will need to open additional command prompts and run the startPlatform command specifying the separate port number each time. For example, in two separate command windows you will run one of the example commands below:

startPlatform 55551
startPlatform 55552

NOTE that before you start any other JVM you must first start the JVM that will contain the Agent Naming Service.

Once all bits of the platform are up and running, you can start your agents running.

Running Platform Agents

Future releases of ABLE may contain a graphical “console” that will connect to the Agent Lifecycle Service, obtain a list of configured agents, and then allow to you to create instances of those agents in their permitted Agent Pools. For now, you will have to start and control your agents manually, or write your own agent console. It is a simple matter to create an agent (derived from AblePlatformDefaultAgent) that connects to the Agent Lifecycle Service and starts agents through it. The agent can create, initialize, suspend, resume, and quit agents through the Agent Lifecycle Service. It is also simple to connect to the Agent Directory Service to find out which agents are registered and running.

The following example code, an agent derived from AblePlatformDefaultAgent, shows how to interact with the platform’s agent services.

 
//====================================================================
// Imports
//====================================================================
import java.rmi.RemoteException;
import java.util.Vector;
 
import javax.agent.AgentName;
import javax.agent.JasConstants;
import javax.agent.Locator;
import javax.agent.service.directory.AgentDescription;
 
import com.ibm.able.*;
import com.ibm.able.platform.*;
 
 
/**
 * ExampleAgent is a self-initializing agent that interacts with Agent
 * Lifecycle Service to create, in various agent pools, instances of
 * agents defined in the <code>ableplatform.preferences</code> file.
 *
 */
public class ExampleAgent extends AblePlatformDefaultAgent 
                             implements AblePlatformServiceEventListener {
 

 

 
  /**
   * Creates, configures, and initializes an ExampleAgent agent that
   * shows how to connect to the Agent Lifecycle Service and create
   * and initialize an instance of another agent in some other agent
   * pool.
   */
  public ExampleAgent() throws RemoteException {
 
    // ---------------------------------------------------------------
    // IF SECURITY IS ON, and because this is an agent that runs from
    // the command line outside of a platform Agent Pool, this agent
    // must: 
    //     o  Specify which of the agent pools to associate with
    //     o  Set the principal that is running this agent/agent pool
    // ---------------------------------------------------------------
    if ( Able.Preferences.isSecure() ) {
      AblePlatform.Preferences.setLocalAgentPool("AgentPool1");
      myPrincipal = "al/borland.binford.com@KERB5REALM.BINFORD.COM";
    }
 
    // Configure this instance. Only a few agent attributes are shown,
    // but many more can be changed to control the agent's behavior. 
    setName             ("ExampleAgent");
    setComment          ("Example agent program.");
    setAgentSummary     (new AbleMessageContainer("Just getting initialized"));
    setAgentType        ("Binford_Agent");
    addJasAgentAttribute("some-arbitrary-user-attribute",    "attribute 1 value");
    addJasAgentAttribute("another-arbitrary-user-attribute", "attribute two value");
 
    // Initialize this instance. Note that when this method completes,
    // the agent will have obtained a unique AgentName from the Agent
    // Naming Service and will have registered an AgentDescription
    // with the Agent Directory Service. The agent is ready to receive
    // JAS Transport Messages, ABLE events, and do whatever else it is
    // configured to do.
    _init();
  }
 
  // This agent registers as a platform Service Event Listener with
  // several agent services. When those services send out Service
  // Events, this method is where they will appear. This method is
  // from the AblePlatformServiceEventListener interface.
  public void handleAblePlatformServiceEvent(AblePlatformServiceEvent theServiceEvent) throws RemoteException {
    System.out.println("\n ExampleAgent received a platform service event: <" + theServiceEvent.getEventDescription() + ">. \n\n");
  }
 
  /**
   * If security is on, this local helper method generates an
   * AbleSecureKey to be used on a subsequent secure method
   * call. Otherwise, null is returned. "mySecSppt" is an
   * AbleSecuritySupport object built-in to the
   * AblePlatformDefaultAgent, but the object is null unless security
   * is on.
   */
  private AbleSecureKey secureKeyOrNull() throws Exception {
    return ((Able.Preferences.isSecure())? mySecSppt.generateKey() : null);
  }
 
 
 
  //==================================================================
  //
  // Mainline logic
  //
  // This method uses Agent Lifecycle Service to create an instance of
  // an agent defined in the ableplatform.preferences file. The agent
  // is created in its first eligible agent pool, and with overridden
  // constructor parameters.
  //
  // The Agent Directory Service is used to locate the agent, and the
  // Message Transport System is used to send the agent a message.
  //
  // Remote methods are invoked on the agent, and the Agent Lifecycle
  // Service is used to terminate the agent.
  //
  //==================================================================
  private void runExampleAgent() throws Exception {
 
    AgentLifeCycleService       agentLifecycleService      = null;
    AbleAgentClassDescription[] agentsDefinedInPreferences = null;
 
    // AblePlatformDefaultAgent has built-in access to the Agent
    // Naming Service, the Agent Directory Service, and the Message
    // Transport System, but not the Agent Lifecycle Service, so
    // acquire the Agent Lifecycle Service from the JAS Service Root.
    agentLifecycleService = (AgentLifeCycleService)myJasServiceRoot.getService (com.ibm.able.platform.AgentLifeCycleService.SERVICE_TYPE);
    if (agentLifecycleService==null) return; // Simply return on any error
 
    // Register as a service event listener with some of the agent services.
              agentLifecycleService.addAblePlatformServiceEventListener(this);
    myJasVerifiableDirectoryService.addAblePlatformServiceEventListener(this);
       myJasVerifiableNamingService.addAblePlatformServiceEventListener(this);
 
    // Ask Agent Lifecycle Service for a list of agents that have been
    // defined in the ableplatform.preferences file. These are the
    // agents that Lifecycle is permitted to create.
    agentsDefinedInPreferences = agentLifecycleService.getPermittedAgents();
    if (agentsDefinedInPreferences==null) return;
 
    // In all the agents defined in preferences, look for an agent
    // defined with the alias of "TimAgent".  This is the agent to
    // create later.
    String agentAlias = "TimAgent";
    AbleAgentClassDescription foundAgentClassDescription = null;
    for (int i=0; i<agentsDefinedInPreferences.length; i++) {
      AbleAgentClassDescription testAgentClassDescription = agentsDefinedInPreferences[i];
      String testAgentAlias = testAgentClassDescription.getAgentAlias();
      if ( agentAlias.equals(testAgentAlias) ) {
        foundAgentClassDescription = testAgentClassDescription;
        break;
      }
    }
    if (foundAgentClassDescription==null) return;
 
 
    // A preference description of the "TimAgent" was found. Save
    // information about the agent; for example, find what Agent Pools
    // the agent is allowed to run in, and select the first Agent Pool.
    Vector                                  eligibleAgentPools = foundAgentClassDescription.getEligibleAgentPools();
    AblePlatformPreferences.AgentPoolEntry_ agentPoolInfo      = (AblePlatformPreferences.AgentPoolEntry_)eligibleAgentPools.elementAt(0);
    String                                  agentPoolAlias     = agentPoolInfo.getAgentPoolAlias();
 
    // The constructor of the (fictitious) TimAgent takes 3
    // parameters: a String (which will become the agent's display
    // name), a Long, and a Double. The ableplatform.preferences file
    // supplies default values for these parameters, but they are
    // overridden here with new values.
    String agentDisplayName = "DisplayNameOfTimAgent";
    Vector newCtorArgs = new Vector();
    newCtorArgs.addElement(new String (agentDisplayName));
    newCtorArgs.addElement(new Long   (3000L));
    newCtorArgs.addElement(new Integer(Able.ProcessingEnabled_PostingEnabled));
    foundAgentClassDescription.setConstructorArgs(newCtorArgs);
 
    // Have the Agent Lifecycle Service create and intialize an
    // instance of the TimAgent.
    AblePlatformAgent timAgent = agentLifecycleService.createAgentInstanceAndInitialize(foundAgentClassDescription, agentPoolAlias, secureKeyOrNull());
 
    // Use the Agent Directory Service to look up the agent just
    // created. ("lookupAgent" is a built-in method in
    // AblePlatformDefaultAgent that uses the Agent Directory
    // Service.) Save the newly created agent's unique AgentName and
    // also all its JAS Locators.
    AgentDescription[] timAgent_AgentDescription = lookUpAgent(JasConstants.AGENT_DISPLAY_NAME, agentDisplayName);
    AgentName          timAgent_AgentName        = timAgent_AgentDescription[0].getAgentName();
    Locator[]          timAgent_LocatorList      = timAgent_AgentDescription[0].getLocators();
 
    // Get the RMI Locator for the agent.  This Locator, and the
    // AgentName saved above, will be used to send the agent a JAS
    // Transport Message.
    Locator timAgent_RmiLocator = null;
    for (int i=0; i<timAgent_LocatorList.length; i++) {
      if ( timAgent_LocatorList[i].getType().equalsIgnoreCase("rmi") ) {
        timAgent_RmiLocator = timAgent_LocatorList[i];
        break;
      }
    }
 
    // Send the agent a JAS Transport Message. Again, the method is a
    // built-in of AblePlatformDefaultAgent. Note that instead of a
    // String, any Java object can be sent as a "message".
    sendTransportMessage(timAgent_RmiLocator, timAgent_AgentName, "Hello, Tim Agent");
 
    // Becaue AblePlatformDefaultAgents are remote objects, their
    // methods can be accessed directly. Here, events are sent to the
    // agent, and the agent is suspended and resumed.
    timAgent.handleAbleEvent( new AbleEvent(this, null) ); // Send the agent an event
    timAgent.suspendAgent( secureKeyOrNull() );            // Suspend the agent
    for (int i=0; i<20; i++) {timAgent.handleAbleEvent( new AbleEvent(this, null) );}// Queue up many events for the agent
    timAgent.resumeAgent( secureKeyOrNull() );             // Resume the agent
 
    // Use the Agent Directory Service to find the agent again, and
    // once found, use the Agent Lifecycle Service to terminate the
    // agent. Note that the agent's "quit" method can be called
    // directly, just like the suspend and resume methods above, but
    // for demonstration purposes Lifecycle is used.
    AgentDescription foundAgentDescription[] = lookUpAgent(JasConstants.AGENT_DISPLAY_NAME, agentDisplayName);
    agentLifecycleService.quitAgent(foundAgentDescription[0], secureKeyOrNull());
 
    // Deregister as a service event listener with the same services
              agentLifecycleService.removeAblePlatformServiceEventListener(this);
    myJasVerifiableDirectoryService.removeAblePlatformServiceEventListener(this);
       myJasVerifiableNamingService.removeAblePlatformServiceEventListener(this);
  }
 

 

 
  //==================================================================
  //
  // MAIN Method
  //
  //==================================================================
  /**
   * Creates an instance of an ExampleAgent agent, and calls its
   * mainline routine, runExampleAgent().
   *
   * @param args
   *        are all ignored....
   */
  public static void main(String args[]) {
 
    // Start ABLE's message and trace logging
    Able.startMessageAndTraceLogging();
 
    // Kickoff an instance of this agent. ExampleAgents are
    // self-customizing and self-initializing, which means that when
    // the constructor is finished, the agent instance will have a
    // unique AgentName and will have registered its AgentDescription
    // with the Agent Directory Service.
    try {
      ExampleAgent anExampleAgent = new ExampleAgent();
      anExampleAgent.runExampleAgent();
    }
    catch(Exception theException) {
      theException.printStackTrace();
      System.out.println("\n++ExampleAgent.main(): Caught exception: " + theException);
    }
 
    System.exit(0);
  }
 
}