Chapter 9. Events

An important property of agents is the ability to react timely to different kinds of events. Jadex supports two kinds of application-level events, which can be defined by the developer in the ADF. Internal events can be used to denote an occurrence inside an agent, while message events represent a communication between two or more agents. Events are usually handled by plans. For example the ping plan gets triggered when a ping request message arrives. When an event occurs in Jadex and no plan is found to handle this event a warning message is generated, which can be printed to the console depending on the logging settings (see Chapter 12, Properties).

The Jadex events XML schema part

Figure 9.1. The Jadex events XML schema part

Two kinds of events are supported in Jadex: Message events and internal events, as well as references to these types, as shown in Figure 9.1, “The Jadex events XML schema part”. Generally, all event types are parameter elements meaning that any number of parameter(set)s can be specified for a user-defined kind of event. A parameter itself can be used for passing values from the source to the consumer of the event. Unlike goals, events are single points in time and therefore only support "in" parameters, which denote the "source to consumer"-direction of value passing. In addition all kinds of events share the common attributes: "posttoall", "metalevelreasoning" and "randomselection". The "posttoall" flag determines if the event should be dispatched to a single receiver or to all applicable plans. The "metalevelreasoning" flag can be used to turn off the process of reasoning about plan candidates if more than one candidate is applicable for a given event. Finally, the "randomselection" flag can be used to turn off the importance of the declaration order of plans for the plan selection process. Nevertheless, the priority of a plan is still respected.

Table 9.1. Event Flags

FlagsDefault Value
posttoallinternal event=true, otherwise false
metalevelreasoningtrue
randomselectionfalse

At runtime, e.g., when accessed from plans, instances of the elements are represented by the jadex.runtime.IMessageEvent and jadex.runtime.IInternalEvent interfaces. The following sections will describe these different types of events in more detail.

9.1. Internal Events

Internal events are the typical way in Jadex for explicit one-way communication of an interesting occurrence inside the agent. The usage of internal events is characterized by the fact that an information should be passed from a source to some consumers (similar to the object oriented observer pattern). Hence, if an internal event occurs within the system, e.g., because some plan dispatches one, it is distributed to all plans that have declared their interest in this kind of event by using a corresponding trigger or by waiting for this kind of internal event. The internal event can transport arbitrary information to the consumers if custom parameter(set)s are defined in the type for that purpose. A typical use case for resorting to internal events is, e.g., updating a GUI.

<!-- ADF snippet showing the internal event declaration. -->
...
<events>
    <!-- Specifies an internal event for updating the gui.-->
    <internalevent name="gui_update">
        <parameter name="content" class="String"/>
    </internalevent>
</events>
...
// Plan snippet showing the creation and dispatching of the internal event.
...
public void body() {
    String update_info;
    ...
    // "gui_update" internal event type must be defined in the ADF
    IInternalEvent event = createInternalEvent("gui_update");
    // Setting the content parameter to the update info
    event.getParameter("content").setValue(update_info);
    dispatchInternalEvent(event);
    ...
}

Figure 9.2. Dispatching an internal event example

In addition to user-defined internal events there are also some already predefined internal events used by the Jadex system itself. In Table 9.2, “Predefined Internal Event Types” these types are explained shortly. They should not be of much importance for most agent developers. Only, if you intend to write mobile plans you may need to know them, because they will be passed to the plan body's action method (information about mobile plans can be found in Section 8.2, “Implementing a Plan Body in Java”)

Table 9.2. Predefined Internal Event Types

Predefined TypesDescription
jadex.model.IMEventbase.TYPE_TIMEOUTA timeout has occurred (generated, when waiting for a fixed time)
jadex.model.IMEventbase.TYPE_EXECUTEPLANA plan should be executed (e.g., initial or conditional plan)
jadex.model.IMEventbase.TYPE_CONDITION_TRIGGEREDA condition has been triggered

9.2. Message Events

All message types an agent wants to send or receive need to be specified within the ADF. The message event (class jadex.runtime.IMessageEvent) denotes the arrival or sending of a message. The direction of the message (arrived or to be sent) can be checked with the isIncoming() method. Note, that only incoming messages are handled by the event dispatching mechanism, while outgoing messages are just sent. The native underlying message object (which is platform dependent) can be retrieved using the getMessage() method. In addition, the message content, which may be a String or some content object, can be retrieved using the getContent() method.

The message passing mechanism is based on using jadex.adapter.fipa.AgentIdentifiers for the unambiguous identification of agents. An agent identifier hence contains an agents globally unique name which consists of a local part followed by the "@" character and the platforms name (schema: <agentname>@<platformname>, example: ams@lars). In addition to the name an agent identifier can contain additional information. On the one hand arbitrary many transport addresses might be present. These addresses can be used to contact the agent from a remote platform and normally represent the address of platform wide transport mechanisms (schema: <transportname>://<address>, example: nio-mtp://mypc18:9876). Besides the transport addresses also so called resolvers can be part of an agent identifier. An agent resolver is itself another agent identifier which can be used for name resolution, i.e. if an agent wishes to send a message to another agent and wants to fetch up-to-date transport addresses of the receiving agent it can query the resolver(s) for additional transport addresses. Please refer to the FIPA Agent Management Specification. An agent identifier of another agent can be obtained in two ways. If all details about the agent are known an agent identifier can directly be created using a local or global name. E.g., new AgentIdentifier("Heinz", true) would create an identifier to contact agent "Heinz" on the local platform. If the details of an agent are not known in advance, an agent may search for other agents using either the AMS listing all agents on a platform or the DF, which allows to search for agents providing a given service. Searching AMS and DF is explained in detail in Chapter 16, Using Predefined Capabilities

Templates for message events are defined in the ADF in the <events> section. The direction attribute can be used to declare whether the agent wants to receive, send or do both (default) for a given event. Possible values for that attribute are "send", "receive" and "send_receive" respectively. The type of the message constrains the available parameters of a message. Currently, the only available type is "fipa" which automatically creates parameter(set)s according to the FIPA message specification (e.g., parameters for the receivers, content, sender, etc. are introduced). Through this message typing Jadex does not require that only FIPA messages are being sent, as other options may be added in future. In the following Table 9.3, “Reserved FIPA message event parameters”, all available parameter(set)s are itemized. For details about the meaning of the FIPA parameters, see the FIPA specifications available at FIPA ACL Message Structure Specification. In addition to the FIPA parameters, Jadex introduces the content-start, content-class, and action-class parameters. The meanings of all of these parameters are explained in the following subsections.

Table 9.3. Reserved FIPA message event parameters

NameClassMeaning
performativeStringSpeech act of the message that expresses the senders intention towards the message content. You can use the constants from jadex.adapter.SFipa.{ACCEPT_PROPOSAL, AGREE, ...}
senderAgentIdentifierThe senders agent identifier, which contains besides other things its globally unique name.
reply-toAgentIdentifierThe agent identifier of the agent to which should be replied.
receivers [set]AgentIdentifierArbitrary many (at least one) agent identifier of the intended receiver agents.
contentObjectThe content (string or object) of the message. If the content is an object it has to be specified how the content can be marshalled for transmission. For this puropose codecs are used. Jadex has built in support for marshalling arbitrary Java beans via setting the language of the message to jadex.adapter.fipa.SFipa.NUGGETS_XML.
languageStringThe language in which the content of the message should be encoded.
encodingStringThe encoding of the message.
ontologyStringThe ontology that can be used for understanding the message content. Can also be used for deciding how to marshal the content.
protocolStringThe interaction protocol of the the message if it belongs to a conversation. There are constants available for the predefined FIPA interaction protocols in jadex.adapter.SFipa.PROTOCOL_{REQUEST, QUERY, ...}
reply-withStringReply-with is used for assigning a reply to a original message. The receiver of the message should respond to this message by putting the reply-with value in the in-reply-to field of the answer. Unique ids can e.g. be generated via the method SFipa.createUniqueId().
in-reply-toStringUsed in reply messages and should contain the reply-with content of the answered message.
conversation-idStringThe conversation-id is used in interactions for identifying messages that belong to a specific conversation. All messages of one interaction should share the same conversation-id. Unique ids can e.g. be generated via the method. SFipa.createUniqueId().
reply-byDateThe reply-by field can contain the latest time for a response message.
content-start [non-fipa]StringCan be used for easy matching of string value. It is checked if the value of the content start is equal to the beginning of the string content of the message.
content-class [non-fipa]ClassThe content-class can be used for matching and tests if the class of the content object equals the specified class.
action-class [non-fipa]ClassThe action-class can be used for matching and checks if the class of the inner agent action (which might be contained in another action concept e.g. in FIPA SL) matches the given class.

9.2.1. Receiving Messages

Typically in the ADF of an agent a number of message event types for sending and receiving message events are declared for the application domain. Examples for such user-defined message event types might be "inform_time", "request_vision", etc. As those message types are defined for each agent separately there are consequently no global message types. So how does an agent know the message type of a newly received message? For this purpose a simple matching process is used. This means that all locally known message types of an agent and its subcapabilities (with direction "receive" or "send_receive") are matched against the newly received message and the best fitting is selected. For the matching process the parameter values of a message type are checked against the values in the received message. For this purpose only parameters with direction="fixed" are considered important, as they represent fixed type information. In addition to fixed parameter values, message matching can be fine-tuned by using a match expression that can be specified for each message event. As shown in the second example below, in the match expression the parameters of a message can be accessed by prepending a "$" before the parameter name. Additionally, it is not allowed having variable names in Java that contain a "-" character as this is interpreted as minus. Therefore, in all parameter(set)s names the "-" characters have been replaced by a "_" character. This means you need to write e.g. "$reply_with" instead of "$reply-with".

A message event type matches an incoming message if all fixed parameter values are the same in the received message and the match expression evaluates to true.

<imports>
    <import>jadex.adapter.fipa.SFipa</import>
</imports>
...
<events>
    <!-- A query-ref message with content "ping" -->
    <messageevent name="query_ping" type="fipa" direction="receive">
        <parameter name="performative" class="String" direction="fixed">
            <value>SFipa.QUERY_REF</value>
        </parameter>
        <parameter name="content" class="String" direction="fixed">
            <value>"ping"</value>
        </parameter>
    </messageevent>
    
    <!-- An inform message where content contains the word "hello" -->
    <messageevent name="inform_hello" type="fipa" direction="receive">
        <parameter name="performative" class="String" direction="fixed">
            <value>SFipa.INFORM</value>
        </parameter>
        <match>((String)$content).indexOf("hello")!=-1</match>
    </messageevent>
</events>

Figure 9.3. Examples for receiving messages

There are several reasons why an agent may fail to correctly process an incoming message. These are indicated by different logging outputs at different logging levels (see Table 9.4, “Possible problems when matching messages”). In the first case, if more than one message event type has a match with the incoming event the most specific match will be used. The number of fixed parameters and the presence of a match expression are used as indicator for the specificity. As this is a common case, it is only logged at level INFO. When a message is received, which does not match any of the declared message events of the agent, a WARNING is generated, indicating that this message is ignored by the agent. Finally, when there are two or more message events, which all match an incoming message to the same degree (e.g., all have the same number of fixed parameters) the system cannot decide which message event to use, and has to choose one arbitrarily. As this probably indicates a programming error in the ADF, a SEVERE ouput is produced.

Table 9.4. Possible problems when matching messages

LevelOutput
INFO<agentname> multiple events matching message, using message event with highest specialization degree
WARNING<agentname> cannot process message, no message event matches
SEVERE<agentname> cannot decide which event matches message, using first

The content-start, content-class, and action-class parameters (if present) are treated specially in the matching process. The content-start parameter matches to all messages with a content starting with the given string value. The content-class parameter is matched against the class of the object sent as message content (see below). Finally, the action-class parameter is useful for messages encoded in valid FIPA SL. The parameter is matched against the action expression contained in the message. As the effect of these special parameters can also be achieved using a match expression, support for these parameters might be dropped in future releases.

9.2.2. Sending Messages

Messages to be sent also have to be declared in the ADF. The actual sending is usually done inside a plan, which instantiates the declared message event, fills in desired parameter values, and dispatches the message using one of the sendMessage...() methods. The super class of both plan types (jadex.runtime.AbstractPlan) provides several convenience methods to create message events. To send a message, a message event has to be created using the createMessageEvent(Strint type) method supplying the declared message event type name as parameter. The receivers of fipa messages are specified by agent identifiers (class jadex.adapter.fipa.AgentIdentifier). The message content can be supplied as String or as Object with setContent(Object content). If the content is provided as Object it must be ensured that the agent can encode it into a transmissable representation as described in Section 9.2.3, “Using Ontologies and Content Languages”.

To actually send the message event it is sufficient to call the sendMessage(IMessageEvent me) method with the prepared message event as parameter. It is also possible to send a message and directly wait for a reply with an optional timeout by using the sendMessageAndWait(IMessageEvent me [, timeout]) method. This is described in Section 9.2.4, “Using Conversations for Managing Sequences of Messages”

<imports>
    <import>jadex.adapter.fipa.SFipa</import>
</imports>
...
<events>
    <!-- A query-ref message with content "ping" -->
    <messageevent name="query_ping" type="fipa" direction="send">
        <parameter name="performative" class="String">
            <value>SFipa.QUERY_REF</value>
        </parameter>
        <parameter name="content" class="String">
            <value>"ping"</value>
        </parameter>
    </messageevent>
</events>
// Plan snippet showing the creation and sending of the message.
public void body() {
    IMessageEvent me = createMessageEvent("query_ref");
    me.getParameterSet(SFipa.RECEIVERS).addValue(new AgentIdentifier("Ping", true));
    // me.setContent("ping 2"); // Set/change content if necessary
    sendMessage(me);
}

Figure 9.4. Example of sending a message

9.2.3. Using Ontologies and Content Languages

Message based communication allows that agents can communicate even when they are distributed across the network. One important property in the context of message based communication is the separaration of address spaces, i.e., that agents do not have direct access to the data inside other agents. Therefore data needs to be encoded into a message before sending and decoded from a message after receival. In the context of multi-agent systems, so called content languages and ontologies are responsible for describing how data should be encoded into messages. A content language defines the syntactical mechanism used to represent data and an ontology specifies the meaning of the concepts used in the message. Together, content language and ontology assure a shared common understanding among agents.

The data inside a Jadex agent is usually represented as a collection of Java objects referencing each other. The Jadex framework provides some useful features that allow to encode/decode object structures, such that they can be used directly for the communication between agents. For this purpose, the agent knows about so called content codecs, some of which are available by default, but can also be extended with custom codecs by the agent programmer. These codecs are selected automatically, when sending and receiving messages and are used to encode or decode the content of a message. From the viewpoint of an agent programmer, the agent is just sending or receiving messages containing Java objects. All the encoding and decoding works behind the scenes.

Two simple examples for sending and receiving a Java object inside a message are shown below (taken from the marsworld example). These examples use a Target object from package jadex.examples.marsworld. On the sender side, the message defines to use the language SFipa.NUGGETS_XML, which is per default available in each agent (see Figure 9.5, “Example of sending an object inside a message”). The corresponding nuggets codec can handle arbitrary JavaBeans (i.e. Java objects, which provide public getter and setter methods for their properties). For detailed information about JavaBean you should have a look at the JavaBeans Specification.

<!-- Message declaration in the ADF -->
<messageevent name="inform_target" type="fipa" direction="send">
    <parameter name="performative" class="String" direction="fixed">
        <value>SFipa.INFORM</value>
    </parameter>
    <parameter name="language" class="String" direction="fixed">
        <value>SFipa.NUGGETS_XML</value>
    </parameter>
    <parameter name="ontology" class="String" direction="fixed">
        <value>MarsOntology.ONTOLOGY_NAME</value>
    </parameter>
</messageevent>
public void body() {
    // Message sending in the plan.
    AgentIdentifier receiver = ...
    Target target = ...
    IMessageEvent me = createMessageEvent("inform_target");
    me.getParameterSet(SFipa.RECEIVERS).addValue(receiver);
    me.setContent(target); // The Java object is directly used as content.
    sendMessage(me);
}

Figure 9.5. Example of sending an object inside a message

As the decoded object is already availble for matching an incoming message, on the receiver side, the content-class parameter can be used to only match messages containing a Target object (see Figure 9.6, “Example of receiving an object inside a message”).

<!-- Message declaration in the ADF -->
<messageevent name="target_inform" type="fipa" direction="receive">
    <parameter name="performative" class="String" direction="fixed">
        <value>SFipa.INFORM</value>
    </parameter>
    <parameter name="content-class" class="Class" direction="fixed">
        <value>Target.class</value>
    </parameter>
    <parameter name="ontology" class="String" direction="fixed">
        <value>MarsOntology.ONTOLOGY_NAME</value>
    </parameter>
</messageevent>
public void body() {
    // Message receiving in the plan.
    IMessageEvent msg = (IMessageEvent)getInitialEvent();
    Target target = (Target)msg.getContent();
    ...
}

Figure 9.6. Example of receiving an object inside a message

Two content languages are predefined in Jadex itself and therefore are available on all platforms. These languages are defined in the constants SFipa.JAVA_XML and SFipa.NUGGETS_XML. The Java XML language uses the bean encoder available in the JDK, to convert Java objects adhering to the JavaBeans specification to standardized XML files. The nuggets XML language is a proprietary language in Jadex, that works similar to the Java XML language but the encoding and decoding is much faster. Both languages allow marshalling content objects independently from the underlying ontology as they rely completely on the Java Bean specification. Using these languages requires that Java bean information about the content object can be found or inferred by the Java bean introspector. Please have a look at the Beanynizer tool (available from the Jadex homepage) if you are interested in converting an ontology to Java beans including the necessary bean infos. Other content languages are available depending on the underlying platform (e.g. the JADE platform supports the FIPA SL language). The usage of these platform-specific languages is described in Appendix B, Platform Adapters.

If you want to use your own mechanism for encoding and decoding of message contents, you can implement the interface IContentCodec from package jadex.runtime. The interface requires you to implement three methods. The match() method is used by Jadex, to determine if your codec applies to a given message. For this decision, the important message properties (e.g. langauge and ontology) are supplied. The other two methods are called to encode() and object to a string for sending and to decode() a string back to an object, when receiving a message. To register a custom content codec in an agent, it is sufficient to add a property starting with contentcodec. in the properties section of an agent:

<properties>
    <property name="contentcodec.my_codec">new MyContentCodec()</property>
</properties>

9.2.4. Using Conversations for Managing Sequences of Messages

Normally messages are not sent in isolation, but occur inside a conversation of many messages that are sent and received. Because of this, you often want to identify a certain message as belonging to a specific conversation or being a direct reply to some other message sent before. In the FIPA message structure, three parameters are responsible for this kind of conversation management. A unique conversation-id can be used to group together several messages belonging to a single conversation. In addition the in-repy-to parameter allows to identify a message as being an answer to a previous message with a corresponding reply-with parameter value.

In Jadex, the relation between messages is used to achieve two things: First, it allows to wait for a specific message while ignoring other messages that do not belong to an ongoing conversation or are a reply to another message. Thanks to this, e.g., when two plans simultaneously wait for the same type of message, a received message will automatically be posted to the correct plan, from which the previous message of the conversation was sent. Second, it allows to restrict message receival to a certain capability, namely the capability from which an earlier message was sent. This means, e.g., that if an agent defines two similar message events in two different capabilities (as is commonly the case, when the same capability is included twice in an agent), the message will automatically be routed to the correct capability where the corresponding conversation originated.

In both cases, the mechanism is based on the usage of the conversation-id and/or in-repy-to and reply-with parameters. The developer has to make sure that, when sending an initial message a useful value has been set to one or more of these parameters. When replying to an initial message (by using msg.createReply(...)), the parameter values are set automatically, based on the values of the initial message (i.e. the conversation-id is retained while the reply-with is copied to the in-reply-to parameter). The setting of initial parameter values can directly be done in the message declaration as shown in Figure 9.7, “Example of an Initial Conversation Message”. In the example, the method createUniqueId() is used to generate a unique id for the conversation, whenever an instance of the message is created. The plan can send the message using dispatchMessageAndWait(). and directly receive the correponding reply message. When using a timout in dispatchMessageAndWait() and the message is not received before the timeout has elapsed, a jadex.runtime.TimeoutException is thrown (see also Section 8.2.1, “Plan Success or Failure and BDI Exceptions”). For a reply message (e.g. the inform below) no special settings have to defined in the ADF.

<events>
    <messageevent name="request" type="fipa" direction="send">
        <parameter name="performative" class="String">
            <value>SFipa.REQUEST</value>
        </parameter>
        <parameter name="conversation-id" class="String">
            <value>SFipa.createUniqueId($scope.getAgentName())</value>
        </parameter>
    </messageevent>
    <messageevent name="inform" type="fipa" direction="receive">
        <parameter name="performative" class="String" direction="fixed">
            <value>SFipa.INFORM</value>
        </parameter>
    </messageevent>
<events>
public void body() {
    IMessageEvent me = createMessageEvent("request");
    ... // Set other parameters as desired
    IMessageEvent reply = sendMessageAndWait(me);
    ... // Handle reply message
}

Figure 9.7. Example of an Initial Conversation Message

On the other hand, if you have received a message event and want to reply to the sender you don't have to create a new message event from scratch but can directly create a reply. This ensures that all important information such as the conversation-id or in-reply-to also appears in the answer. Moreover, message properties, which should not change during a conversation (e.g. protocol, language and ontology) are also automatically copied into the reply. A reply can be created by calling createReply(String type [, Object content]) method directly on the received message event. This method takes the message event type for the reply as parameter. Note that the message type with which you are replying also has to be present in your ADF as shown in Figure 9.8, “Example for Replying to a Message”.

<events>
    <messageevent name="request" type="fipa" direction="receive">
        <parameter name="performative" class="String" direction="fixed">
            <value>SFipa.REQUEST</value>
        </parameter>
    </messageevent>
    <messageevent name="inform" type="fipa" direction="send">
        <parameter name="performative" class="String">
            <value>SFipa.INFORM</value>
        </parameter>
    </messageevent>
<events>
public void body() {
    // Message receiving in the plan.
    IMessageEvent msg = (IMessageEvent)getInitialEvent();
    Object content = ... // Prepare content for reply
    IMessageEvent reply = msg.createReply("inform", content);
    sendMessage(reply); // Take care to send 'reply' and not 'msg'!
}

Figure 9.8. Example for Replying to a Message

The way of handling conversations described in this section is pretty different to programming agents based on abstract goals, as the programmer has to directly deal with all alternatives of the interaction flow. This process can be tedious and error-prone. Therefore, in Jadex a predefined capability is available, that already implements common use cases of interactions as specified in standardized FIPA interaction protocols (e.g. request, contract-net, auctions). The protocols capability allows to focus on the goals of the agents participating in a conversation. The protocols capability is described in detail in Chapter 16, Using Predefined Capabilities. Even if you want to implement your own custom interaction protocol, you should have a look at the protocols capability, because it introduces helpful patterns that can be applied to other interactions as well.

9.3. Goal Events

Besides internal and message events, there is a third kind of event in Jadex, that is sometimes of interest to an agent developer. So called goal events (IGoalEvent) are used to manage the processing of goals. For standard plans, goal events can usually be ignored, but when developing mobile plans, the results of goal processing are passed as goal events to the action() method of the plan. Therefore, understanding goal events is quite important, if you want to develop mobile plans.

Goal events are not declared in the ADF, as they are used only internally for the processing of goals. This means that the agent will automatically create a goal event in response to an active goal it wants to perform (process event) and for a goal that finished its processing (info event). To explicitly decide which kind of event has happened (as commonly necessary inside mobile plans) the IGoalEvent.isInfo() method can be used. Below, an excerpt of the PickUpWastePlan from the cleanerworld mobile example is shown to illustrate the usage of goal events. In contrast to standard plans a mobile plan will be always be invoked through calling its action() method regardless of the state of that plan. Hence the programmer has to supply case distinction for the different plan steps and can use the event provided parameter for this decision. In the example in the first step a moveto subgoal is dispatched and in the second step (when the position has been reached) the desired clean-up operation can be performed.

public void action(IEvent event)
{
    Waste waste = (Waste)getParameter("waste").getValue();

    // First event is a process event (=!isInfo)
    if(event instanceof IGoalEvent && !((IGoalEvent)event).isInfo())
    {
        IGoal moveto = createGoal("achievemoveto");
        moveto.getParameter("location").setValue(waste.getLocation());
        dispatchSubgoalAndWait(moveto);
    }

    // Second event is info event from achievemoveto goal dispatched above.
    else if(event instanceof IGoalEvent
        && ((IGoalEvent)event).getGoal().getType().equals("achievemoveto"))
    {
       ...
    }
}

Figure 9.9. Usage of goal events in the cleanermobile example