IBM Agent Building Environment Developer's Toolkit


Chapter 3. Agent Server Sample


The Purpose of the Agent Server Sample

This sample is intended to illustrate many of the application interfaces for using Agent Building Environment Developer's Toolkit However, there are many different configurations and options for using Agent Building Environment Developer's Toolkit and more may be added, calling for additional such illustrations. Therefore this illustration eventually may be only one of possibly several that demonstrate some of the methods of composing the components of Agent Building Environment Developer's Toolkit for the requirements of particular applications.

This particular sample illustrates Agent Building Environment Developer's Toolkit in a single server environment. It focuses primarily on that server and the use of Agent Building Environment Developer's Toolkit to assist in the work of the application agent. A rudimentary, command line driven client program is provided only to make the sample complete. The real client for this application would probably be a graphic or web browser interface, however, because the details of a user-friendly client are not the focus of this sample, the detailed concentration is upon the functions of the server.

Although this sample also previews some of the general aspects of writing an Agent Building Environment Developer's Toolkit adapter, this is not the primary focus of the sample. Rather, you should read Chapter 4. "Writing an Adapter" for a more complete tutorial on writing an adapter.


The Client Application and Commands

The specific client command interface is not particularly optimal, but is only an example to illustrate the use of Agent Building Environment Developer's Toolkit in the server for the clients. In this respect the details of the client command interface is not very important. However, it is described to allow you to run and experiment with the sample, as well as to follow the server logic for processing the commands.

The application allows clients to establish periodic reminders for events that are defined by the client. These events might be meetings, birthdays, doctor appointments, etc.. For simplicity, the current sample only allows for yearly reminders, but it could be easily extended to handle monthly, weekly, daily, or even smaller periods, as well as non-periodic reminders. The client can also indicate the number of days (count) that the reminders are to be provided before the event. For example, you can specify a count of 3 to cause reminders to be provided on three successive days for a particular event. In this case you would get a reminders (Email or reminder messages) on 5/23, 5/24, and 5/25 for a 5/25 event.

Here is an example of PUT command that creates a new reminder:

     PUT:Janet:ID$000$typeOfEvent=Birthday#
     nameOfEvent=My Birthday#month=March#day=10#year=1998#
     count=5#via=E-Mail#user=Janet#emailAddr=Janet@jansaddr.com

The line breaks in the above example are only to fit a single long line into this page.

Although the parameters have associated keys (typeOfEvent, nameOfEvent etc.), the keys are for documentation use only and the parameters are interpreted by position. PUT identifies the command (put a new reminder). The syntax uses colon, dollar and equal as separators. The colon separates the command from the client name (userid) and, where additional information follows , it separates the client name from that which follows. Dollar sign separators are used by the PUT and MODIFY commands to bracket the reminder id.

You can enter any string value for typeOfEvent and nameOfEvent. The user=value is used as a greeting in the resulting reminder message. By specifying "E-Mail" in the via= position, you cause the reminder message that results when the reminder comes due to be presented as an email message. If you specify anything else for the via value, you must explicitly fetch your reminder messages from the server, as explained below. If you specify E-Mail, you must also specify an email address in the last parameter position.

The server retains the original input that the client provides for creating a reminder. This is used as returned data for the GET command.

For the PUT command, the reminder id is 000 on input as a positional place holder. The server assigns and fills in a reminder id in this position. This identifier is returned on the GET command and used on the MODIFY and REMOVE commands to specifically identify the reminder.

The GET command is used to retrieve existing reminders for a client. Following is an example:

     GET:Janet

You get back a string of reminders, each of which is separated by a semicolon. The format of the returned string is as originally submitted by the PUT command (or as modified by a MODIFY command), but with the reminder ID filled in. The reminder ID is used in a subsequent MODIFY or REMOVE command. Manually keying in IDs in the client is awkward, but in a real application this would be avoided by providing a graphic interface program in the client. The client program displays the returned reminders for you.

The MODIFY command is similar to the PUT command, except that a reminder ID is provided. The REMOVE command also provides a reminder ID (without the ID key and dollar bracketing).

There is also a set of commands for viewing reminder messages that result from maturing reminders, that is reminders that have come due. Each of these command names contain the string "MSGS" to distinguish them from the reminder commands that were described above. The format of the reminder messages that are stored when a reminder matures is illustrated by the following example:

     n 002053853950002 05/29/97 15:31 EST This is the message text

A blank delimiter is used to separate the tokens (up to the message text itself). n is a status code for "new" (or message not read). The other possible status code is o, meaning "old" (or has been read). The long number string that follows the status is the unique message id that is assigned by the reminder server. This is followed by the date that the message was produced, the time it was produced and the time zone. Finally there is the message text itself.

Client commands that work with these messages start out the same as the reminder commands that were discussed earlier:

     GETMSGS:Janet

When a message command addresses specific message according to their id(s), the format is:

     DELMSGS:Janet:0020538539950002,0021483999229442

Messages are initially created (by the Message Adapter of the reminder server) and retrieved (by the GETMSGSNOMARK or GETMSGS commands) and are presented with a status of n. After retrieving the messages for a client, the GETMSGS command changes the status of all messages stored for the client to o. This allows for distinguishing these old messages from new ones that are added later. The GETMSGSNOMARK retrieves the messages for the client without changing their status in storage. MARKMSGSASNEW allows a client to change the status of specific messages (by ID) from o to n (without retrieving them again). DELMSGS deletes specific messages from storage. Finally, STATUSMSGS queries the status of all messages for a client. It returns the following:

     STATUSMSGS:nnn

where nnn is a code: 220 meaning the client has at least 1 message with a status of n, 500 meaning the client has only message marked with a status of o, 221 meaning the client has no messages at all, or 501 meaning an error has occurred.

Each command returns the command name followed by a colon followed by a string. For example:

     DELMSGS:220

The 220 is a code for successful completion. In the case of a successful GETMSGS or GETMSGSNOMARK, the return string that follows the colon is the set of messages that are retrieved. These messages have the format shown above (status followed by message ID etc.). Each message in this string is separated by a newline character, \n.

You can use the ? command to get help on the command syntax.

Use the quit command to end the client and command entry.

The Java code for the client is included in "Client Code for Reminder Sample Application".


Overview of the RemAgent Server Sample

Before offering an examination of the reminder server in detail by looking at the Java code, this section examines the concepts that are involved. If this is all that you require at this time, you can bypass the detail of the Java code discussion that follows. If you intend to go for the detailed code description, you should nevertheless read this overview to firm up the concepts.

Figure 3-1 gives an overview of the RemAgent Server. You can see represented the components of Agent Building Environment Developer's Toolkit: agent control, an engine, a library, and some adapters. Around these components is the application. In the current case, the application is a reminder agent server. This reminder agent server is married to the agent components from Agent Building Environment Developer's Toolkit by sharing a server environment with Agent Building Environment Developer's Toolkit and by the application's use of the Agent Building Environment Developer's Toolkit application interfaces. This sample illustrates the use of those interfaces.

Figure 3-1. Overview of Reminder Server


The figure represents the various application interfaces by broad delineation lines. In some cases, the same application programming interface is used internally by the Agent Building Environment Developer's Toolkit components, such as the engine's use of the library application programming interface to load rules. Dotted lines represent the boundaries of the Agent Building Environment Developer's Toolkit component parts. Where it is possible to extend Agent Building Environment Developer's Toolkit to include new Agent Building Environment Developer's Toolkit components, Agent Building Environment Developer's Toolkit also includes the application programming interface(s) that are required for accomplishing this extension. The Message Adapter in this sample utilizes the Agent Building Environment Developer's Toolkit adapter application programming interface for this purpose.

The Agent Building Environment Developer's Toolkit Agent Control (agent application programming interface) is used to configure the agent and to control when and which rules are to be loaded for the engine to do inferencing on behalf of the application. The application programming interface for using Agent Control is available through the IAAgent object, described in Chapter 2. "Agent Reference" of the Components and Adapter Reference book. The outer layer of the reminder agent becomes a concrete Agent Building Environment Developer's Toolkit IAAgent object by inheriting from the abstract object, IAAgent, that is supplied by Agent Building Environment Developer's Toolkit

The Agent Building Environment Developer's Toolkit Library application programming interface is used here in three ways:

  1. By the application to add and maintain rules or to examine messages from reminders that have come due.

  2. By the Agent Building Environment Developer's Toolkit engine to load rules (conduct sets) for inferencing based on application directives through the Agent Control (application programming interface).

  3. By the Message Adapter to record messages for application retrieval where these messages each represent a reminder that has come due.
The Agent Building Environment Developer's Toolkit Agent Control provides the high level control interface, the Agent Building Environment Developer's Toolkit Library provides the rule storage and retrieval interface, the Agent Building Environment Developer's Toolkit engine loads and interprets the loaded rules.

The Agent Building Environment Developer's Toolkit adapters in this sample are the Time Adapter and Email Adapter as supplied and unmodified from the set of adapters provided by Agent Building Environment Developer's Toolkit. In addition there is a Message Adapter that was written especially for this sample reminder application. It was written in C++ using the Agent Building Environment Developer's Toolkit adapter application programming interface. The reason for using C++ rather than Java was for better performance, although this motivation is reduced as Agent Building Environment Developer's Toolkit supports Java 1.1.

The Agent Building Environment Developer's Toolkit engine is represented here interpreting rules written by the reminder server. Refer to Figure 3-2. There is a set of rules for each reminder client that has defined one or more reminders. Each such reminder is represented by a set of rules. All reminders for a single client are stored together in a single RuleSet in the Agent Building Environment Developer's Toolkit Library by the reminder application. At appropriate times these RuleSets are loaded by the engine for inferencing.

Note: RuleSet is a shortened form for IALibInferenceRuleSet which is the actual name of the Library object/class. Such shortened form names are used for ease of reading in much of this documentation.

Figure 3-2. Reminder Rules and Reminder Metadata


Conceptually there are only a couple of rules needed by the reminder application: to set alarms for reminders (Rule 1) and either store a message (Rule 2) or send Email (Rule 3) when the reminder alarm goes off (pops). (We will see later that Rule 1 is really 3 rules for this application, but consider it a single rule for the current conceptual discussion).

It is common and expected that adapters have the primary control over rule "firing" through trigger events that the adapters generate. The rules are conditioned on these events. That is, the rules have antecedents that are validated through the facts that originate from adapter generated events. In this sample, there is only one such adapter generated event, the alarm pop from the Time Adapter that conditions Rule 2 or Rule 3 to fire. Rule 1 is conditioned primarily by the loading or changing of the rules (conduct sets). That is, Rule 1 basically says:

     If a particular client's rules (representing his reminders) are
     loaded or have been changed,
     then use the information provided by that client (incorporated
     into his reminder rules) to set alarms for those reminders.

The alarms are managed by the Time Adapter. When such rules "fire" successfully, the result is for an effector (in the "then" portion (consequent) of the above illustrated rule) to invoke the Time Adapter to cause that adapter to set (create) the appropriate alarm. When such alarms "pop", the Time Adapter generates an alarm pop event. This event provides facts that properly condition the "if" portion (consequent) of Rule 2 or Rule 3. This causes the "then" (consequent) portion of Rule 2 or 3 to fire, consisting of effectors to the Email Adapter to send email for the reminder or to the Message Adapter to store a reminder message in the Library.

RemAgent - Main Thread

Numbers such as: (1), (2)... in the remainder of this chapter refer to the numbered items in Figure 3-1.

RemAgent - Initialization of the Agent

The RemAgent Class, represented by the box in the top left corner of Figure 3-1, is the top Java class for the reminder agent. It handles the initialization of the Agent Building Environment Developer's Toolkit facilities that assist the application in its agent work. It also initializes the application server itself.

The RemAgent Class executes at the top of the reminder server process, basically as the default Java thread. All of the RemAgent class code runs under this thread, including the initialization work that it invokes through the various Agent Building Environment Developer's Toolkit application programming interfaces. The initialization of Agent Building Environment Developer's Toolkit itself runs under this same thread. This includes everything discussed in this section.

RemAgent also inherits from the Agent Building Environment Developer's Toolkit IAAgent abstract class. This means that it can use, extend, and incorporate the methods of that abstract class.

RemAgent - Agent Building Environment Developer's Toolkit Agent Configuration

RemAgent invokes a its own method (1) to set up the agent for operation. The first aspect of this setup is to configure the agent (2). Some of this agent configuration work is driven from a configuration file where configuration choices are specified. This file is very similar to the Agent Building Environment Developer's Toolkit default configuration file that is described in "Configuration File for the Sample Agent Programs". Following shows the contents of the configuration file used by this sample:

library file iaglibf . . 2

adapter Time iagatime

adapter SampMail sampmail

adapter Messages iagamess file iaglibf . . 2

Note: The . (dot) parameters convert implicitly to the current (drive and) directory. The configuration file provides information such as where to find the top Collector for this application's instance of the Agent Building Environment Developer's Toolkit Library where the client's reminder/rules are stored. This top Collector is the anchor for all of the Agent Building Environment Developer's Toolkit Library's persistent storage. It also specifies information used to initialize each Agent Building Environment Developer's Toolkit adapter that is required by this application. This information could have been "hard-coded", but having it in a file gives it more flexibility and extendibility.

For each type of Agent Building Environment Developer's Toolkit component (engine, library, adapter) there is an IAAgent method to invoke to cause an instance of that component to be configured: addEngine, addAdapter, etc.. Setup Agent invokes these methods with the appropriate parameters (many from the configuration file). Each of these agent configuration methods causes the construction of Agent Building Environment Developer's Toolkit component object instances: an IAEngine object, IAAdapter objects, and an IALibrary object for use by Agent Building Environment Developer's Toolkit and the reminder server application. The IAAdapter and IALibrary objects and their application programming interfaces (methods) are described in Chapter 3. "Adapter Reference Material" and Chapter 4. "Library Reference" respectively of the Components and Adapter Reference book. These are the application programming interfaces that applications, including the reminder application, use to allow Agent Building Environment Developer's Toolkit to assist in the work of the agent. Because the application has no need for direct use of the IAEngine application programming interface, it is not yet included in this documentation.

There are a couple of ways for a particular adapter to do its initialization work:

  1. When the adapter is configured by the agent (refer to addAdapter() above). That is, initialization can be done in the coding of the adapter's constructor. addAdapter() includes a provision for application-supplied parameters, which are passed on to the adapter by Agent Control as parameters on the IAAdapter constructor. These parameters can be used to input options to the adapter for its initialization.

  2. When the adapter is started through the start() method for IAAdapter.
The Message Adapter uses the first method of initialization. The main work for the initialization of the Message Adapter is to initialize the Library (11) (similar to the application's initialization of it as described in the next section). To do this, a set of parameters are passed to the adapter as described in 1 above. These parameters include such things as a map to the Agent Building Environment Developer's Toolkit Library's top Collector. This information is obtained by the application from its configuration file. The next section further describes the initialization of the Library that is required for each "environment" that uses it.

The result of initialization of the Library by the Message Adapter is an IALibrary object. This object is retained by the adapter in the IAAdapter object attributes.

At the completion of this configuration initialization work, the aforementioned objects are built and the agent is initialized, but the agent is not started. In our example there is yet another initialization activity to complete.

RemAgent - Initializing the Agent Building Environment Developer's Toolkit Library for Application Use

When the Agent Building Environment Developer's Toolkit Library component is configured for Agent Building Environment Developer's Toolkit, as described above, it is made available for use by the Agent Building Environment Developer's Toolkit engine to load rules etc., but this does not make the Library available for use by the application itself. We described earlier that an IALibrary object is instantiated when we configure the Library for the agent via the addLibrary() method of IAAdapter. However, that object is used internally by the engine and is not presented for use by the application.

For the application to use the Agent Building Environment Developer's Toolkit Library directly to store, retrieve and change rules and other information in the Library, it must instantiate its own IALibrary object. Then it invokes an attachImplementation() method on that IALibrary object to bind that object to a specific library implementation, namely the same one that the engine is to use for retrieving rules, and, as we will see later, the same library that the Message Adapter uses to store reminder messages. Once the Agent Building Environment Developer's Toolkit Library initialized (3) in this manner, it is ready for application use.

The Agent Building Environment Developer's Toolkit Library is potentially portable and shakable, as illustrated by this particular application, where it is accessed from the agents engine for rule retrieval; by the application for storing and examining rules, as well as retrieving reminder messages; and by an adapter for storing reminder messages. Each of these uses of the Library involves a separate IALibrary object instantiation. However, because each of these IALibrary objects is instantiated from the same set of parameters, they all bind to and share the same physical library implementation and persistent objects. They access the library through separate IALibrary objects because, in this particular application, the three instances of library access do not require sharing the same memory objects, but only the same persistent objects. Also, Agent Building Environment Developer's Toolkit does not expose its own instance of IALibrary (the one created internally from an addLibrary() method on the IAAdapter during agent configuration). Furthermore, using separate IALibrary objects retains a model which has the possibility for sharing the Agent Building Environment Developer's Toolkit Library from separate processes and environments - the most general case of sharing the library (not illustrated here).

Now that the IALibrary object is instantiated and prepared properly, the application is free to use it to instantiate additional Agent Building Environment Developer's Toolkit Library objects such as are needed for the application to use the library for storing rules etc..

RemAgent - The Special Case of Non-Persistent Alarms

This particular sample server application was written with the assumption that Time Adapter alarms are not persistent. At the time that it was written, persistent alarms were not available. With non-persistent alarms, when a rule fires that causes an alarm to be set (through a Time Adapter effector), that alarm continues to exist, within the scope of its particular definition, only as long as the server continues to run. However, should the server be terminated or shutdown, such non-persistent alarms would be lost.

Note: The option to make alarms persistent, requiring the Time Adapter to record them in persistent storage, is included with Agent Building Environment Developer's Toolkit, but is not illustrated in this sample application.

In lieu of using the option for persistent alarms (where the Time Adapter handles the persistent alarms problem), this sample application reestablishes alarms by running the rules that initially caused the alarms to be set (19). It does this each time the server is re-started. First it must determine which clients have rulesets (active reminders), then it must cause the agent to load those rules. The first rule for each reminder does essentially the following:

    "If the agent is just starting up, set an alarm for this reminder".

A rule conditioned in this way on an "agent-starting" event gets "fired" when the application starts the agent (discussed later). When these rules have been loaded, the needed alarms get set just as they had been when the server was last up and running.

Therefore, the application uses the Agent Building Environment Developer's Toolkit Library to find all clients that have active RuleSets so that it can invoke Agent Control to load those RuleSets.

Note: For the Agent Building Environment Developer's Toolkit engine, a RuleSet, along with some RuleSet-level metadata that is stored with it, is referred to by the name Conduct Set. By requesting that a Conduct Set be loaded (5), and (later) starting the agent (9) (causing the agent-starting event (10) that properly conditions the rules to fire), all alarms for the existing rules/reminders are reestablished for the current server start-up. Requesting that a Conduct Set be loaded means that the associated rules are available for inferencing, which is one step toward reestablishing alarms. This causes the engine to actually load the Conduct Set (6 and 7). Then the subsequent Agent Start-up event (10), on which the reminder rules depend, completes the rule conditions required for causing alarms to be set (19).

RemAgent - Agent Building Environment Developer's Toolkit Library Objects for the Reminder Server

To understand this better, it is appropriate to discuss at a high level how the Library objects are used by this application to store rules. A Collector (Library) object is used to represent a user or client in the reminder application. The Collector name is the same as the name of the client. There is one (Library) RuleSet object for each client (Collector) that has any active reminders. Each reminder requires just a few rules. Even if a client has more than one active reminder, the associated rules for all of that client's reminders are stored in the same RuleSet object. This RuleSet has the same name as the name of the associated client and Collector. Further, it is contained in the associated Client Collector. That is, a Collector is a Library object for collecting or grouping objects - in this case, all the stuff for a particular client.

You can see in Figure 3-1 that all of the client Collectors are also contained in a top Collector object. From this you can see that Collectors are not only used to collect or group the objects such as RuleSets for a client, but also to collect or group the clients (their Collectors). The top Collector is the anchor point for Library.

You can also see in the figure that, in addition to a RuleSet, a Client Collector may also contain one or more Reminder Messages. These objects are stored by the Message Adapter (23), discussed later. If a Client Collector no longer has any reminders, its RuleSet is empty and is deleted. If it also has no Reminder Messages, the Client Collector is also deleted.

Therefore, the Initial Agent CS Load routine (4) must find all Client Collector objects in the Library that also have RuleSet objects. Where these are found, it tells the agent/engine to Load the Conduct Set (5) for each of these Clients. The Conduct Set has the same name as the name of the client and the name of the client's RuleSet and Collector.

RemAgent - Start-up of the Agent Building Environment Developer's Toolkit Agent

After the agent has been initialized, RemAgent starts the Agent Building Environment Developer's Toolkit agent (8). There is an appropriate IAAgent method (start) for this. As a result of this, Agent Control starts the Agent Building Environment Developer's Toolkit engine by starting a new thread that is dedicated to that engine. This thread waits on event queues. Then Agent Control generates a special "Agent-Starting" event. The engine processes this event by inferencing on its current Conduct Sets (loaded during initialization). The reminder rules in these conduct sets are be satisfied by the Agent-Starting fact that comes from this Agent-Starting event, causing effectors to be invoked to the Time Adapter for starting (creating) alarms.

After the completion of the Agent-Starting event processing by the engine, Agent Control starts each adapter that has been configured (refer to "RemAgent - Agent Building Environment Developer's Toolkit Agent Configuration") by invoking the associated IAAdapter start() method on each adapter. This causes adapters to do their own initialization. Each adapter that generates trigger events is expected to start its own thread for that purpose. In the current application, this applies only to the Time Adapter. Other adapters, such as the Message Adapter in the current application, which do not generate trigger events, need not establish their own threads. They can run under the engine's thread when called to process a sensor or an effector (callback processing). Trigger events should never be initiated by an adapter while running under the engine's callback thread.

After all adapters have been started, return is to the application (RemAgent).

RemAgent - Initialization of the Server Object

Finally, a new Java object and separate thread, called Server, is constructed by RemAgent (12).

Server - Open Communications Thread

The Server object establishes a TCP/IP socket and does an accept on the socket. When a client connects with the server, the Server instantiates a Connection object (13). Server does similarly for each such client connection. Because the Connection object instantiations are also separate threads, they can process asynchronously.

In summary, the server application has established a main object/thread (RemAgent) for the initialization, a Server Object/thread for accepting new connections, and a Connection object/thread to service each client connection. Obviously most of the work for clients is done by the Connection objects, as discussed in the next section.

Connection - New Connection Handler Thread

Each Connection reads and parses its input from TCP/IP. Based on the client request, there is a main split in the processing: either process reminders (14) or just view messages (24) that have been stored from reminders that have come due (in lieu of Email).

Connection - Maintaining Application Reminders

For reminder processing there is some basic setup required This is common for all reminder commands. This setup uses the Agent Building Environment Developer's Toolkit Library (15) to establish the client's Collector object and the associated RuleSet. If the client has no reminders or messages stored, the client will have no persistent Collector or RuleSet (empty ones are not retained persistently). In this case Collector and RuleSet (empty) memory objects are initialized so that the client can potentially store a new reminder. If a Collector and RuleSet already exist for the client, they are retrieved from the Agent Building Environment Developer's Toolkit Library persistent storage and instantiated into memory objects as a part of this initial setup for reminder processing.

Using Agent Building Environment Developer's Toolkit Library Object Metadata

In addition to storing the rules for a clients reminders in the client's RuleSet, the reminder application also takes advantage of an Agent Building Environment Developer's Toolkit Library feature that allows for storing of application-defined metadata with the Library objects. The reminder application illustrates the use of three different types of Library object metadata:

This is a good illustration of the flexibility of object metadata in the Agent Building Environment Developer's Toolkit Library,

Note: You can also have multiple instances of each type of metadata for an object, each with a distinguishing name specified by the originating application. By convention the name used by the engine ('0') for its RuleSet metadata is reserved.

The GET Command

For the GET command, the server simply gathers information about all existing reminders for the requesting client. If the client has existing reminders, this retrieval consists of simply gathering the descriptive metadata string for each reminder. The metadata string for each reminder is stored with the first Rule of the set of Rules for a reminder (refer Figure 3-2). The concatenation of these strings is returned to the client.

The PUT Command

This command stores a new reminder for the client. The reminder is stored as a set of Rule objects in the client's RuleSet, as described earlier. Further, the descriptor string submitted by the client to describe the rules is also stored with the first Rule of the reminder in the form of Rule-level metadata (as used above for return to the client in the GET command).

For accomplishing this, the PUT processing must parse the command string and plug its parameter values into the reminder rule skeletons, specializing the base rules to the requirements of the current reminder. Once the reminder rules are completed in this way, they are added to the clients RuleSet.

Note: All of the Rules for a particular reminder are stored contiguously into the containing client RuleSet. The PUT command string itself is added to the first Rule of the set of Rules for the new reminder in the form of a Rule metadata string.

PUT processing must also create a new, unique id for the reminder. This id is used in several ways including the unique identification of the alarms that are to be set by the Time Adapter for this reminder. This id is used to allow the client MODIFY and REMOVE commands to specify a target reminder (using the reminder ids returned by a GET command).

Then the PUT command processing stores the new or updated Client RuleSet in the Agent Building Environment Developer's Toolkit Library (15).

Finally, Agent Control is told, through Dynamic CS Load (16) to load (17) the client Conduct Set (the new or changed RuleSet and its metadata) into the Agent Building Environment Developer's Toolkit engine's storage for inferencing. This Dynamic CS Load processing includes the following actions (by the agent):

Note: In all of the above Dynamic CS Load descriptions, the client is identified to the agent, engine, and adapters through an identifier called a selector. A selector is an identifier for potentially scoping Conduct Sets, trigger events, and sensor/effector calls to a particular (e.g. client) subset of a larger set of objects. Following are some of the potential uses of a selector:

Just as for the GET command, the PUT command returns all of the newly established Rule metadata strings for the client's existing reminders as a response to the PUT command.

The MODIFY Command

The MODIFY command is very similar to the PUT command. It uses the reminder ID that is a parameter to for the MODIFY command to find the rules (and metadata) that were established for an existing reminder. Then is deletes the identified reminder's Rules and associated metadata. Finally it does the same processing as PUT to replace the deleted reminder information with new reminder Rules and metadata information. However, instead of generating a new reminder ID (as PUT does), it uses the reminder ID given by the client command input.

The REMOVE Command

The REMOVE command processing is exactly the same for the first part of the MODIFY command. It simply uses the ID provided with the command input to find the associated reminder Rules and metadata information and deletes that information.

Viewing Application Messages

We have examined the first category of client commands, the reminder maintenance commands. The second category of client commands are the commands for viewing reminder messages. When a reminder comes due (matures), it can do one of two things (based on the option chosen in the originating PUT command). It can generate Email or generate Messages. The generation of EMail has been covered in the discussion of the EMail Adapter. This section discusses the second option, the generation of reminder messages. With this option, messages are recorded in the Agent Building Environment Developer's Toolkit Library; then later retrieved by the client. These messages carry a status that can be used to indicate if they have been previously viewed (read).

The format of reminder messages and the syntax/semantics of the reminder message handling commands are discussed above (refer to "The Client Application and Commands").

Recording Messages In the Library

Reminder messages are stored by the Message Adapter (23) when a reminder Rule detects that a reminder alarm has "popped", causing a Message Adapter effector to be invoked. The Message Adapter is written especially for this reminder application to handle the recording of reminders that have come due (matured).

Reminder messages are stored in the Agent Building Environment Developer's Toolkit Library in the form of application-defined (based on client parameters) Collector-level metadata. That is, they are stored as a part of a single string, containing all messages for a single client, and this string is stored in association with the Collector object for the that client. This Collector has the same name as the name of the client. Refer to "Using Agent Building Environment Developer's Toolkit Library Object Metadata" for a discussion of how applications can use Library metadata.

The GETMSGS Command

This command finds the Collector Library object for the specified client, retrieves the associated metadata (25), consisting of a string of messages stored by the Message Adapter, and sends these messages as a response to the requesting client. It also updates the messages metadata as stored in the Library with a status of o (old), which indicates that they have been read (presented to the client).

The GETMSGSNOMARK Command

This command does the same as the GETMSGS command except that it does not change the status of messages as stored in the Library. That is, there is no update of the messages in the Library.

The DELMSGS Command

This command input includes one or more IDs for messages that were previously retrieved. The message IDs are used to find and delete messages in the Collector-level metadata for the client. This command only returns a code to indicate success or failure.

The MARKMSGSASNEW Command

This command allows for marking specific message status as new (n). The message to be marked are listed by ID in the command input. This command only returns a code to indicate success or failure.

The STATUSMSGS Command

This command is a query to determine if any message exist that have not been read (have a status of n). It returns a code to indicate whether new messages exist for the client.


Explanation of the Reminder Server Code

Refer to "Server Code for Reminder Sample Application", where the code is formatted for ease of reading. Each class is formatted and line-numbered separately. In the following discussion, line numbers are expressed as follows (# prefixed):

#12-15

References to particular parts of Figure 3-1 will continue to take the following form (no # prefix):

(14)

where the number corresponds to numbers in that figure.

The code for the client driver is not specifically described in more detail than is covered by :hdref refid=clicom. because it does not particularly illustrate any Agent Building Environment Developer's Toolkit concepts or methods. However, the code is included at :hdref refid=remcl1. for your reference.

The following sections address those Java classes and associated object instances:

RemAgent Object - Initialization

Please refer to :hdref refid=remag1 for the RemAgent Class Java code. RemAgent Class inherits (extends) the IAAgent abstract class (#34) that is provided by Agent Building Environment Developer's Toolkit (refer to Chapter 2. "Agent Reference" of the Components and Adapter Reference book for a description of that abstract object). This makes it possible for RemAgent (1) to invoke the various Agent Building Environment Developer's Toolkit IAAgent methods required to configure (2) an agent and to eventually start (9) that agent. RemAgent (1) also becomes the top object and thread in the Reminder Server. This happens when its main() method invokes its own constructor (#114). The RemAgent constructor invokes its setupAgent() method (#87) to handle the following agent initialization activities:

RemAgent Object - Configuration of Agent Building Environment Developer's Toolkit Components

The agent configuration information is read from a file (#133). See "RemAgent - Agent Building Environment Developer's Toolkit Agent Configuration" for the contents of this file. This configuration information drives the three agent configuration methods of RemAgent, addEngine(), addLibrary(), and addAdapter(). They are invoked from setupAgent() (#135-7).

The addEngine() method of RemAgent (#149) invokes the addEngine() method (#155) (same method name, but different signature), which is a method of the super class (IAAgent). Because the current Agent Building Environment Developer's Toolkit has only a single engine, no configuration information is needed to configure that engine, so only the standard information is specified (RAISE type and its dll). This returns a handle that can be used as parameters to other IAAgent methods to identify the engine.

Similarly, the addLibrary() method of RemAgent (#164) invokes the addLibrary() method (#216) of IAAgent. In this case configuration information is used (passed as a parameter) to build the arguments for the addLibrary() at #216. Lines #189-212 tokenize and extract this information. This information establishes the particular implementation (only one exists at this time) of the Agent Building Environment Developer's Toolkit Library and the map (location) of the root of the Library in persistent storage.

The remainder of the addLibrary() code (#222-244) of RemAgent apples to and is discussed in the section that follows.

Finally, the addAdapter() method of RemAgent (#269) invokes the addAdapter() method (#330) of IAAgent. Unlike the adding of the engine and library, the Reminder application requires more than one adapter. This is the first example here of configuring multiple instances of an Agent Building Environment Developer's Toolkit component. The configuration calls for 3 adapters so there is an iteration loop (#292-334) for this. Again the configuration information is tokenized (#294-326), passed, and used to build the arguments for the addAdapter() method at #330.

This particular adapter configuration also illustrates the capability and necessity for passing parameters from the application to the adapter. In particular, the Reminder application passes the Library configuration information to the Message Adapter so that the Message Adapter can also initialize the Library for its own use. Recall from "Overview of the RemAgent Server Sample" that the Reminder server uses the Library in three somewhat independent ways: 1) application - to store/change reminder rules, 2) engine - to retrieve and interpret reminder rules, and 3) message adapter - to store reminder messages. Each of these works independently with the Library and must initialize the Library interface through the IALibrary object for its own purposes. Because basically the same information is used in the Library initialization from each of these three uses, the result is a sharing of the very same Library Implementation and persistent storage objects.

This Initialization of the Library by the Message Adapter is exactly the same as the Library initialization by the RemAgent Application, as discussed in the following section.

RemAgent Object - Initialization of the Library for its Own Use

For the initialization of the Library for use by the Reminder Application (3), we go back to #222-244 in the addLibrary() method of RemAgent. It may help to read the code comment at #222 to reiterate the distinction between initializing the Library for use by the engine and the initialization for use by the application.

First a IALibrary object is constructed (#233).

Note: For the engine's use of the Library, the IALibrary object is constructed and used internally by Agent Building Environment Developer's Toolkit, returning only a handle to the application for representing the Library. IALibrary establishes a Agent Building Environment Developer's Toolkit Library application programming interface for use by applications (and adapters) as described in Chapter 4. "Library Reference" of the Components and Adapter Reference book. It also implicitly constructs a Collector (full name is IALibInferenceCollector) object for the top Collector of the Library. For initialization purposes, the IALibrary method of interest is addImplementation(). This is very similar to the addLibrary() method of IAAgent, with the same parameters and same purpose (refer to previous discussion of addLibrary()). The main difference is that here the application is operating on the IALibrary object directly, rather than configuring the agent to do it for the Agent Building Environment Developer's Toolkit engine (indirect use of the Library).

The getTop() method of IALibrary is used (#243) to obtain the top Collector object for the current Library for use later (in the preloadCS() method of RemAgent (#344)). Then the get() method of the (top) Collector is used to "fill" the Collector object from persistent storage.

Note: The (top) Collector object that is implicitly built by the IALibrary constructor does not implicitly have contents filled in from persistent storage). Collector and other Library object methods are described in Chapter 4. "Library Reference" of the Components and Adapter Reference book.

Having completed the invocations of the RemAgent add...() methods (#135-7), setupAgent() goes on to invoke preloadCS() (#139). The need for loading all existing "conduct sets" each time that the Reminder server is restarted, as accomplished by preloadCS(), is described in "RemAgent - The Special Case of Non-Persistent Alarms" and "RemAgent - Agent Building Environment Developer's Toolkit Library Objects for the Reminder Server".

The preloadCS() method finds all Client Collector objects in the Library that also have RuleSet objects. Where these RuleSets are found, it tells the agent/engine to load the Conduct Set for each of these associated clients. For the Reminder Application, a Conduct Set has the same name as the associated client and that client's RuleSet.

Lines #361-71 is a good illustration of how to find a second-level (client) Collector under the top Collector in the Library. The Collector methods allow the use of a cursor to move through the ContentsElements (short for IALibInferenceContentsElement) that the Collector contains. Once a Collector is "filled" (via a Collector get() method), it contains a ContentsElement for each of the Library objects that it "contains". These objects are each of a particular type: RuleSet, Collector (where a Collector contains other Collectors), etc.. The Collector methods allow you to move a cursor through the contents of a Collector. This cursor can be set up to position on contained objects of a particular type. In the current case, we are only interested in looking at Collectors contained within the top Collector, so #361 sets up the cursor scope to only position on ContentsElements of type Collector within the current top Collector object. With this scope established, the first contained element found by the firstElement() method (#365) is the first client Collector contained in the top Collector.

Then the name of this client Collector is extracted (#368). Recall that the convention of the Reminder Application is that this client Collector name is also the name of the associated client and the client's RuleSet, as well as the client's Conduct Set. Now we ha ve identified a client Collector, but have not yet instantiated an object for it in memory. This is done by constructing a new Collector (#370) and filling it from persistent storage, using the Collector get() method (#371). Notice that the Collector, as for all other Library objects, are constructed by using a factory method on IALibrary ("new_.........").

Now we have a client Collector object and need to examine its contents (ContentsElements) to see if it has a RuleSet. To do this we set the cursor scope for the new client Collector (#379) to examine only RuleSet objects. Then the Collector find() method (#381) is used to determine if the client Collector actually contains a RuleSet.

Note: The client Collector might only contain reminder messages for which the Rules (in RuleSets) have been deleted, in which case the Collector is kept around only for the client's reminder messages and it is not appropriate to load the Conduct Set for the client. If the find() fails, its boolean false return causes the containing if condition to iterate the for loop (#364), completing the processing of the current client Collector and going on to look for the next client Collector.

Note: In the case of the Reminder Application, there is only one RuleSet for a Client, so once it is found, there is no need to do further cursor positioning to look for more. Also, the find() method includes arguments for doing its own scoping (to RuleSet), not depending on the setCursorElementScope() to do that scoping. Therefore, setCursorElementScope() is not really needed here. However, it does illustrate the sequence required for the more general case where, after finding a element by name, you can go on to look at other contained elements of the same type under control of the established cursor scope.

At #389 a client RuleSet is constructed for the object just found, specifying the "parent" as the client Collector, and the RuleSet name as the same name as the client Collector, established at #368. Now we are ready to tell the engine to do the initial load of the Conduct Set (#397) for the current client. This method requires both the engine and the library handle that was returned when those components were initialized. The third argument, csname, is a path from the top Collector (not including the top Collector itself) to the current (client) Collector, followed by the name of the Conduct Set. In our case this path is simple because there are just two levels of Collectors. It is just the name of the client Collector, which is the same as the name of the client (user name). Also the Conduct Set name is simply that same as that client name (user name) (see #391 where this string is set up). Finally, the fourth argument for loadConductSet() is the selector name. For this application, this name is also the same as the name of the client (user name). There is a lot of flexibility in Agent Building Environment Developer's Toolkit for naming things, but the current example is probably more common, where that flexibility is not required and all names for a particular client's objects match the id of the client itself!

At #405 is the setup to position the top Collector cursor to the next contained client Collector prior to iterating the loop.

RemAgent Object - Agent Building Environment Developer's Toolkit Component Startup

This completes setupAgent() processing invoked at #87, which, when successful, brings us to the agent start (#92) (8). This IAAgent method has a appears simple, but causes quite a bit of agent activity, as indicated by the important description in "RemAgent - Start-up of the Agent Building Environment Developer's Toolkit Agent".

RemAgent Object - Server Object Initialization

Finally, RemAgent constructs another application object/thread, the Server (#100), which goes on as an independent Java thread. It is passed the IALibrary object, which it, in turn, passes to each Connection thread/object the Server object spawns. This IALibrary object is needed by the Connections to access the initialized Library objects where it maintains RuleSets and views Reminder Messages for client Collectors in the Library.

Server Object - Open Communications

Please refer to "Server Class" for the Server class Java code. The Server constructor invoked by RemAgent (above) is shown at #20 in "Server Class". The constructor establishes a port (#7,8,22) and constructs a ServerSocket for that port (#25). Then the Server invokes the start() method for the inherited Thread class which invokes the run() method of the current class.

The Server run method hangs on a the ServerSocket accept() method until an incoming connection occurs. When the accept() completes, we have a new socket connection and the application constructs (#44) one of its Connection objects (13) to handle it. The Connection objects are also separate threads, so they go on asynchronously, as described below.

Connection Objects - Handling Connection Services

Please refer to "Connection Class" for the Java code for the Connection Class of objects. The Connection constructor (#40) is passed the socket and the IALibrary objects. It invokes the getTop() method (#45) of IALibrary to acquire the top Collector. Then it establishes a BufferedReader (#51) for reading from the socket and a PrintWriter (#52) for responding (output) on the socket. It invokes the start() method for the inherited Thread class which invokes the run() method of the current class.

The run() method reads input (in a loop beginning at #103) from the BufferedReader (#106) and tokenizes the first two token of the input (#115-121). The first two tokens are standard for all commands: the command name and the user (client) name. First it does some special processing for reminder type commands, as described below. Then it fans on the various commands that it allows. These commands are described further in "The Client Application and Commands" and "Connection - Maintaining Application Reminders".

Connection Objects - Setup for Reminder Commands

There is a special set up required for reminder commands. These are command names that do not contain the character sequence "MSGS" (which identify the reminder message viewing commands). These reminder commands are isolated (#131) and the setupRulesAccess() method is invoked (#134) to handle the set up that is required for them.

The setupRulesAccess() method (#486) is passed the user (client) name that was already parsed, as well as the entire command string. Basically, this method finds and establishes the client Collector and Ruleset f or the client (15). If no previous reminders exist for the client, an empty RuleSet is constructed. If there are existing reminders for the client, the RuleSet is filled from persistent storage with the Rules that represent those existing reminders..

The code for reading the establishing the client Collector is similar to that discussed above when we initialized all existing Conduct Sets for all clients. However, the current setup code is a little different in that we are setting up for one particular client.

Note that the synchronized block (#490-533) serializes access to the top Collector for multiple Connection threads. We will see similar serializations where needed in this server application.

The cursor scope for the top Collector is establishes that we are only interested in Collector objects (#494) and a client Collector is constructed (#504-5). Following this (#507) is a find() method on the top Collector which looks for the a ContentsELement for the client in the top Collector. If the boolean return on the find indicates (true) that the client is represented in the contents of the top Collector, we are ready to do a get() method (#517) on the client Collector object to fill it from persistent storage. (This is how we actually establish its own contents). But before doing the get we also establish that the client Collector can have Collector-level metadata (this is how reminder messages are stored for the client by the Message Adapter). This is indicated by specifying the application-defined name for that metadata (#515). This name is a one character value that is selected by the definer of the metadata (this application). The name used by the Reminder Application is '1'. In other applications, there might be more than one instance of Collector-level metadata, each with a distinguishing name (for example: 1,2,...A, B..etc.), but here there is only one type of metadata. Its name must be established as a Collector attribute so that the get() method will know to retrieve that metadata with the rest of the Collector information.

For the case where the find() fails, returning boolean false, and where the current command is something other than a GET (#526), it is appropriate to establish a client Collector in persistent storage for the current command to use (for example, to use to store a new RuleSet for a new reminder). This is accomplished by using the put() method on the client Collector memory object (#530). This method uses the current client Collector, which is basically empty, but has some attributes: a name and parent, to establish a similar persistent storage object. For the case where there was no client Collector found and the current command is GET, return code 2 (#526) and exit.

A RuleSet is constructed (#566). In the case of a client for which we did not find an old client Collector, (new user case), we need not look for an existing RuleSet. Instead, just associate the proper metadata with the RuleSet, as required by the engine and the particular rules of the current application. This metadata and the standard metadata name ('0') for the engine's RuleSet-level metadata is established (#570-1) and, just before returning from this method, the same return code 2 is set as was set for in #526 to indicate "no previous Collector found". The Ruleset metadata that is used (#587) for the current application's rules is statically declared at #43. Refer to "RuleSet Metadata Required for RuleSets" for detailed explanation of how to formulate this metadata.

For clients that already have a Collector (not new users), #576 looks for an existing RuleSet in the contents of the client Collector. If it is found, the new RuleSet object in memory is filled from persistent storage (#579). Otherwise (we had an existing client Collector but no RuleSet - only had it to hold reminder messages for an reminder that had since been deleted), just set the metadata and return code 2, as we did for the new user case. The default return code 0 (#598), indicates that there was an old RuleSet.

This completes the set up for reminder commands. We continue with the command processing (14).

Connection Objects - PUT Command

We look at the PUT command before the GET command because GET is just a minor subset of PUT.

The reminder ID and the tokenized reminder string are parsed (#184-199) and stored into remID and remStr. The reminder ID is expected to be a place holder "000" that is replaced (#203) by the createUniqueID() Connection Class method (#764), which generates a unique reminder ID for the submitting client.

The Connection Class method saveReminderInLibrary() is invoked (#215) to do just as the method name implies. It uses the reminder ID and reminder string that was just parsed. It also requires passing a boolean value, newFlg (true) to indicate if the affected RuleSet is new (with this reminder) or changed (false). This invoked method (#602) constructs a new Reminder Object (#604).

Reminder Objects

Refer to "Reminder Class" for the Reminder Class Java code. The Reminder Class constructor (#22) completes the tokenizing of the reminder string, storing extracted tokens in its own object attributes (#24-76). This includes some conversions of date/time forms (#52, 81-93). Finally, it invokes its own method createReminderMessage() (#95) to build a Reminder Message for the current reminder (#99-115). This message is the one that is presented as Email or as a retrievable message for the client when the reminder matures. The two forms of this message are conditioned (#101) on the attribute viaStr which holds the parsed token "E-Mail" if Email was requested in the command. Various substitutions of the reminder's (parsed) attributes are made in the message skeleton. The resulting message is returned and saved in remMsg (#115,95). You will see that this message is made available, through the reminder rules, to either the Message Adapter or the SampMail Adapter where it is used to formulate the actual messages when they are needed.

While we are looking at the Reminder Class, lets look ahead at the remaining methods which are each devoted to building one of the Rules for the reminder: getAlarmSet1Rule() (#120), getAlarmSet2Rule() (#135), getAlarmSet3Rule() (#153), and getAlarmPopRule() (#168). The rules are built by incorporating into the various Rule skeletons the various attributes of the Reminder object, based on information from the client that arrived in the reminder string that was parsed. The following section discusses in some detail the various reminder rules and their formulation. You can look at this detail now or skip it now and come back to it at another time. It is important for a complete understanding of the Remind er Application and interesting for a better understanding of some of the subtle points in rule building, especially involving some of the more complex uses of Time Adapter alarms.

Reminder Rules

Following are the rules that are required by the Reminder Application to represent its individual reminders. Formatting (line breaks) were added here to improve readability. These rules correspond with those that are built in the Reminder Class methods getAlarmSet1Rule(), getAlarmSet2Rule, getAlarmSet3Rule, and getAlarmPopRule beginning at #120.

AlarmSet1Rule: This rule starts the yearly interval alarms for the reminder. It is set to go off one day before the reminder messages are to begin, so the countStr value should be one greater than the number of days BEFORE the event (assuming that you want to be reminded on the day of the event as well as during the days leading up to it). For example, if client specifies 5 days before (the initial countStr value) then this is incremented to 6 days (the countStr value used in the rule) so that the yearly alarm goes off 6 days before. When it goes off, it starts a second alarm which is a daily interval alarm (see rule 3) which initially goes off on day 5.

     ( => (AND (OR
          (EventName "AGENT:STARTING")
          (EventName "AGENT:CSCHANGE"))
          (addTimeInterval ""dateStr" "00:00:01"
              -countStr "days" ?date ?time))
          (setRandomStartBoundedIntervalAlarm 1 "years"
              ?date ?time 21600 "idStr2" "" ))

Substitutions (when the rule is built by the Reminder Application code for the particular reminder):

dateStr: reminder date of the form YYYY-MM-DD in double quotes where the values come from client-specified values.

countStr: set to the value of the current countStr (#56) in the Reminder Class plus 1 (NOT in quotes) For ex., if the client specifies 5 as the back off from the reminder event date then countStr will be 5, but we should put 6 (countStr + 1) in the rule (preceded by minus sign for the backoff: -6) because there is a delay of a day in before the daily alarms (see below) go off.

idStr2: set to a unique identifier with a character "Y" appended to it to distinguish it from idStr (see second rule below). This id is in double quotes.

AlarmSet2Rule: This rule handles a sliver condition whereby the first rule is fired AFTER the designated start time in the first (yearly) alarm. Because alarms do not pop when their start time is before the current time, we need a rule to handle that unique first year sliver case. This rule takes care of that problem. It fires in the window where the first rule fails to fire - after the start of the time of the first rule, but not after the client's event has already past for this year. There may be a very unlikely window yet where both rule 1 and rule 2 would fire in the first year due to the randomizing that occurs in the first rule, but this would be unlikely and would only result in doubling up on the reminder messages (twice your money's worth!).

     ( => (AND (AND (OR
          (EventName "AGENT:STARTING")
          (EventName "AGENT:CSCHANGE"))
          (addTimeInterval "dateStr" "00:00:01"
              -countStr "days" ?date ?time))
          (currentTimeBetween ?date "00:00:01" "dateStr" "06:00:00))
          (setRandomBoundedIntervalAlarm 1 "days"
              ?date "00:00:01" "dateStr" "06:10:00" 21600
              "idStr" ""))

Substitutions (when the rule is built by the Reminder Application code for the particular reminder):

dateStr: reminder date of the form YYYY-MM-DD in double quotes where the values come from client-specified values.

countStr: set to the value of the current countStr variable in the Reminder Class plus 1 (NOT in quotes) For ex., if the client specifies 5 as the back off from the reminder event date then countStr will be 5, but we should put 6 (countStr + 1) in the rule (preceded by minus sign for the backoff: -6) because there is a delay of a day in before the daily alarms (see below) go off.

idStr: This is the same as idStr2 but without the character "Y" appended.

AlarmSet3Rule: This rule starts daily alarms one day after the first rule fires and starts off the daily alarms that lead up to the event. Rule 1 and 3 firings are associated and both are intended to be mutually exclusive with rule 3 firing. Rule 3 uses a countStr value that is also one greater than originally specified by the client as the number of back-off days. This causes it to run through the day of the event. So, with the previous example of 5 days back off, countStr is 6 (still one greater) and the first daily alarm goes off 5 days before. We get a total of 6 such alarms, the last of which goes off on the day of the client's event (e.g. birthday).

     ( => (AND (AND (AND (EventName "Time:Alarm")
          (AlarmId "idStr2"))
          (EventTime ?d ?t))
          (addTimeInterval ?d ?t countStr "days" ?newdate ?newtime))
          (setEndBoundedIntervalAlarm 1 "days"
               ?newdate ?newtime "idStr" ""))

Substitutions:

idStr2: same as in the first rule

countStr: same as in the first rule without the preceding minus sign. In our example, this is the original backoff value from the client plus one to allow for getting a reminder message one the day of the event as well as the preceding days.

idStr: This is the same as idStr2 but without the character "Y" appended. We need two alarm ids because there are two separate alarms firing. In fact the second one is firing once for each day in the daily reminder cycle. So there are two alarms: one for the yearly alarm (idStr) and one for the daily interval alarms (idStr2). Note: now that we have multiple alarms going off (daily ones) for the days leading up to the event, we can no longer use the reminder id as the message id. Instead we will be changing the message adapter to use the EventTime as the message id.

AlarmPopRule: for the Message Adapter timer pop:

     ( => (AND (EventName "Time:Alarm")
               (AlarmId "idStr"))
               (SetMessage "remMsg"))

Substitutions:

idStr: same as idStr (in double quotes) in second rule above.

remMsg: reminder message string (in double quotes) from the client.

AlarmPopRule: for the EMail Adapter timer pop:

     ( => (AND (EventName "Time:Alarm")
               (AlarmId "idStr"))
               (EMailWithSubject "userEmailStr"
               "remMsg" "nameStr" ))

Substitutions:

idStr: same as idStr (in double quotes) in second rule above.

remMsg: reminder message string (in double quotes) from the client.

UserEmailStr: user email address (in double quotes).

nameStr: subject for eMail (in double quotes) from client.

Following is the standard RuleSet-level metadata (named '0') that is required by the engine for the Reminder application RuleSets along with the above rules:

     CONDUCTSET csname
     BEGIN PREDICATES
     EventName(string);
     EventTime(string,string);
     AlarmId(string);
     addTimeInterval(string,string,integer,string,
          string,string);
     currentTimeBetween(string,string,string,string);
     setRandomStartBoundedIntervalAlarm(integer,string,
          string,string,integer,string,string);
     setEndBoundedIntervalAlarm(integer,string,string,
          string,string,string);
     setRandomBoundedIntervalAlarm(integer,string,string,
          string,string,string,integer,string,string);
     EMailWithSubject(string,string,string);
     SetMessage(string);
     END PREDICATES
     BEGIN SENSORS
     SENSOR addTimeInterval Time:addTimeInterval;
     SENSOR currentTimeBetween Time:currentTimeBetween;
     END SENSORS
     BEGIN EFFECTORS
     EFFECTOR setRandomStartBoundedIntervalAlarm
          Time:setRandomStartBoundedIntervalAlarm;
     EFFECTOR setEndBoundedIntervalAlarm
          Time:setEndBoundedIntervalAlarm;
     EFFECTOR setRandomBoundedIntervalAlarm
          Time:setRandomBoundedIntervalAlarm;
     EFFECTOR EMailWithSubject SampMail:EMailWithSubject;
     EFFECTOR SetMessage Messages:SetMessage;
     END EFFECTORS

This metadata is declared at #43 in the RemAgent Class.

Connection Objects - PUT Command (continued)

The remainder of the Connection Class saveReminderInLibrary() method involves mainly invoking the Reminder Object methods for building the reminder rules and adding those rules to the RuleSet that was initialized by the setupRulesAccess() method.

In each case a Agent Building Environment Developer's Toolkit Rule (IALibInferenceRule) object is constructed (#611 for example). The rule name (and rule id) specified (#612) is the unique reminder id concatenated with a value (Set1 etc.) to distinguish the different rules that make up each reminder. The rule string itself is provided by the Reminder Class method (#612, for example) for the particular rule. It is important to notice that the input reminder string (remStr) is given as a Rule-level metadata string argument (#612), but only for the first rule of the set of rules for this reminder. In this way the original input from the client for creating these rules is retained along with the rules. This is used whenever the reminder is retrieved or played back to the client. Recall Figure 3-2, where this was discussed in the overview of this sample. Finally, the RuleSet method addRule() is invoked (#614, for example) to add the Rule to the client's RuleSet.

Once the RuleSet is updated (in memory), we still have to update it in persistent storage by using the put() method of the Library Ruleset Class (#633).

One final step is required to tell the agent and adapters about the new or changed Conduct Set for the client (picking up the new or changed RuleSet and its metadata). The IAAgent methods required for this depend on whether or not the Conduct Set is new or changed as determined from the newFlg parameter (#602,636). If it is changed (adding new rules to an existing Conduct Set), then we must quiesce some activity for the current version of the Conduct Set before loading the new one (#641):

Otherwise, it is a new Conduct Set, so it is only necessary to:

The PUT command invokes getUserRules() Connection Class method (#219). This allows for "playing back" to the client not only the new reminder, but also any previous reminders that the client has established. These are used to update the current set of reminders viewed by the client. The getUserRules() method (#710) basically looks at each rule in the client's RuleSet object, extracts the metadata string from the first rule for each reminder, and concatenates each such metadata string (separated by semicolons) in remsStr (don't confuse with remStr, which is the single input reminder string from the client for the current PUT command!).

Finally, PUT processing concludes by sending back a string on the socket (#222), consisting of the "PUT:" prefix, followed by the tokenized string of all reminders for the client as built by getUserRules() above.

Connection Objects - GET Command

GET command processing (#152) is the same as the last part of the PUT command processing described above. That is, it involves invoking the getUserRules() method of the Connection Class to assemble the tokenized string, stored in metadata with the first Rule for each Reminder, and returning this to the requesting client.

Connection Objects - REMOVE Command

Unlike the PUT command, REMOVE (#277) requires that the client provide a reminder ID to identify the reminder that is to be removed. Then this ID is used (along with the "Set1" string that distinguishes the first rule for a particular reminder) as input to the deleteReminderFromLibrary() method invocation (#290). This method (#653) uses the reminder ID with the Library RuleSet find() method (#658) to locate the first of the reminder's Rules in the client's RuleSet, matching on the Rule name. Then is uses the RuleSet deleteRule() method (#662) to delete the rule that was found. It does similarly for the remaining rules for the same reminder (#668-701).

After deleteReminderFromLibrary() processing, there is a check to see if the removal of the current reminder rules leaves the containing RuleSet empty. It uses the RuleSet size() method (#294) to determine this. When the RuleSet is empty, it is appropriate to tell the agent/engine/adapters to clean up all work/activity for the specified (client) selector (#304):

In this case it is also appropriate to delete the RuleSet, using the del() RuleSet method (#309). If there are no messages stored in the client's Collector (test a #320), the client's Collector is also deleted, using the similar del() method of the (top) Collector (#323). Notice that this activity on the top Collector, which is shared with other Connection threads, needs appropriate serialization (#322).

Otherwise (rules for other reminders remain in the Conduct Set), we need to use a put() RuleSet method (#338) to get persistent storage updated with the changed RuleSet. In this case we also need to quiesce and reload the new Conduct Set; then restart (#341-2):

Connection Objects- MODIFY Command

Like the REPLACE command, MODIFY (#230) requires that the client provide a reminder ID to identify the reminder that is to be modified. Rather than update the existing reminder's rules, the existing reminder is deleted and replaced, using the reminder tokens given by the client and the same reminder ID. This is very much like a REMOVE followed by a PUT.

After parsing the input tokens (#235-252), the newFlg parameter is set to false (#255) (changed Conduct Set, rather than a new one), in preparation for the saveReminderInLibrary() invocation. But first deleteReminderFromLibrary() is invoked (#256) to delete the rules for reminder that is being changed. Then saveReminderInLibrary() operates as previously described, except that the newFlg parameter value of false insures that there is a proper quiesce of the old Conduct Set. The MODIFY command also plays back the current reminders (tokenized strings) for the client response like the other reminder commands, using getUserRules() (#261).

Connection Objects - Message Commands

The commands for viewing and deleting reminder messages (24) are also processed in the Connection Object/Thread. Unlike the reminder commands, the message commands do not have a separate setup routine for building the client's Collector. Instead, this is done by the same method (getUserMessages()) that gathers messages from the Library for the client.

Message commands use a couple of other objects: RemMessage and RemMessageVector.

RemMessage Objects

The RemMessage class (see "RemMessage Class") is used to represent a reminder message for the reminder message commands that process them.

RemMessageVector Object

The RemMessageVector Object (see "RemMessageVector Class") inherits from the Java Vector class. It is used by the message reminder commands to collect the reminder message objects for a particular client. You will notice that most of the methods used are inherited.

Connection Objects - GETMSGS Command

The GETMSGS command invokes the getMsgs() method of the "Connection Class" (#366) with the mark parameter set to true, which not only retrieves the messages for the client, but also marks them as read (old). The getMsgs() method (#774) uses getUserMessages() to read the messages in the client Collector metadata in the Library (25), assembling them in a msgCollection object (object of the "RemMessageVector Class").

First the msgCollection object is cleaned out by the removeAllElements() inherited method from Vector Class (#955). After a new Collector is constructed (#958) and it is set up to retrieve Collector-level metadata with a name of "1" (#959), the Library get() method is used (#960) to fill the client's Collector from persistent storage, especially the metadata which is of interest here.

The metadata string is extracted from the client's Collector object using the extractMetadata() Library method(#963). A BufferedReader Java object is established (#964) to allow for reading the individual messages in this string via its readLine() method(#968). Then each line read is used to construct a "RemMessage Class" object (#971). Finally, the addElement() method of the inherited Vector Class is used (#972) to assemble these messages into the msgCollection (RemMessageVector Class object).

A failure occurs when there are no messages, causing getUserMessages() to return false (#977).

Continuing with getMsgs() (after #784), the parameter calls for marking all messages as read (old), so the markAllRead() method of the "RemMessageVector Class" (#58) is invoked to do this (#788). Finally, the updateUserMsgs() method of "Connection Class" (#798) is invoked to put the messages back into the Library's persistent storage.

The updateUserMsgs() method (#994) has two main cases: message remain and no messages remain in the msgCollection. When messages remain (#1003), it uses a Library put() method (#1005) to write the Collector back to persistent storage (25). The top Collector memory object's contents are protected (#1004) against concurrent operations by other Connection threads that may be searching the top Collector's contents using the synchronized Java serialization method.

In the case where there are no messages, which would not be the case for the current use of this method, but is the case, for other uses of it (deleteMsgs() for example), it is appropriate to check to see if the client's Collector also has no RuleSet. This is done by setting up a Library find() method (#1020) for the client Collector. The setup for this is similar to previous such code for this same purpose. Where a RuleSet is found, the Collector metadata is deleted (#1023), but the Collector itself is retained. Where no RuleSet is found, the entire client Collector is deleted (#1027), using the appropriate Library method. Because the top Collector Contents are affect and the fact that multiple Connection threads may share top Collector contents search activities, it is appropriate to serialize this delete activity (#1026).

Returning to getMsgs() processing (at #790): if there is a return code of 1, we have an interesting special case to handle. This is a version mismatch that is detected by updateUserMsgs() (at #1010). This error can occur when the client Collector put() Library method (at #1005) conflicts with a similar put by the Message Adapter.

Note: We are ignoring, dismissing, or not expecting Version Mismatches in most other places in this application code because the only potential inter-thread multiple write situation is between the writes to the Library by the Message Adapter and the update of messages (marking them as read or whatever) by the Connection threads. Other cases of conflict do not involve multiple writers (with the assumption that we are not trying to handle conflicts between multiple client threads for the same client).

However, the conflict that is addressed here could occur if the adapter happened to be writing a new message for that same client from an reminder alarm pop (very unlikely). Refer to the discussion of Library version mismatches in "Library Version Management". This essentially calls for the invoker (getMsgs) to repeat processing (#782-797) because its assumptions about its input from Library persistent storage has changed by processing by another thread, affecting the same persistent storage Library data. The version mismatch processing repeat is controlled by retry and retrycount. The Message Adapter does similarly for the case where it is the loser in a version mismatch. This is an important illustration of how to handle this particular kind of Agent Building Environment Developer's Toolkit Library concurrency, but you should read the guidelines on Library concurrency ("Library Objects in General") to understand it more completely.

This essentially completes the getMsgs() processing (which returns to the GETMSGS command processing.

ConnectionObjects - GETMSGSNOMARK Command

There is no need to go into this (#382) because it is just a subset of the previous GETMSGS command

Connection Objects- DELMSGS Command

This command invokes deleteMsgs() (#404), which uses getUserMessages() (already described above). Multiple messages can be deleted with a single such command so there is a for() loop (#828-839) to handle them. Various msgCollection methods are used to find the message in the collection and delete them (#830-839). Then updateUserMsgs() (#994) - previously described - is used to update Library persistent storage for the deletions. As before, version mismatches are possible and handled (#841-2) and the for loop at 828).

Connection Objects - MARKMSGSASNEW Command

This command (#420) uses markMsgs() (#424), which is very much like deleteMsgs() above except that an update of messages is involved, rather than a delete.

Connection - STATUSMSGS Command

STATUSMSGS (#439) is pretty straightforward and does not demonstrate anything new.


Message Adapter

The RemAgent Server is presented primarily to illustrate the use of IAAgent and IALibrary for application use, particularly in a server environment. It happens to also utilize a specialized adapter, that is, one that is written especially for the Reminder Application. However, it is not the intention of this documentation to describe all of the aspects of writing an adapter. Rather, writing an adapter fundamentals is addressed in Chapter 4. "Writing an Adapter". The reference material is in Chapter 3. "Adapter Reference Material" of the Components and Adapter Reference book.

This section describes the Message Adapter that was written especially for the Reminder Application in just sufficient detail to allow you to understand how it works with the rest of the Reminder Application Server.

In "Message Adapter - h file", you can see (#39) that the adapter constructor has a single parameter for getting the map that is needed to initialize the Agent Building Environment Developer's Toolkit Library. This parameter originally came from the Reminder Application configuration file (see "RemAgent - Agent Building Environment Developer's Toolkit Agent Configuration"). There are only a few methods for this adapter, including:

Library Initialization

Refer to "Message Adapter - cpp file", where the MessageAdapter() constructor (#36) handles the initialization of the Library (11) so that it can store messages in client Collector metadata strings. The input parm string is tokenized (#38-48), establishing the required arguments for the attachImplementation() method (#50) of IALibrary (constructed at #49). The Library top Collector is extracted from IALibrary (#51) and saved in the adapter attribute "myCollector".

Store Messages in Library

Refer to "Message Adapter - cpp file", where performAction() (#89) provides the Message Adapter's only effector procedure. This procedure builds an Atom (refer to "Class IAAtom" of the Components and Adapter Reference book) from the input binding string (#90-94).

The token parameter for a performAction() is to identify the particular effector. In this case there is only one effector, so the test for the SETMESSAGE_TOKEN (#96) is not especially interesting. Most of the initial code (#98-114) establishes values for the current time and date, as needed for the reminder message that is to be stored in the Library. It generates a unique message ID that is also needed in the reminder message. Then it extracts the first (only) "term" in the Atom, a string, which is the main text of the message. This text was built in the "Reminder Class" (#95,111-113) and passed as an effector in the alarm pop rule. In #118 the whole reminder message is built from the above described pieces.

The next step is to store that message in the Library (23). A Collector object is constructed and initialized (#121-3). The parent (top) Collector for the constructor is from 'myCollector", which was saved when the Library was initialized (above). The name of the current client Collector is obtained using getSelector(), which is a IAEventHeader (refer to "Class IAEventHeader" of the Components and Adapter Reference book) method, where the event is the current event (timer pop) that caused the current effector to be invoked. Recall that the client name, selector, Conduct Set (and RuleSet), and client Collector all correspond (for a particular client).

Then the Collector is filled from persistent storage using a Library get() method (#130). At #123 the adapter established that it expects to find Collector-level metadata (as named, 1) on the Collector get() (at #130). Then at #134-5 it determines and remembers the case where there was actually no metadata found (means that there were no previous reminder messages stored by the adapter). Lines #137-140 handle errors other than the no metadata found error.

When previous metadata exists (#143), the previous metadata is extracted from the Collector and concatinated with the new reminder message; then the result is put back into the Collector object's metadata (#146). Notice (#118) that the reminder message is followed by a newline (\n), which separates reminder messages in the metadata string. When no previous metadata existed (#145), it is not necessary to concatenate with previous messages (#146).

Having stored new Collector metadata, the client Collector is updated in persistent storage using the Library put() method (#148). At #152 it catches an unlikely version mismatch error on the put(). This was discussed in "Connection Objects - GETMSGS Command" above. In this case, the version mismatch would indicate that the client has changed the same client's message status codes and put() the containing client's Collector to record the new status codes in persistent storage. It also means that concurrent update occurred between the time of the current adapter's get() to the same client Collector and the time of the adapter's current put(). In this case, the adapter must begin again with the get() of the client's Collector and repeat processing from that point on (the loop at #128-158), in order to avoid destroying the message status updates made by the GETMSGS (for example). This continues until a record is actually written (without a version mismatch).


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