IBM Agent Building Environment Developer's Toolkit


Chapter 4. Writing an Adapter

Now that you have been introduced to the basics of inferencing and rule formulation, the next step is to understand how the rules and the inferencing on rules relates to the outside world through adapters:

These questions are addressed through a very high level description along with some concrete illustrations from the sample Stock Adapter.

Basic Adapter Concepts

  +---------------------------------------------------------------------*
  |                                                                     |
  |                                                                     |
  |                                RULE                                 |
  |                                                                     |
  |                                                                     |
  |                                                                     |
  *--------------------------------------------------------------*------*
         ^                          ^                            |
         |                          |                            |
         | Trigger                  | Sensor                     | Effector
         | Event                    | (query)                    | (Action)
         |                          |                            |
         |                          V                            V
  *------*--------------------------------------------------------------*
  |                                                                     |
  |                                                                     |
  |                              Adapter                                |
  |                                                                     |
  |                                                                     |
  |                                                                     |
  *---------------------------------------------------------------------*

Trigger Events. An adapter can cause engine(s) to fire rules by generating a trigger event. Earlier we saw that a Trigger Event can include one or more facts (provided by the adapter) that go into the fact pool as short term facts and, by being in the fact pool, these facts can be used for rule evaluation. In addition, Trigger Events also include a standard set of event facts that also go into the fact pool as short term facts. These are as follows:

The adapter provides only the EventType and Selector (and an optional FactSet) when building a trigger event object. The remaining values of the event header are supplied by the agent implicitly.

The engine(s) invoke eventComplete() adapter methods in a broadcast manner such that adapters can expect to see eventComplete() invocations for events that they originate as well as for events that they did not originate. For this reason it is necessary to check the domain in the event header that is passed or check that the event id is for an event that your adapter generated. If the adapter generates multiple types of events, it should also check the event type.

Registration of Sensors and Effectors. During the initialization of an agent, each adapter specified in the configuration file is invited (through an identify() method) by agent initialization to "register" its sensors and effectors. That is, the adapter must specify to the agent exactly which queries (sensors) and actions (effectors) can by invoked in its domain by the engine(s) through applicable rules.

There are two types of sensors that can be registered:

  1. Boolean: which passes the terms of the sensor atom, which are fully bound, from which the invoked adapter returns a validation: true or false. A true result causes the atom that represents the sensor to be added as a short term fact in the fact pool. As you have seen, this allows the originating antecedent atom (the one that mapped to the boolean sensor) to be evaluated by the engine as true, contributing positively to the truth of the rule.
  2. Non-boolean: which passes the terms of the sensor atom, which can be unbound. In this case, when the sensor is invoked by the engine, the job of the adapter is to provide bindings for any unbound variable sensor term(s). The result of this binding is that the adapter returns one or more facts (one for each binding). These facts are added as short term facts to the fact pool. As before, these facts contribute to the evaluation of the current rules by providing facts that can match with antecedent atoms. If a non-boolean sensor's terms are bound before the sensor is invoked, the adapter simply validates and (if true) returns the same fact.

Sensors can only appear in the antecedent expression of a rule. Effectors can only appear in the consequent expression.

Effectors cause the adapter that registered that effector to be invoked to perform an action. The terms of the effector atom are parameters that are used by the adapter in performing the action. Effector actions may result in trigger events to affect other rule firings - in the manner of a chain reaction. Unlike sensors, there is no adapter response to an effector invocation.

Although a particular adapter may provide trigger events and also register both sensors and effectors, it is not imperative that it provide for all three of these. Some adapters only take actions, that is, provide effectors. Others may only provide trigger events or sensors. Adapters can relate to each other through rules. This is illustrated below where adapter 1 provides a trigger event that conditions a rule which contains a sensor that invokes adapter 2 and a consequent effector that invokes adapter 3.

  +---------------------------------------------------------------------*
  |                                                                     |
  |                                                                     |
  |                                RULE                                 |
  |                                                                     |
  |                                                                     |
  |                                                                     |
  *--------------------------------------------------------------*------*
         ^                          ^                            |
         |                          |                            |
         | Trigger                  | Sensor                     | Effector
         | Event                    | (query)                    | (Action)
         |                          |                            |
         |                          V                            V
  *------*------*            *-------------*              *-------------*
  |             |            |             |              |             |
  |             |            |             |              |             |
  |  Adapter 1  |            |  Adapter 2  |              |  Adapter 3  |
  |             |            |             |              |             |
  |             |            |             |              |             |
  |             |            |             |              |             |
  *-------------*            *-------------*              *-------------*

Compiling and Linking Your New Adapter Code

When you write an adapter, you use the class interfaces described in Chapter 3. "Adapter Reference Material" of the Components and Adapter Reference book.

Refer to Chapter 1. "Building ABE-Related Components" for assistance in compiling and linking with the IBM Agent Building Environment Developer's Toolkit classes.

A Sample Adapter - The Stock Adapter

Whereas Chapter 3. "Agent Server Sample" illustrated writing your own agent program, using IAAgent, the current Stock Adapter sample concentrates on adapter writing and is set up to run under one of the sample agent programs that are provided by Agent Building Environment Developer's Toolkit There are two such sample agent programs: one for C++ and one for Java. Refer to Chapter 6. "Building and Starting Your Agent" for further perspective on this distinction.

Thus far in this tutorial, KIF syntax has been used to illustrate and define it. Other sections of these guidelines continue use of KIF to solidify your understanding of it because it is consistently used internally. It is important especially for adapter writers where it is used to code facts. It is also important for activities such as creating a rule editor (or any form of authoring rules) because it is the form of syntax of rules and facts required by the Library Component of the IBM Agent Building Environment Developer's Toolkit for storing rules and facts. However, for the remainder of the tutorial on adapters, using the Stock Adapter sample, infix notation is used for rules and facts because it is easier to read. In addition, if you use the IBM Agent Building Environment Developer's Toolkit generic rule editor to view and change the rules provided with the sample Stock Adapter, and you are encouraged to do so, the infix notation is consistent with the form of rules that you see using the rule editor.

An adapter focuses on a particular domain. In the case of the Sample Stock Adapter, this domain involves the monitoring of stock prices. The Stock Adapter is written in Java to illustrate how to write adapters in Java. (The Mail Adapter is written in C++ to illustrate writing adapters in that language). These sample adapters are not meant to be industrial strength or particularly efficient. Rather, they emphasize teaching and illustrating the basic elements of adapter writing.

The Stock Adapter is used in the following section of the tutorial to build an understanding of adapters and their rules from the conceptual level all the way to the actual code. It describes the problem that is addressed, the registration of sensors and effectors, the set up and initialization of the adapter, the pertinent trigger events, specific rules utilized, agent/engine/adapter flows, and finally the annotated Java code. If you already understand the concepts, you can skip forward to suit your knowledge level, perhaps directly to the code.

Where/How Do You Start in Writing an Adapter?

Experience has shown that the most effective way to approach the problem of writing/designing an adapter is to decide what you want the adapter to do. What does the adapter monitor (define its domain)? What actions does it take? Does it need other adapters? What actions does it require from other adapters? Then write the rules (and optional long term facts) to bring it all together. Define the sensors and effectors as you write the rules. As you write the rules, it becomes more apparent where you need trigger events to kick them off, sensors to query for more information, and cause actions through effectors. You start to see where you need to employ other adapters through these rules.

For the Stock Adapter, the following are some beginning thoughts that influenced writing of the rules and long term facts:

The Stock Adapter is driven by time intervals, using the Time Adapter to drive these time intervals. This is a good illustration of the relationship between adapters through rules. The objective of the Stock Adapter is to sample selected stock prices at these timed intervals, compare the stock price to a price threshold, and notify a buyer (or other appropriate person) when the price exceeds the threshold. It soon becomes obvious that some of this work takes place in the adapter itself, but that much of the work is done through adapter related rules and the use of other adapters. The Stock Adapter gets stock prices through a web search, using Yahoo. It must parse the web page that results from the search in order to get the stock price.

Much of the work just described is accomplished through the rules and long term facts themselves. We have some rules and long term facts especially for for configuring the Stock Adapter. This illustrates one way of setting up an adapter with some special parameters. This method allows for easy changes to the parameters by editing the long term facts from which they are derived. ("RemAgent Object - Configuration of Agent Building Environment Developer's Toolkit Components" and "Message Adapter" illustrate yet another, way of passing them: passing a parameter through the newadapter() method) to the adapter constructor. The latter has the advantage of getting information to the adapter constructor where it can be used to do special initialization that is required before any events can occur or before any sensor or effectors can be invoked.

The Stock sample even isolates the rules and long term facts for this configuration activity in a separate conduct set called stockset This conduct set controls fairly stable directions such as where and how to find the stock status information that is monitored.

Its other conduct set named sampstok controls the run time activity for the Stock application. The rules and facts in this run time conduct set control more dynamic things such as which stocks are to be monitored and at what time intervals they are to be monitored.

So you begin by sketching out the rules and establishing just which sensors and effectors are required for them, as well as trigger events to kick off the rules. Writing the code is then a simple matter of implementation of those trigger events, sensors, and effectors. This tutorial follows these same steps, concluding with the code itself. If you prefer, you may look ahead to the code segments that follow and that are associated with these conceptual explanations, although a basic understanding of the concepts is generally more helpful.

Initialization - New Adapter Object

Each adapter is represented in the agent by an adapter object. Writing a new adapter is largely a matter of writing some standard (and perhaps a few non-standard) methods for a new adapter object class. Each adapter object class is an extension of the IAAdapter super class (provided by the IBM Agent Building Environment Developer's Toolkit). Most of the methods written are overrides of the methods of the super class, but an adapter can also add its own methods.

The newAdapter() function is a special one. For C++ adapters it is written as a regular C function rather than a method on the adapter class. In Java it is written as a static member function. This function is invoked by the agent at the beginning of agent initialization for each adapter that it finds in the configuration file.

Initialization - Adapter Registration


                                                                     Registry of Sensor, Effectors
                                                                  *-----------------------------------*
                                      Association                 |Each entry:                        |
                            *------------------------------------>|  Procedure-name                   |
                            |                                     |    StockAdapter:GetStockEventCount|
                            |                                     |  Type                             |
                            |                                     |    IASensorProc                   |
                            |                                     |  Signature                        |
                            |                                     |    IntegerLvar                    |
                            |                                     |  Token                            |
                            |                                     *-----------------------------------*
                            |
                            |
     Rule                   |
  *-------------------------+----------------------------*
  |      Antecedent Atom:   *                            |
  |                   predicate(term) *-------*          |
  *-------------------------------------------+----------*
    ^                                         |
    |                                         |
    | Trigger Event                           |       Invoke
   **----------------*                        |  Non-Boolean Sensor
   | Event Header    |              *---------*-----------------------*
   | Event FactSet   |              | answerQuery(token,EventHdr,atom)|
   **----------------*              |                              ^  |
    |                               |                              |  |
    |                               |                KIF form      |  |
    |                               |                of term list*-*  |
    |                               *---------*-----------------------*
    |                                         |
    |                                         V
  *-+----------------------------------------------------*
  | **notify()              Adapter                      |
  |                                                      |
  *------------------------------------------------------*

During initialization, the agent prompts each adapter to register its sensors and effectors by invoking the identify() adapter method. The identify() method does the registration by invoking a registerProcedure() method for each of its sensors and effectors. The parameters provide the information for the agents registry as illustrated above:

The above illustration shows:

You will see the particular sensor and effectors for the Stock Adapter discussed below.

Initialization - Adapter Configuration Parameters

Adapter initialization is accomplished through rules that are sensitive to a special, adapter-generated EventName, "AGENT:CONFIG". The event with this name is generated (note the special domain name, AGENT:) when the agent is going through its initialization. This occurs even before the adapter start() method is invoked and before the "AGENT:STARTING" event is generated by the agent.

The AGENT:CONFIG event allows for configuring adapters through rules that should be evaluated before trigger events are generated by any adapter. Rules conditioned on AGENT:CONFIG should be in a conduct set that also has a selector named "AGENT_CONFIG" to insure that these configuration rules are "selected" when the AGENT:CONFIG event occurs. By applying the AGENT_CONFIG selector to a conduct set, you are separating the rules for doing configuration from other rules (in other conduct sets) that are used by your application after configuration.

An important distinction between the AGENT:CONFIG and AGENT:STARTING events is that all adapters receive an event complete (invoking eventComplete() of IAAdapter) for AGENT:CONFIG events, but only adapters that have effectors associated with rules conditioned by AGENT:STARTING get event complete invocations from the AGENT:STARTING event. This makes it appropriate to use AGENT:CONFIG for configuring adapters, insuring that they all have a way (their eventComplete() method) of knowing when they have received all of the configuration information (in case they need to take some appropriate action).

A little later you will see a flow of how all of this works together. By conditioning rules on this event name you can get configuration information to an adapter. This conditioning is possible by including the following as an antecedent atom of a rule: EventName ("AGENT:CONFIG") and a consequent atom that is an effector. It is also necessary to specify the "AGENT_CONFIG" selector for the containing conduct set. The effector that is invoked from a rule conditioned on this event can provide for the initialization of the invoked adapter using the terms in the effector as initialization parameters.

The initialization of the Stock Adapter depends on such initialization rules to invoke Stock Adapter effectors. Some of these configurating rules use long term facts, both to allow for ease of modification and for illustrating the use of long term facts by an application. Following is the information supplied and the associated rules:

Figure 4-1. Rule 1. Initialize Monitor Class

EventName("AGENT:CONFIG") =>
     StockAdapterMonitorClass("ibm.AgentBuilder.samples.stock.QuoteYahooCom")

Requests that the StockAdapter use class 'QuoteYahooCom' to obtain HTML for processing. This class is loaded dynamically and performs all required network transactions to obtain raw HTML containing stock quote information.

Note: This class must be a subclass of MonitorSC. This allows the use of polymorphism when the class is loaded (i.e. once the specific class is loaded, it is referenced as an instance of MonitorSC).

Figure 4-2. Rule 2. Initialize URL

EventName("AGENT:CONFIG") => StockAdapterURL("quote.yahoo.com/quotes")

Requests that the URL 'http://quote.yahoo.com/quotes' be used when obtaining stock quote information.

Figure 4-3. Rule 3. Initialize Parser Class

EventName("AGENT:CONFIG") => StockAdapterParserClass("ParseQuoteYahooCom")

Requests that the StockAdapter use class 'ParseQuoteYahooCom' when parsing the raw HTML returned during a monitoring event.

Note: This class must be a subclass of ParseSC. This allows the use of polymorphism when the class is loaded (i.e. once the specific class is loaded, it is referenced as an instance of ParseSC).

Note: The specification of the parser and monitor classes is done in parallel with the specification of the URL (see the previous rule). A change in the URL may require a change in the parser class because the parser is designed to handle the specific HTML returned from the specified URL.

Figure 4-4. Rule 4 and Companion Long Term Fact. Initialize Proxy

EventName("AGENT:CONFIG") AND (HTTPProxyAndPort (?proxy, ?port)
         => StockAdapterProxy( ?proxy, ?port)
-----------------------------------------------------------------------
HTTPProxyAndPort ("myhttpproxy.mycompany.com" 80)

This long term fact (and others for the stock adapter configuration) are in a fact set that is in the same conduct set as the associated rules (for stock adapter configuration). So the conduct set "stockset" contains a RuleSet called "stockset" and a LTFactSet called "stockset".

This rule and long term fact establish a proxy server as 'myhttpproxy.mycompany.com' at port 80. This application was developed behind a fire wall. This requires the use of a proxy server to gain access to the Internet. If you also have this situation you should place the name of your proxy server in this rule. If no firewall exists, the fact should be deleted.

Figure 4-5. Rule 5 and Companion Long Term Fact. Initialize 'from' field

EventName("AGENT:CONFIG") AND EmailFrom (?from)
         => EMailFromAddressIs(?from)
----------------------------------------------------------------------
EmailFrom ("me@myaddress.com")

This rule and long term fact establish the from address in any notes sent from the Email adapter. The StockAdapter's rules use the Email adapter to notify interested parties that stock prices have reached a specified level.

Figure 4-6. Rule 6 and Companion Long Term Fact. Initialize 'relay host' field

EventName("AGENT:CONFIG") AND RelayHost (?host)
         => EMailRelayHostIs(?host)
----------------------------------------------------------------------
RelayHost ("myrelayhost")

This rule and long term fact establish the relay host for Email. This is required for Win32 platforms. Fill in a host or delete the fact as appropriate for your platform. By deleting the fact where not applicable, the rule fails and the Stock Adapter simply does not establish a relay host.

Initialization - Starting Event - Time Intervals

The remaining rules are in the "sampstok" conduct set, which contains a RuleSet and LTFactSet by that same name. This includes rules and long term facts that finish initialization by establishing Time Adapter effector-driven alarms that drive the Stock application's activities, as well as the trigger event driven rules and long term facts that result from the alarm pops and other associated activities.

The Stock Adapter's rules control how often it obtains stock price quotes by using the Time Adapter, which is a core adapter of the IBM Agent Building Environment Developer's Toolkit. This is a good illustration of interactivity between Java and C++ adapters. At the agent level, the implementation of the adapter is not a consideration and both are treated equally.

In particular, the Stock Adapter uses the SetIntervalAlarm effector of the Time Adapter. The Time Adapter supports setting of several types of alarms through its effectors. This one provides a recurring alarm at a given time interval and an AlarmId string to be associated with the resulting alarm trigger event. The latter is an example of an adapter-defined fact that accompanies the generated trigger event. The EventName associated with the trigger event is "Time:Alarm". So the rule responsible for setting an IntervalAlarm to monitor IBM and LXK stock every 20 seconds is:

Figure 4-7. Rule 7 and Companion Facts. Initialize Primary Interval Alarm

EventName("AGENT:STARTING") AND
      StocksToMonitor1 (?stocks) AND
      MonitorFrequency1 (?interval, ?units)
      => SetIntervalAlarm (?interval, ?units, ?stocks "")
---------------------------------------------------------------------
StocksToMonitor1 ("IBM LXK")
MonitorFrequency1 (20 "seconds")

Notice that this rule fires only once, during agent initialization (AGENT:STARTING) and establishes a alarm that will 'pop' every 20 seconds.

Initialization - Start Up

Once all adapters objects are created, their sensors and effectors are registered, and the AGENT:CONFIG and AGENT:STARTING events are processed, the agent invokes the start() method for each adapter. Adapters, such as the Stock Adapter use this to indicate that it is okay to start generating trigger events. Adapters should not generate events until initialization is complete, as indicated by this start() method invocation.

One reason an adapter might have for overriding the start() method would be to pass initialization parameters to the adapter. This is okay for more static parameters, but using the initialization rules to set adapter startup parameters is more appropriate for dynamic use where they are more easily changed through simple rule modification.

Trigger Events - Notification to Engine(s)

Another Stock Adapter rule is sensitive to the alarm pop event that was set up at initialization (above). This alarm-conditioned rule obtains the data (AlarmId) associated with the alarm pop event (IBM and LXK in this case):

Figure 4-8. Rule 8. Invoke Stock Monitor

EventName("Time:Alarm") AND AlarmId(?alarmId) => StockMonitor(?alarmId)

As you can see, this rule can be successfully fired only when a Time:Alarm event occurs. The AlarmId() atom binds the identification information associated with this identified interval alarm to a variable. This same binding is propagated to the effector term by which it is passed to the StockMonitor method through a Stock Adapter effector call. The engine that invokes this effector uses a performAction() adapter method passing the registered effector token, the effectors term (?alarmId), etc. The token resolves to a Stock Adapter routine called StockMonitor() which takes the stock symbol (alarmId) and uses the web to find the current price for the specified stock. Once the price has been obtained, an event is generated by the Stock Adapter. This event includes facts that specifies the actual current stock prices along with the stock symbols.

The engine is notified of stock prices when the Stock Adapter generates a trigger event. This event is named:

StockAdapter:StockPriceEvent
This event occurs after the StockMonitor routine of the Stock Adapter (kicked off by the alarm pop rule effector above) has found the price of the stocks specified by the stock symbols in the ?alarmId (term of the effector in rule 7). The StockAdapter:StockPriceEvent includes a fact set made up of a fact atom for each stock quote included in the alarmId that kicked it off. For example, where the alarmId is "IBM LXK", prices for IBM and LexMark are requested. For example, the resulting StockMonitor:StockPriceEvent may include a fact: StockPrice("IBM", 125.00) and a second fact: StockPrice("LXK", 23.75).

Then there are Stock Adapter-associated rules that are sensitive to this event and these facts (that go into the fact pool):

Figure 4-9. Rule 9 and Companion Long Term Facts. Notify interested stock owners

EventName("StockAdapter:StockPriceEvent") AND
    StockPrice (?stock, ?price) AND
    StockPriceThreshold (?stock, ?low) AND
    StockCompare(?price, ">", ?low) AND
    EmailAddress (?address) AND
    Append (?msg, ?stock, " stock is above ", ?low, ". It's price is ", ?price, ".", "", "")
    => StockAdapterShow (?stock", "is above threshold price  I sent email.  Price is: ", ?price) AND
       EMail (?address, ?msg)
----------------------------------------------------------------------------------------------
StockPriceThreshold ("IBM", "80")
StockPriceTHreshold ("LXK", "15")
EmailAddress ("me@myaddress.com")

Note: The rule is placed on several lines to aid readability. This is not true in the actual rules file.

This rule uses long term facts, "StockPriceThreshold", to determine the threshold price for comparison with the current price. Then it uses a Stock Adapter sensor, "StockCompare", to do the actual comparison (this sensor was required/written for the Stock Adapter BEFORE the Utility Adapter was written to provide a similar comparison sensor). It uses a long term fact, "EmailAddress" to get the email address. The "Append" predicate maps to the Utility Adapter sensor, "StringAppendMany" to concatinate some strings (resulting string in ?msg). The mapping of the predicate "Append" to the sensor "StringAppendMany" is accomplished through the required RuleSet-level metadata for the "sampstok" RuleSet. This is the RuleSet metadata named '0' and it is the metadata that is required by the engine for its RuleSets. This metadata is used to map predicates to sensors or effectors etc. Refer to "RuleSet Metadata Required for RuleSets" for an explanation of this mapping and the required RuleSet metadata file. Refer to the "sampstok.ms0" file in the Stock sample adapter directory to see the exact RuleSet metadata mappings used for the current sample.

The effectors provide for sending email and a Stock Adapter message for reporting the similarly.

Sensors - Supplemental Information Request (by engines to adapters)

The fourth antecedent atom, StockCompare, in rule 9 above is a good example of a boolean sensor. The ?price variable is bound by the engine before the sensor is invoked. This binding occurs as follows: The StockPrice fact of the StockAdapter:StockPriceEvent binds the second atom of the rule and the same binding for ?price is propagated forward to other occurances in the rule (the fourth atom of the antecedent and in the StockAdapterShow atom of the consequent).

The engine determines that the fourth atom is a boolean sensor by associating it with the registered sensors. It invokes the sensor by invoking the adapter's testCondition() method, in pretty much the same way as illustrated above for answerQuery() (refer to "Initialization - Adapter Registration").

Another Stock Adapter rule below shows the two remaining sensors:

Figure 4-10. Rule 10 and Companion Long Term Facts. Terminate primary alarm, set secondary alarm

EventName("StockAdapter:StockPriceEvent") AND
    GetStockEventCount(?count) AND
    IntegerCompare(?count,"=", 2) AND
    StocksToMonitor1 (?stocks1) AND
    StocksToMonitor2 (?stocks2) AND
    MonitorFrequency2 (?interval, ?units)
    => TurnOffAlarm (?stocks1) AND
       StockAdapterShow("I just turned off the primary alarm.", " ", " ") AND
       SetIntervalAlarm(?Interval, ?units, ?stocks2, "") AND
       StockAdapterShow( "I just started an interval alarm for monitoring ", ?stocks2, " stocks.")
-----------------------------------------------------------------------------------------------
StocksToMonitor1 ("IBM LXK")
StocksToMonitor2 ("HSY")
MonitorFrequency2 (20, "seconds)

This rule is has an atom that is conditioned on an internal counter having reached a value of 2. The counter counts 'event complete callbacks'. This is a deliberate demonstration of the use of eventComplete() to control adapter clean up or adapter state information. Often, adapters may want to make use of this to clean up after an inferencing event. This rule does the following:

  1. Stops the interval alarm on 'IBM' and 'LXK'
  2. Starts a new interval alarm on 'HSY'
  3. Prints some console messages
The second antecedent atom, GetStockEventCount, in rule 10 above is a non-boolean sensor for acquiring a count maintained by the stock adapter that indicates the number of price quotations (StockMonitor checks on the web) that have been completed so far. The invocation of the answerQuery() method for this sensor results in a new fact: GetStockEventCount, which has a term of type integer, bound with the current count. This same binding for count is propagated to the third atom, which is a boolean sensor IntegerCompare, also for the Stock Adapter. This boolean sensor asks the Stock Adapter to perform the comparison operation indicated and return true or false. If true, the atom goes into the fact pool and the antecedent is evaluated as true. This is the case when two IBM/LXK quotes have been completed.

Now Rule 9 applies again for "HSY" stock. Finally Rule 11 below terminates the sample after a count of 5 is exceeded.

Figure 4-11. Rule 11 and Companion Long Term Fact. Terminate secondary alarm

EventName("StockAdapter:StockPriceEvent") AND
    GetStockEventCount(?count) AND
    IntegerCompare(?count,">", 5) AND
    StocksToMonitor2(?stocks2)
    => TurnOffAlarm(?stocks2) AND
       StockAdapterShow( "I just turned off the secondary alarm.", " ", " ")
---------------------------------------------------------------------------------------------
StocksToMonitor2("HSY")

Effectors - Action Request (by Engine(s) to adapters)

Rules 9 and 10 that were used to illustrate the Stock Adapter sensors (above) also illustrate its effectors:

Summary of Flow

Assuming the sample rule and configuration files are used, the following is the sequence of events as the agent starts and the Stock Adapter begins to monitor stock prices.

Figure 4-12. Flow 1. Initialization

==============================================================================================================================
   Agent                 Engine                    StockAdapter (Subclassed from IAAdapter)
==============================================================================================================================
(Agent is started via iagent.exe)


(Consult config file)


-----------------------> newEngine()
<--- obj reference -----
(Repeat, each engine)


--------------------------------------------------> newAdapter()
<--------------- object reference -----------------
(Repeat, each adapter)


--------------------------------------------------> identify()
                                                         ---------------------> registerProcedure()
                                                         <---------------------
                                                         (Repeat, each function)
<--------------------------------------------------
(Repeat, each adapter)


--- load conduct set --->
<------------------------
------------------------> start()
<------------------------


--AGENT:CONFIG evt-->
                       (Inference on "stockset" Conduct Set)

                         Boolean sensors:
                            ----------------------> testCondition()
                                                         ---------------------> someInternalProc()
                                                         <--- boolean ---------
                            <--- boolean ----------

                         Non-boolean sensors:
                            ----------------------> answerQuery()
                                                         ---------------------> someInternalProc()
                                                         <--- fact set --------
                            <--- fact set----------

                         Efectors:
                            ----------------------> performAction()
                                                         ---------------------> someInternalProc()
                                                         <---------------------
                            <----------------------

                         (Repeat above, as needed...)
                         (i.e. inference until done )

                         Event complete:
                            ----------------------> eventComplete()
                            <----------------------

<----------------------
--AGENT:STARTING evt-->
                       (Broadcast so it applies to all Conduct Sets)
                         Event complete:
                            ----------------------> eventComplete()
                            <----------------------

<----------------------

--------------------------------------------------> start()
<--------------------------------------------------
(Repeat, each adapter)

(Agent now waits for trigger events)

  1. The agent consults the configuration file and creates objects for any engines that are specified. In the sample an object for RAISE is created.

  2. The agent consults the configuration file and creates objects for any adapters that are specified. In this sample, objects for

    are created. The adapter objects are created by a call to each adapters newAdapter() method. Recall that it's primary function is to instantiate an object of "itself" and return it to the caller.

  3. For each adapter, the agent calls the identify() function. This causes each adapter to register sensors and effectors that the adapter supports. The registerProcedure() method is used by the identify() method to do the registration.

  4. The conduct set is loaded for use by the RAISE engine.

  5. The engine is started.

  6. The AGENT:CONFIG event is generated by the agent. This conditions Rules 1, 2, 3, 4, 5, and 6 to be evaluated. Rule 7 is conditioned by the AGENT:STARTING event. These rules are concerned with initializing the Stock and EMail adapters. Notice that the Rule 7 is requesting that the Time adapter generate an event every 20 seconds. This IntervalAlarm is the driving force behind the Stock Adapters function.

  7. Once these initial agent-originated events are finished, the start() method of each adapter is invoked. This gives each adapter "permission" to begin generating appropriate events of their own.

    Figure 4-13. Flow 2. Normal Event Flow

    ==============================================================================================================================
       Engine                   Time  Adapter               Stock Adapter                     Email Adapter
    ==============================================================================================================================
                                Alarm pop - set at
                                  initialization
      Trigger Event <-----------notify()
        Time:Alarm
         .
         .
      Inferencing
         .
         |------------------------------------------------>performAction()
                                                             monitorStock
         |---------------------->eventComplete()               thread
                                    note 1                        .
                                                                  .
                                                               stock quote obtained
    
      Trigger Event<-------------------------------------------notify()
        StockAdapter:
           StockPriceEvent
         .
         .
           inferencing
         .
         |------------------------------------------------>testCondition()
                                                             StockCompare price > 100
                                                                  .
                                                                  .
           <-----------------------------------------------------true
    
         |------------------------------------------------>performAction()
                                                             StockAdapterShow
    
         |--------------------------------------------------------------------------------->performAction()
                                                                                              EMail (send)
    
         |------------------------------------------------>eventComplete()
                                                              note 1
    
                  note 1: eventComplete() goes to all adapters that have
                          sensors or effectors that are fired under the
                          rule conditioned by the associated event
                          occurrence.  Therefore,
                          each adapter must test the event header
                          domain for applicability to its own domain.
                          However, eventComplete() is shown here only
                          for the originator.
    

    :ebody.

  8. After the specified period of time has elapsed, the Time adapter generates a Time:Alarm event. This is generated as a result of the rule 7.

  9. The Time:Alarm event causes Rule 8 to be evaluated true. The second clause in Rule 8 ( AlarmId(?alarmId) ) contains a variable. The variable is bound or "set equal to" the alarms identification. At this point it is the string "IBM LXK" (see Rule 7).

    Because the rule is evaluated true, the effector StockMonitor() is called, passing in the binding for the variable ?alarmId.

    Inside the Stock Adapter, the performAction() method is called. This method is provided with the token (IAProcToken) (known from registration), the current event header (IAEventHeader) and a string that represents the atom being passed in. The performAction() method evaluates the passed token and determines that the correct internalroutine to call is stockMonitor().

    If you study the sample code you will see that the internal routine stockMonitor() creates a separate thread, starts it and returns control to the caller. The separate thread continues execution in the background. At this point it performs all the required steps to contact the source of stock information, parsing it and then generating an event.

    At this point the event is complete. No other rules are triggered by the Time:Alarm event.

  10. In the meantime, the Stock Monitor thread succeeds in obtaining a stock quote for IBM stock. For this discussion assume that IBM stock was quoted at $80.75. MonitorThread.java then creates an atom called "StockPrice" and fills it with two terms It places the atom in a fact set.

    This is repeated if a stock quote from LXK is obtained. That is, a factset may contain one or more fact.

    Then MonitorThread.java calls the notify() method passing it a trigger event (for event=StockPriceEvent) and passes the factset that was just created. This informs the engine that an event needs to be processed.

  11. A StockAdapter:StockPriceEvent occurs. This event was created by the Stock Monitor thread. The appearance of this event conditions Rules 9, 10, and 11 as for evaluation by the engine. Note that each of these rules has multiple antecedent atoms, and each must evaluate to true to cause execution of the consequent.

    Rule 9 is sensitive to the either the IBM or the LXK stock quote. In fact, the rule may fire twice if the fact set that is generated with the event contains two facts (one for IBMand one for LXK).

    The StockPrice() clause for Rule 9 evaluates to true and the ?price variable is set to "80.75" (recall that the factset we used when we called notify() was for IBM stock). The StockCompare() boolean sensor is called. If the StockAdapter testCondition() method is called, the passed token is consulted and the internalstockPriceCompare() called. In this case the answer is "true" and the entire antecedent for Rule 9 is evaluated as "true".

    Because the antecedent is true, each clause in the consequent is driven. StockAdapterShow() clause causes performAction() in StockAdapter to be driven. The token is consulted and the internal routine stockAdapterShow() is called. The passed parameter is also passed within an atom. The EMail() clause causes a similar action but this action occurs within the sample Mail adapter.

    Rules 10 and 11 fail because the EventCount is below the specified threshold.

  12. After the event has completed, the eventComplete() method in the adapter is called. In this example the only action taken by eventComplete() is to advance an internal counter. The counter is used by Rules 10 and 11 to demonstrate turning on/off interval alarms.

The above description only describes the beginning of execution. Events will continue to be generated by the Time adapter, causing the monitor thread to obtain stock quotes. Rules 10 and 11 demonstrate the ability to turn off interval alarms and set them again based on the value of our internal event complete counter.

Stock Adapter - Annotated Java Code

This section describes the main parts of the Stock Adapter. As you examine each section of code for the adapter, you see a description of its role in the overall adapter operation, and the important points in its implementation. The code presented is Java but the concepts (and method names) are consistent with adapters written in C++. The source code for the Stock Adapter is part of the IBM Agent Building Environment Developer's Toolkit download package, and can be consulted as well. Note that the code segments presented have had some comments removed.

Figure 4-14. StockAdapter. Imports and class statement

// -----------------------------------------------------------------------
// (C) Copyright IBM Corp, 1996
//
// File:        StockAdapter.java
// Version:     1.0 (sample) 6 August 1996
//
// Description: This class extends IAAdapter and performs all tasks required
//              to register and act as the StockAdapter Adapter.
//
// DISCLAIMER OF WARRANTIES:
// -------------------------
// The following (enclosed) code is sample code created by IBM
// Corporation.  This sample code is not part of any standard IBM product
// and is provided to you solely for the purpose of assisting you in the
// development of your applications.  The code is provided "AS IS",
// without warranty of any kind.  IBM shall not be liable for any damages
// arising out of your use of the sample code, even if they have been
// advised of the possibility of such damages.
// -----------------------------------------------------------------------
package ibm.AgentBuilder.samples.stock;

import ibm.AgentBuilder.aeapi.*;
import ibm.AgentBuilder.Adapter.*;
import ibm.AgentBuilder.Adapter.IAAdapterException;

import ibm.AgentBuilder.samples.stock.*;

public class StockAdapter extends IAAdapter {

Aside from the disclaimer, this section of code contains Java import statements that obtain access to several key structures including the adapter and exception classes and the other local classes that make up the Stock Adapter. Note that StockAdapter extends the IAAdapter super class. Each user-written adapter simply overrides methods provided in the super class (and also adds methods for it's own use as required).

Figure 4-15. StockAdapter. newAdapter()

   public static IAAdapter
         newAdapter(String domain, String parms) throws IAAdapterException {

     if (!domain.equals("StockAdapter")) {
        System.out.println("Wrong Domain *" + domain + "*");
        throw new IAAdapterException("newAdapter() does not support domain " +
                                                        domain + "; only \"StockAdapter\" is valid.");
     }
     return new StockAdapter();
   }

newAdapter() is a method in the IAAdapter super class that must be overridden. This method creates an object that represents the adapter and returns it to the caller. It is used during agent initialization. Notice that it is defined as "public static" and thus exists before the object has been instantiated.

The newAdapter() function instantiates an object of type StockAdapter and returns it to the caller (the agent). The agent then acts on the object.

Figure 4-16. StockAdapter identify() example, part 1. This code fragment shows the first half of the identify member function of the StockAdapter class. This section shows the registration of the StockAdapter effectors.

   public boolean identify() {

     // Registration tasks.
     try {

       // Register Effectors.
       registerProcedure("StockAdapterShow",
                          IAEffectorProc,
                          "String String String",
                          new IAProcToken(StockAdapterShow_Token));
       registerProcedure("StockAdapterMonitorClass",
                          IAEffectorProc,
                          "String",
                          new IAProcToken(StockAdapterMonitorClass_Token));
       registerProcedure("StockAdapterURL",
                          IAEffectorProc,
                          "String",
                          new IAProcToken(StockAdapterURL_Token));
       registerProcedure("StockAdapterParserClass",
                          IAEffectorProc,
                          "String",
                          new IAProcToken(StockAdapterParserClass_Token));
       registerProcedure("StockMonitor",
                          IAEffectorProc,
                          "String",
                          new IAProcToken(StockMonitor_Token));
       registerProcedure("StockAdapterProxy",
                          IAEffectorProc,
                          "String Integer",
                          new IAProcToken(StockAdapterProxy_Token));




Figure 4-17. StockAdapter identify() example, part 2. This code fragment shows the second half of the identify member function of the StockAdapter class. This section shows the registration of the StockAdapter sensors.

       // Register Boolean Sensors.
       registerProcedure("StockCompare",
                          IABooleanSensorProc,
                          "String String String",
                          new IAProcToken(StockCompare_Token));
       registerProcedure("IntegerCompare",
                          IABooleanSensorProc,
                          "Integer String Integer",
                          new IAProcToken(IntegerCompare_Token));

       // Register NON Boolean Sensors.  i.e. Sensors that return a factset.
       // The one term in this sensor may be a variable for us to replace
       registerProcedure("GetStockEventCount",
                          IASensorProc,
                          "IntegerLvar",
                          new IAProcToken(GetStockEventCount_Token));

     // Errors?
     } catch (Exception e) {
       System.out.println("StockAdapter:identify() " + e.getMessage());
       return false;
     }
     return true;
   }

Note: Only one example of each sensor/effector is shown in this example in the interest of saving space.

The identify() method invites your adapter to register sensors and effectors that are supported by the adapter. By using the registerProcedure() method call, the adapter is proclaims that it honors invocations for the specified sensor or effector.

The first argument provided to registerProcedure() is the procedure name that can be associated with the predicate in a rule atom. The second argument is the type of procedure being registered. The third argument is the signature associated with the call. The fourth argument is the token that is associated with the call. The token is used by the adapter\ when a sensor or effector is invoked to determine which internal routine is to used to satisfy a sensor or effector call. The final argument is an IAError object which will contain status after the identify returns.

Things to Watch For

Figure 4-18. StockAdapter. start(), stop() and shutdown()

   public boolean start() {
      started = true;
      return super.start();
   }

   public boolean stop() {
      started = false;
      return super.stop();
   }

   public void shutdown() {
      monitorObj = null;
      parseObj = null;
      super.shutdown();
   }

The start() method in the adapter is called by the agent to indicate that processing may begin. Specifically, after it has been called, the adapter is free to call notify() to generate events. Other uses of this routine include initialization of internal structures. In this limited example, a boolean is simply set to true. For adapters in general, the start() method is optional and need not be provided.

The stop() method in the adapter is called by the agent when processing should stop. Specifically, after it has been called, no further events should be generated by the adapter. It is optional, but is required for any adapters that do notify() (generate trigger events).

The shutdown() method in the adapter is called by the agent when all events have completed and processing is to be terminated. The adapter should clean it's internal state. In this example, two internal objects are set to null.

Things to Watch For

Figure 4-19. StockAdapter. eventComplete()

   public void eventComplete(IAEventHeader hdr) {
      String myDomain;

      try {
         myDomain = hdr.getDomain();
      } catch (Exception e) {
         System.out.println("StockAdapter:eventComplete() error getting data
                                                       from event header!");
         return;
      }

      // check to see if this event complete is for an event in our domain
      // (our adapter) and then increment our count of events processed by
      // this adapter
      if (myDomain.equals("StockAdapter")) {
         eventCount++;
      }
   }

The eventComplete() method is called by the agent after the associated event has been completely processed. Its primary intent is to allow the adapter to clean up anything it is keeping during event processing. In this example a counter is simply advanced.

Things to Watch For

Figure 4-20. StockAdapter. performAction()

   public void performAction(IAProcToken token,
                             IAEventHeader hdr,
                             String binding)        throws IAError {

      IAAtom iAtom;

      try {
         // create an atom from the binding String in KIF format for ease
         // of processing
         iAtom = new IAAtom();
         iAtom.setKIF(binding);

         // switch on the IAProcToken to determine the correct effector
         // IAProcToken is set in the registerProcedure call
          switch(token.intValue()) {

             case StockAdapterShow_Token: {
                stockAdapterShow(iAtom);
                break;
             }
             case StockAdapterMonitorClass_Token: {
                stockAdapterMonitorClass(iAtom);
                break;
             }
             case StockAdapterURL_Token: {
                stockAdapterURL(iAtom);
                break;
             }
             case StockAdapterParserClass_Token: {
                stockAdapterParserClass(iAtom);
                break;
             }
             case StockMonitor_Token: {
                stockMonitor(iAtom);
                break;
             }
             case StockAdapterProxy_Token: {
                stockAdapterProxy(iAtom);
                break;
             }

             default:
                throw new IAError(performActionError,
                     "Unknown Token seen in perform action!");
          }

      } catch (Exception e) {
         throw new IAError(performActionError,
               "StockAdapter:performAction(): " + e.getMessage());
      }

     return;
   }

The performAction() adapter method is the "routing" routine for any predicates that are associated with registered effectors. This routine uses the token to determine the effector routine is to be called to satisfy the request. The parameter "binding" contains a string in KIF format. This is used to create an atom for passing to our internal routines. The internal routines can then interrogate the atom for the specific terms that are required (consult the source for an example of a routine that does this). Refer to "Class IAAtom" of the Components and Adapter Reference book for a detailed discussion of atoms.

Things to Watch For

Figure 4-21. StockAdapter. answerQuery()

   public IAFactSet answerQuery(IAProcToken token, IAEventHeader hdr, String binding)
                  throws IAError {

      IAAtom    iAtom;
      IAAtom    rAtom;
      IAFactSet returnFactSet;

      try {
         switch(token.intValue()) {
            // in this case we have only one IAProcToken for answerQuery
            case GetStockEventCount_Token: {

               // Create an atom from the input binding String
               iAtom = new IAAtom();
               iAtom.setKIF(binding);

               // Create a true fact for GetStockEventCount
               rAtom = new IAAtom();
               rAtom.setPredicate(iAtom.getPredicate());
               rAtom.addTerm(eventCount);

               // our return fact set
               returnFactSet = new IAFactSet();

               // If the term was a variable just add the true fact we
               // created
               if (iAtom.isLogicalVariable(1, "integer")) {
                  returnFactSet.add(rAtom);

               // otherwise we need to check if the one and only one term
               // in the fact we created matches the value sent in the
               // binding String and add the fact if we have a match
               } else {
                  if (iAtom.getIntegerTerm(1) == rAtom.getIntegerTerm(1) ) {
                     returnFactSet.add(rAtom);
                  }
               }
               return returnFactSet;
            }
            default:
               throw new IAError(answerQueryError,
                      "Unknown Token seen in AnswerQuery!");
         }

      } catch (Exception e) {
          throw new IAError(answerQueryError,
                "StockAdapter:answerQuery() " + e.getMessage());
      }

   }

get The answerQuery() adapter method is the "routing" routine for any predicates that are associated with registered non-boolean sensors. Note that in this example the code is presented "inline" instead of branching to a internal routine for processing. This may be appropriate for small routines.

Figure 4-22. StockAdapter. testCondition()

   public boolean testCondition(IAProcToken token, IAEventHeader hdr, String binding)
                  throws IAError {

      IAAtom iAtom;

      try {

        iAtom = new IAAtom();
        iAtom.setKIF(binding);

        switch(token.intValue()) {
          case StockCompare_Token: {
            return stockPriceCompare(iAtom);
          }
          case IntegerCompare_Token: {
            return integerCompare(iAtom);
          }
          default:
            throw new IAError(testConditionError,
                     "Unknown Token seen in Test Condition!");
        }

      } catch (Exception e) {
          throw new IAError(testConditionError,
              "StockAdapter:testCondition() " + e.getMessage());
      }

   }

The testCondition() adapter method is the "routing" routine for any predicates that are associated with registered boolean sensors. The routine uses the token to determine which internal routine is to be called to satisfy the request. The parameter "binding" contains a string in KIF format. This is used to create an atom for passing to internal routines. The internal routines can then interrogate the atom for specific terms they require.

Figure 4-23. StockAdapter. Other functions


   // -----------------------------------------------------
   // Other functions as required.....
   // -----------------------------------------------------
   .
   .
   .

The remainder of the functions are internal, specific to the Stock Adapter, and are not discussed. The details can be found by reading the source file (provided with the sample).

Figure 4-24. StockAdapter. Data areas

   // -----------------------------------------------------
   // Java Class Private Data Area:
   // -----------------------------------------------------

   // Tokens for our eff/sen routines.
   final static private int StockAdapterMonitorClass_Token = 1;
   final static private int StockAdapterURL_Token = 2;
   final static private int StockAdapterParserClass_Token = 3;
   final static private int StockMonitor_Token = 4;
   final static private int StockCompare_Token = 5;
   final static private int StockAdapterShow_Token = 6;
   final static private int GetStockEventCount_Token = 7;
   final static private int IntegerCompare_Token = 8;
   final static private int StockAdapterProxy_Token = 9;

   // Our monitor and parser objects.
   private MonitorSC      monitorObj;
   private ParseSC        parseObj;

   boolean                started = false;
   int                    eventCount = 0;
}  // end of java class StockAdapter

The remainder of the adapter contains state data and constants. The list of constants represent the tokens used to associated the registered routines with the internal routines. The remainder of the state data is specific to the implementation of the Stock Adapter.

MonitorThread.java

MonitorThread.java contains an important part of the Stock Adapter, the code used to generate a trigger event. MonitorThread.java is designed to run as a thread and generates an StockAdapter:StockPriceEvents when new stock prices are obtained. The thread is started by a call to the StockMonitor() effector.

Figure 4-25. notify() invocation, part 1. This code example shows the first part of the necessary setup before calling IAAdapter::notify().

   public void run() {

      String      key, keyValue;
      IAAtom      myAtom = null;
      IAFactSet   myFactSet = null;
      Properties  listOfFacts = null;

      // Connect to our intended source of information.
      // This should be indicated in the URL.
      // Obtain our page of HTML data.
      String rawHTML = monitorObj.startMonitor(stockSymbols);

      // Parse the data by passing it to our parseObj.
      try {
         listOfFacts = parseObj.parseHTML(stockSymbols, rawHTML);
      } catch (Exception e) {
         System.out.println("MonitorThread(): Exception while parsing. " + e.getMessage());
         Thread ct = Thread.currentThread();
         ct.stop();
      }


Figure 4-26. notify() invocation, part 2. This code example shows the remainder of the necessary setup and the actual invocation of IAAdapter::notify().

      // Create a notification event if we were able to parse the data

      StringTokenizer st = new StringTokenizer(stockSymbols);

      // (1) Create an atom for use in building each fact
      try {
         myAtom = new IAAtom();
         myAtom.setPredicate("StockPrice");
      } catch (Exception e) {
         System.out.println("MonitorThread(): Exception while creating atom. " + e.getMessage());
         Thread ct = Thread.currentThread();
         ct.stop();
      }

      // (2) Create a fact set
      try {
         myFactSet = new IAFactSet();
      } catch (Exception e) {
         System.out.println("MonitorThread(): Exception while creating fact. " + e.getMessage());
         Thread ct = Thread.currentThread();
         ct.stop();
      }

      // (3) Add each stock symbol and quote as a fact to our fact set to be
      //     passed on the TriggerEvent
      try {
        while(st.hasMoreTokens()) {

           // Obtain the key/value to add to our atom.
           key = st.nextToken();
           keyValue = listOfFacts.getProperty(key);

           // (3a) Clear any current atom terms.
           myAtom.setTerms("");

           // (3b) Build our atom.
           myAtom.addTerm(key);
           myAtom.addTerm(keyValue);

           // (3c) Add it to the current fact set.
           myFactSet.add(myAtom);

        }
      } catch (Exception e) {
         System.out.println("MonitorThread(): Exception while building atom. " + e.getMessage());
         Thread ct = Thread.currentThread();
         ct.stop();
      }

      // (4) Now notify the agent of our StockPriceEvent
      try {
         IATriggerEvent stockPriceEvent = new IATriggerEvent();
         stockPriceEvent.setType("StockPriceEvent");
         stockPriceEvent.setFactSet(myFactSet);
         stockAdapter.notify(stockPriceEvent);
      } catch (Exception e) {
         // if StockAdapter is now not started, an exception on notify is expected
         if (stockAdapter.isStarted())
           {
             System.out.println("MonitorThread(): Exception while performing notify. " + e.getMessage());
           }
      }

   }



Generating a notify event in the Stock Adapter requires several steps.

(1) Create an atom and initialize the predicate

The atom (class IAAtom) is used to represent information. Each atom consists of a predicate name and a list of terms. In this case the predicate name is called StockPrice and the list of terms is null.

(2) Create a fact set

The fact set (class IAFactSet) serves as a container for information that is made available to the agent. Fact sets contain atoms.

(3) Load the fact set

While information is available, atoms are created to represent the fact and the atom is added to the fact set.

(3a) Clear the atoms terms

Sets the term list of the atom to null. The predicate name remains unaltered.

(3b) Add the terms

Sets the terms to the specified values. The terms are added in order.

(3c) Add the atom to the fact set

The atom is then added to the fact set. Multiple atoms are allowed in a single fact set.

(4) Notify the agent

A call to the adapters notify()method is made to generate the new event. The call requires an IATriggerEvent object as input. The domain specific eventType must be set on the IATriggerEvent before it is sent on the notify. In this case the eventType is StockPriceEvent. Then we set the fact set to the fact set we created for this event. Events can be generated without a factset, but in our example the generation of an event without supporting data (the actual stock symbols and prices) would not be appropriate.

Things to Watch For

Miscellaneous classes

The Stock Adapter contains several classes that are specific to the functioning of the Stock Adapter. They are not studied here in detail as they do not pertain to "adapters in general". For additional detail, study the sample code.

ParseSC

ParseSC is the "super class" from which the HTML parser is derived. The use of this class makes possible the reference to any class derived from ParseSC as an instance of ParseSC (i.e. polymorphism). This is required because the Stock Adapter allows the dynamic loading of parser classes.

ParseQuoteYahooCom

ParseQuoteYahooCom is a class that extends ParseSC. It is designed to parse HTML obtained from a single web site.

MonitorSC

MonitorSC is the "super class" from which the monitor class is derived. The use of this class makes possible the reference to any class derived from MonitorSC as an instance of MonitorSC (i.e. polymorphism). This is required because the Stock Adapter allows the dynamic loading of monitor classes.

QuoteYahooCom

QuoteYahooCom is a class that extends MonitorSC. It is responsible for connecting to and obtaining the raw HTML page.

StockClassLoader

StockClassLoader is responsible for loading Java classes.


[ Top of Page | Previous Page | Next Page | Table of Contents | Index ]