Designing ABLE Applications

This document describes how to use ABLE to add intelligent agent functions to applications. It illustrates several options for using AbleAgents and AbleBeans in applications ranging from using synchronous method calls to synchronous or asynchronous action events.

The ABLE framework provides considerable flexibility to the developer of custom application specific agents. The agent can use the provided AbleBean and AbleAgent components to implement its functions. These beans can be tightly integrated using method calls and run on the application's thread of control. Or the beans could be loosely connected using event passing and some or all of the beans could have their own thread of control. Data can be shared between beans by accessing bean properties held in the container agent, or data can be passed between beans using the data flow (buffer) connections.

You can use one or more forms of data connection within your AbleAgent, and use different connection mechanisms between the agent and the rest of your application. For example, you may create a custom AbleAgent and use data flow and synchronous method calls between AbleBeans inside of the agent, but you may decide to integrate it with your application by sending asynchronous action events. It all depends on your application requirements.

The major design decisions involve:

  1. threading
    You can create an AbleAgent that runs entirely on the application's thread.
    You can create an AbleAgent that runs on its own thread and handles work requests from the application through events on that thread
    Your AbleAgent can itself contain 0, 1, or N threads of control
  2. data flow between beans
    You can pass buffers of data between beans using the data flow connections
    You can share data by using the container agent properties as global data
    You can share specific data members by using property connections
    You can pass data between beans as arguments on notification or action events
  3. processing flow between beans
    You can wire up beans using data flow connections and let the default agent process() method sequence the beans
    You can invoke the beans in any desired order by hard coding the logic in your agent process() method
    You can let the bean processing be driven by events generated external to the agent or internal to the agent

AbleBeans

The techniques described in this section apply to all JavaBeans that implement the AbleBean interface. This includes all core AbleBeans provided with the ABLE framework (based on the AbleObject base class) and all core AbleAgents (based on the AbleDefaultAgent class). These functions are also supported by any custom AbleBeans that extend AbleObject or AbleDefaultAgent.

Synchronous method calls

Perhaps the simplest way to use an AbleBean is to call its process() method. The standard sequence for instantiating and using an AbleBean (as implemented by AbleObject) is shown below:

// 1.  Instantiate and configure the bean 
AbleObject bean = new AbleObject() ; // create the bean
bean.init() ;    // configure it, start thread(s) if any 

// 2. Set the input buffer 
double[] inputData = new double[2] ; //allocate bean's input buffer
inputData[0] = 1.2 ;   
inputData[1] = 3.5 ;    

Steps 2, 3, and 4 may be repeated in a loop. Note: The init() method will set up the behavior of the bean in two respects. First, it will configure the AbleEventQueue to process AbleEvents. Second it will set up an asychronous timer event loop. For processing AbleEvents, there are two aspects to consider. You can tell the event queue to accept new events (queue them) or not, and you can tell the event queue to process events (dequeue them) or not. For example, you may want the bean to accept all incoming events but not start processing them until some other condition applies. Or you may want the event queue to not even accept new events if the bean is not ready to process them.

In the following example, the bean's event queue is set up with no timer processing and no asynch event processing enabled. No thread is created in the bean's event queue and this is the lightest weight bean you can create. It must be called synchronously by other beans from their thread.


setSleepTime(0);
setTimerEventProcessingEnabled(false);
setAbleEventProcessingEnabled(Able.ProcessingDisabled_PostingDisabled);

In the next example, we set up the bean's event queue to wake up every 5 seconds (the bean's processTimerEvent() method is called each time the timer pops) and also tell it to accept new events and process them on its own thread.


setSleepTime(5000);   // call processTimerEvent() every 5 seconds
setTimerEventProcessingEnabled(true);
setAbleEventProcessingEnabled(Able.ProcessingEnabled_PostingEnabled);

// create the event queue thread and start processing events
startEnabledEventProcessing();

In some cases, the init() method and event queue configuration is called directly from the bean constructor. This works well for relatively simple beans. However, in many cases, the user must first set bean parameters from the bean's customizer before the bean can be initialized. In this case, init() is called when a Configure button on the customizer is pressed. Any user parameters are set and the bean is configured and then ready to process timer or asynch events.

You can use the AbleBean function as any ordinary Java object or JavaBean. You call methods when you want the bean to do some processing for you. You can call getter/setter methods to set bean properties and state.

You can also use data flow buffers (input and output) to pass data between AbleBeans if they support it. If you want to pass data into the process() method, you first set the input buffer then call the process() method, and get the results from the output buffer.


AbleFilter bean = new AbleFilter() ;  // create a translate filter bean
bean.setDataFlowEnabled(true) ;  

double[] data = new double[10] ;  
double[] inBuf = (double[])bean.getInputBuffer() ;
for (int i=0 ; i < data.length ; i++) {
    inBuf[i] = data[i] ; // copy data into the input buffer of the bean
}
bean.process() ;  //  process the input buffer data

double[] outBuf = (double[])bean.getOutputBuffer(); // retrieve results 

Synchronous action events

You can also invoke methods indirectly be sending action events. These are AbleEvents whose arguments include the method (action) name and any argument objects.

    
AbleImport  import = new AbleImport() ;

import.setFileName( "aFileName") ;
import.open() ; 
// create a synchronous action event  
AbleEvent ev1a = new AbleEvent(this, null, "process", false);
// invoke process() method, read record, load output buffer
import.handleAbleEvent(ev1a) ; 
String[] outputData = (String[])import.getOutputBufferAsStringArray();

Asynchronous action events

You can also create asynchronous action events, which will be queued up and processed by the receiving AbleBean or AbleAgent on its own thread. This function allows any AbleBean to act as a server object.


AbleImport  import = new AbleImport() ;
import.setFileName( "aFileName") ; 
import.open() ;  
// create an asynchronous action event
AbleEvent ev1a = new AbleEvent(this, null, "process", true) ;
// invoke process() method, read record, load output buffer
import.handleAbleEvent(ev1a);

Timer action events

AbleObjects also have a timer capability, which can be optionally used to provide processing at defined intervals. When the timer expires, this calls the AbleObject's processTimerEvent method.

AbleDefaultAgent agent1 = new AbleDefaultAgent("agent1") ;
// Enable timer processing, but not asynch events
agent1.setTimerEventProcessingEnabled(true);
agent1.setAbleEventProcessingEnabled(Able.ProcessingDisabled_PostingDisabled);
agent1.setSleepTime(5000); // call processTimerEvent() every 5 seconds
// create/resume the event queue thread
agent1.startEnabledEventProcessing(); 

Processing can be suspended, resumed, or quit as well as started.

agent1.suspendEnabledEventProcessing(); // suspend thread, don't call autoProcess()
agent1.resumeEnabledEventProcessing(); // resume thread, processTimerEvent() gets called
agent1.quitEnabledEventProcessing();  // shut down the event queue thread

AbleAgents

Agents are containers; that is, they typically contain one or more beans who pass data to each other or to the agent itself. This is an example of an AbleNeuralAgentClassifier instantiating and configuring several beans with data connections in its init() method. It creates an AbleImport bean as a data source, and instantiates two filter beans generated from that import bean. Next it instantiates an AbleBackPropagation neural network bean, and creates data connections from the AbleImport bean to the first AbleFilter bean to the AbleBackPropagation bean and to the second AbleFilter bean. Finally it configures the agent for timer event processing which is used for training the neural network.


public void init() { 

    try {
      removeAllBeans() ;   // make sure container agent is empty

      imp1 = new AbleImport("Import") ;
      addBean(imp1) ;
      imp1.setBufferSize(bufferSize) ;
      imp1.setDataFileName(dataFileName) ;
      imp1.init() ;
      imp1.generateTranslateTemplate() ;

      // now import the two filters
      filt1 = (AbleBean)new AbleFilter("InFilter");
      filt1 = filt1.restoreFromFile( imp1.getDataFileName()+".xlt") ;
      addBean(filt1) ;

      filt2 = (AbleBean) new AbleFilter("OutFilter");
      filt2 = filt2.restoreFromFile( imp1.getDataFileName()+"b.xlt") ;
      addBean(filt2) ;

      // create a neural network
      net = new AbleBackPropagation("Network" ) ;
      addBean(net) ;

      int numInUnits = ((AbleFilter)filt1).getNumInUnits() ;
      int numOutUnits = ((AbleFilter)filt1).getNumOutUnits()  ; // net dups output
      // Note: netArch contains the hidden unit specs (usually "n 0 0")
      String arch = numInUnits + " " + netArch + " " + numOutUnits ;
      net.setNetArchitecture(arch);

      // create the data connections
      new AbleBufferConnection(imp1, (AbleObject)filt1 ) ; // training
      new AbleBufferConnection((AbleObject)filt1, net ) ;
      new AbleBufferConnection((AbleObject)net, (AbleObject)filt2 ) ;

      inputBuffer  = ((AbleFilter)filt1).getInputBuffer();
      outputBuffer = ((AbleFilter)filt2).getOutputBuffer();
      imp1.setDataFlowEnabled(true) ;  // training
      filt1.setDataFlowEnabled(true) ;
      filt2.setDataFlowEnabled(true) ;
      net.setDataFlowEnabled(true) ;
      // create/resume asynch thread and event queue thread
      startEnabledEventProcessing();
    } catch (IOException e1) {
      throw new RemoteException(e1.toString());
    } catch (ClassNotFoundException e2) {
      throw new RemoteException(e2.toString());
    }
}

Application Integration Scenarios

Here are several descriptions of how to integrate an AbleAgent into an application.

Transaction Server

In this case the user fills out an HTML form or a Wizard and specifies a set of parameters for a transaction request. This could be to book a trip, order a product, or perform a document search. The application receives the user input and packages it into an object or objects.

One way to do this is to call the AbleAgent process() method passing the object as an argument. The application will wait until the agent completes the transaction.

A second way to do this is to create an AbleEvent and then to pass the object as an argument and specify "process" as the action. This can be done either synchronously or asynchronously. If synchronously, then the application will wait until the agent completes the transaction. If asynchronously, then the application can continue processing, while the AbleAgent processes the request on an asynchronous thread.

A third way is to have the AbleAgent wake up occasionally and look to see if there is any work for it to do on an input queue shared with the application. It could take each transaction request, process them on its own thread, and then post the results to an output queue shared with the application.

Notification

In this case the application sets conditions on which it wants to be notified. The purpose of the agent is to monitor some system or application, watch changes in the system or application state, and send an event to the application when the specified condition occurs. This could be when a stock exceeds a threshold, when a message comes from a certain agent, or when urgent e-mail comes from the boss.

The standard way to achieve this would be for the AbleAgent to run asynchronously. The application will send events or call methods to register its interest in certain data and to set the trigger conditions (rules). Once the rules are set, the agent will take over, monitor the data, and send notification events to the application when any of the rules fire. The application would register as a listener on the agent.