15.2. KIE Framework

15.2.1. KIE Systems

The various aspects, or life cycles, of KIE systems in the JBoss BPM Suite environment can typically be broken down into the following labels:
  • Author
    • Knowledge author using UI metaphors such as DRL, BPMN2, decision tables, and class models.
  • Build
    • Builds the authored knowledge into deployable units.
    • For KIE this unit is a JAR.
  • Test
    • Test KIE knowledge before it is deployed to the application.
  • Deploy
    • Deploys the unit to a location where applications may use them.
    • KIE uses Maven style repository.
  • Utilize
    • The loading of a JAR to provide a KIE session (KieSession), for which the application can interact with.
    • KIE exposes the JAR at runtime via a KIE container (KieContainer).
    • KieSessions, for the runtimes to interact with, are created from the KieContainer.
  • Run
    • System interaction with the KieSession, via API.
  • Work
    • User interaction with the KieSession, via command line or UI.
  • Manage
    • Manage any KieSession or KieContainer.

15.2.2. KieBase

The JBoss BPM Suite API allows you to create a knowledge base that includes all your process definitions that may need to be executed by that session. To create a knowledge base, use a KieHelper to load processes from various resources (for example, from the classpath or from the file system), and then create a new knowledge base from that helper. The following code snippet shows how to create a knowledge base consisting of only one process definition (using in this case a resource from the classpath):
KieHelper kHelper = new KieHelper();
  KieBase kBase = kieHelper.addResource(ResourceFactory.newClassPathResource("MyProcess.bpmn")).build();
This is a manual method that you can use to create simple knowledge base. It uses KieHelper and ResourceFactory that are a part of Internal APIs org.kie.internal.io.ResourceFactory and org.kie.internal.utils.KieHelper. Using RuntimeManager is a recommended way to create knowledge base and knowledge session.

Note

The classes belonging to the Internal API (org.kie.internal) are not supported as they are subject to change.
A KieBase is a repository of all the application's knowledge definitions. It contains rules, processes, functions, and type models. The KieBase itself does not contain data; instead, sessions are created from the KieBase into which data can be inserted, and, ultimately, process instances may be started. Creating the KieBase can be quite heavy, whereas session creation is very light; therefore, it is recommended that KieBase be cached where possible to allow for repeated session creation. Accordingly, the caching mechanism is automatically provided by the KieContainer.

Table 15.1. kbase Attributes

Attribute name Default value Admitted values Meaning
name none any The name which retrieves the KieBase from the KieContainer. This is the only mandatory attribute.
includes none any comma separated list A comma separated list of other KieBases contained in this kmodule. The artifacts of all these KieBases will also be included in this one.
packages all any comma separated list By default all the JBoss BRMS artifacts under the resources folder, at any level, are included into the KieBase. This attribute allows to limit the artifacts that will be compiled in this KieBase to only the ones belonging to the list of packages.
default false true, false Defines if this KieBase is the default one for this module, so it can be created from the KieContainer without passing any name to it. There can be at most one default KieBase in each module.
equalsBehavior identity identity, equality Defines the behavior of JBoss BRMS when a new fact is inserted into the Working Memory. With identity it always create a new FactHandle unless the same object isn't already present in the Working Memory, while with equality only if the newly inserted object is not equal (according to its equal method) to an already existing fact.
eventProcessingMode cloud cloud, stream When compiled in cloud mode the KieBase treats events as normal facts, while in stream mode allow temporal reasoning on them.
declarativeAgenda disabled disabled, enabled Defines if the Declarative Agenda is enabled or not.

15.2.3. KieSession

Once you load your knowledge base, you must then create a session to interact with the engine. You can then use this session to start new processes, and signal events. Here is how you create a session:
KieSession ksession = kbase.newKieSession();

ProcessInstance processInstance = ksession.startProcess("com.sample.MyProcess");
Add the following import statement to get access to the process instance:
import org.kie.api.runtime.process.ProcessRuntime;
The KieSession stores and executes on runtime data. It is created from the KieBase, or, more easily, created directly from the KieContainer if it has been defined in the kmodule.xml file.

Note

In case where the JBoss BPM Suite engine is managed within a Container Managed Transaction (CMT) environment and the transactions are out of control of the engine, concurrent access to the same session instance may lead to errors. To handle this situation, an interceptor is provided that locks the KieSession for a single thread until the transaction completes. This enables you to safely use KieSession in a CMT environment. To enable this interceptor, set the system property org.kie.tx.lock.enabled and the environment entry TRANSACTION_LOCK_ENABLED to true. The default value of these properties is false.

Table 15.2. ksession Attributes

Attribute name Default value Admitted values Meaning
name none any Unique name of this KieSession. Used to fetch the KieSession from the KieContainer. This is the only mandatory attribute.
type stateful stateful, stateless A stateful session allows to iteratively work with the Working Memory, while a stateless one is a one-off execution of a Working Memory with a provided data set.
default false true, false Defines if this KieSession is the default one for this module, so it can be created from the KieContainer without passing any name to it. In each module there can be at most one default KieSession for each type.
clockType realtime realtime, pseudo Defines if events timestamps are determined by the system clock or by a psuedo clock controlled by the application. This clock is specially useful for unit testing temporal rules.
beliefSystem simple simple, jtms, defeasible Defines the type of belief system used by the KieSession.

15.2.3.1. The ProcessRuntime Interface

The ProcessRuntime interface defines all the session methods for interacting with processes as shown below.
 /**

     * Start a new process instance.  The process (definition) that should

     * be used is referenced by the given process id.

     * 

     * @param processId  The id of the process that should be started

     * @return the ProcessInstance that represents the instance of the process that was started

     */

    ProcessInstance startProcess(String processId);


    /**

     * Start a new process instance.  The process (definition) that should

     * be used is referenced by the given process id.  Parameters can be passed

     * to the process instance (as name-value pairs), and these will be set

     * as variables of the process instance. 

     * 

     * @param processId  the id of the process that should be started

     * @param parameters  the process variables that should be set when starting the process instance 

     * @return the ProcessInstance that represents the instance of the process that was started

     */

    ProcessInstance startProcess(String processId,

                                 Map<String, Object> parameters);


    /**

     * Signals the engine that an event has occurred. The type parameter defines

     * which type of event and the event parameter can contain additional information

     * related to the event.  All process instances that are listening to this type

     * of (external) event will be notified.  For performance reasons, this type of event

     * signaling should only be used if one process instance should be able to notify

     * other process instances. For internal event within one process instance, use the

     * signalEvent method that also include the processInstanceId of the process instance

     * in question. 

     * 

     * @param type the type of event

     * @param event the data associated with this event

     */

    void signalEvent(String type,

                     Object event);


    /**

     * Signals the process instance that an event has occurred. The type parameter defines

     * which type of event and the event parameter can contain additional information

     * related to the event.  All node instances inside the given process instance that

     * are listening to this type of (internal) event will be notified.  Note that the event

     * will only be processed inside the given process instance.  All other process instances

     * waiting for this type of event will not be notified.

     * 

     * @param type the type of event

     * @param event the data associated with this event

     * @param processInstanceId the id of the process instance that should be signaled

     */

    void signalEvent(String type,

                     Object event,

                     long processInstanceId);


    /**

     * Returns a collection of currently active process instances.  Note that only process

     * instances that are currently loaded and active inside the engine will be returned.

     * When using persistence, it is likely not all running process instances will be loaded

     * as their state will be stored persistently.  It is recommended not to use this

     * method to collect information about the state of your process instances but to use

     * a history log for that purpose.

     * 

     * @return a collection of process instances currently active in the session

     */

    Collection<ProcessInstance> getProcessInstances();


    /**

     * Returns the process instance with the given id.  Note that only active process instances

     * will be returned.  If a process instance has been completed already, this method will return

     * null.

     * 

     * @param id the id of the process instance

     * @return the process instance with the given id or null if it cannot be found

     */

    ProcessInstance getProcessInstance(long processInstanceId);


    /**

     * Aborts the process instance with the given id.  If the process instance has been completed

     * (or aborted), or the process instance cannot be found, this method will throw an

     * IllegalArgumentException.

     * 

     * @param id the id of the process instance

     */

    void abortProcessInstance(long processInstanceId);


    /**

     * Returns the WorkItemManager related to this session.  This can be used to

     * register new WorkItemHandlers or to complete (or abort) WorkItems.

     * 

     * @return the WorkItemManager related to this session

     */

    WorkItemManager getWorkItemManager();

15.2.3.2. Event Listeners

The session provides methods for registering and removing listeners. You can use a ProcessEventListener class to listen to process-related events, such as starting or completing a process and entering and leaving a node. An event object provides access to related information, like the process instance and node instance linked to the event. You can use this API to register your own event listeners. Here is a list of methods of the ProcessEventListener class:
public interface ProcessEventListener {

  void beforeProcessStarted( ProcessStartedEvent event );
  void afterProcessStarted( ProcessStartedEvent event );
  void beforeProcessCompleted( ProcessCompletedEvent event );
  void afterProcessCompleted( ProcessCompletedEvent event );
  void beforeNodeTriggered( ProcessNodeTriggeredEvent event );
  void afterNodeTriggered( ProcessNodeTriggeredEvent event );
  void beforeNodeLeft( ProcessNodeLeftEvent event );
  void afterNodeLeft( ProcessNodeLeftEvent event );
  void beforeVariableChanged(ProcessVariableChangedEvent event);
  void afterVariableChanged(ProcessVariableChangedEvent event);

}
You need to add the following import statement to get access to the ProcessEventListener class:
import org.kie.api.event.process.ProcessEventListener;

15.2.3.3. Before and After Events

The before and after events behave like a stack. It means that any events that occur as a direct result of the previous event, it occurs between the before and the after of that event. For example, if a subsequent node is triggered as result of leaving a node, the node triggered events occur in between the beforeNodeLeftEvent and the afterNodeLeftEvent of the node that is left (as the triggering of the second node is a direct result of leaving the first node). This enables you to derive cause relationships between events more easily. Similarly, all node triggered and node left events that are the direct result of starting a process, occur between the beforeProcessStarted and afterProcessStarted events. In general, if you just want to be notified when a particular event occurs, you must check for the before events only (as they occur immediately before the event actually occurs). If you are only looking at the after events, you may get an impression of events firing in the wrong order. As the after events are triggered as a stack, they only fire when all events that were triggered as a result of this event have already fired. Use the after events only if you want to ensure that all processing related to this has ended. For example, when you want to be notified when starting of a particular process instance has ended.
Not all nodes always generate node triggered or node left events. Depending on the type of node, some nodes might only generate node left events, and others might only generate node triggered events. Catching intermediate events is like generating left events, as they are not really triggered by another node, rather activated from outside. Similarly, throwing intermediate events are not generating left events. They are only generating triggered events, as they are not really left, as they have no outgoing connection.

15.2.3.4. Correlation Keys

When working with processes, you may require to assign a given process instance some sort of business identifier to reference later without knowing the actual (generated) ID of the process instance. To provide such capabilities, JBoss BPM Suite allows you to use CorrelationKey that is composed of CorrelationProperties. CorrelationKey can have either a single property describing it or can be represented as multi valued properties set. The correlation feature, generally used for long running processes, requires you to enable persistence in order to permanently store correlation information.
Correlation capabilities are provided as part of the CorrelationAwareProcessRuntime interface. This interface that exposes following methods:

      /**

      * Start a new process instance.  The process (definition) that should

      * be used is referenced by the given process id.  Parameters can be passed

      * to the process instance (as name-value pairs), and these will be set

      * as variables of the process instance.

      *

      * @param processId  the id of the process that should be started

      * @param correlationKey custom correlation key that can be used to identify process instance

      * @param parameters  the process variables that should be set when starting the process instance

      * @return the ProcessInstance that represents the instance of the process that was started

      */

      ProcessInstance startProcess(String processId, CorrelationKey correlationKey, Map<String, Object> parameters);


      /**

      * Creates a new process instance (but does not yet start it).  The process

      * (definition) that should be used is referenced by the given process id.

      * Parameters can be passed to the process instance (as name-value pairs),

      * and these will be set as variables of the process instance.  You should only

      * use this method if you need a reference to the process instance before actually

      * starting it.  Otherwise, use startProcess.

      *

      * @param processId  the id of the process that should be started

      * @param correlationKey custom correlation key that can be used to identify process instance

      * @param parameters  the process variables that should be set when creating the process instance

      * @return the ProcessInstance that represents the instance of the process that was created (but not yet started)

      */

      ProcessInstance createProcessInstance(String processId, CorrelationKey correlationKey, Map<String, Object> parameters);


      /**

      * Returns the process instance with the given correlationKey.  Note that only active process instances

      * will be returned.  If a process instance has been completed already, this method will return

      * null.

      *

      * @param correlationKey the custom correlation key assigned when process instance was created

      * @return the process instance with the given id or null if it cannot be found

      */

      ProcessInstance getProcessInstance(CorrelationKey correlationKey);
Add the following import statement to get access to CorrelationAwareProcessRuntime:
import org.kie.internal.process.CorrelationAwareProcessRuntime;

15.2.3.5. Threads

Multi-threading can be classified into technical and logical multi-threading. Technical multi-threading occurs when multiple threads or processes are started on a computer. Logical multi-threading occurs in a BPM process, say after a process reaches a parallel gateway. The original process then splits into two processes that are executed in parallel.
The JBoss BPM Suite engine supports logical multi-threading. The logical multi-threading is implemented using one thread, which is a JBoss BPM Suite process that includes logical multi-threading. This process is executed in only one technical thread. The reason behind this implementation is that multiple technical threads need to be able to communicate state information with each other, if they are working on the same process. While multi-threading provides performance benefits, the extra logic used to ensure the different threads work together well, means that this is not guaranteed. There is an additional overhead of avoiding race conditions and deadlocks.
In general, the JBoss BPM Suite engine executes actions in serial. For example, when the engine encounters a script task in a process, it synchronously executes that script and waits for it to complete before continuing execution. Similarly, if a process encounters a parallel gateway, it sequentially triggers each of the outgoing branches, one after the other. This is possible since execution is almost always instantaneous. As a result, you may not even notice this. Similarly, action scripts in a process are also synchronously executed, and the engine waits for them to finish before continuing the process. For example, doing a Thread.sleep(...) as part of a script does not make the engine continue execution elsewhere, but blocks the engine thread during that period. The same principle applies to service tasks. When a service task is reached in a process, the engine invokes the handler of this service synchronously. The engine waits for the completeWorkItem(...) method to return before continuing execution. It is important that your service handler executes your service asynchronously if its execution is not instantaneous. An example of this is a service task that invokes an external service. Since the delay in invoking this service remotely and waiting for the results may take too long, invoking this service asynchronously is advised. This means that the handler only invokes the service and notifies the engine later when the results are available. In the mean time, the process engine then continues execution of the process.
Human tasks are a typical example of a service that needs to be invoked asynchronously, as the engine does not have to wait until a human actor responds to the request. The human task handler only creates a new task when the human task node is triggered. The engine then is able to continue execution on the rest of the process (if necessary) and the handler notifies the engine asynchronously when the user completes the task.

15.2.4. KieFileSystem

It is also possible to define the KieBases and KieSessions belonging to a KieModule programmatically instead of the declarative definition in the kmodule.xml file. The same programmatic API also allows in explicitly adding the file containing the Kie artifacts instead of automatically read them from the resources folder of your project. To do that it is necessary to create a KieFileSystem, a sort of virtual file system, and add all the resources contained in your project to it.
Like all other Kie core components you can obtain an instance of the KieFileSystem from the KieServices. The kmodule.xml configuration file must be added to the filesystem. This is a mandatory step. Kie also provides a convenient fluent API, implemented by the KieModuleModel, to programmatically create this file.
To do this in practice it is necessary to create a KieModuleModel from the KieServices, configure it with the desired KieBases and KieSessions, convert it in XML and add the XML to the KieFileSystem. This process is shown by the following example:

Example 15.1. Creating a kmodule.xml programmatically and adding it to a KieFileSystem

import org.kie.api.KieServices;
import org.kie.api.builder.model.KieModuleModel;
import org.kie.api.builder.model.KieBaseModel;
import org.kie.api.builder.model.KieSessionModel;
import org.kie.api.builder.KieFileSystem;
//...
KieServices kieServices = KieServices.Factory.get();
KieModuleModel kieModuleModel = kieServices.newKieModuleModel();

KieBaseModel kieBaseModel1 = kieModuleModel.newKieBaseModel( "KBase1 ")
        .setDefault( true )
        .setEqualsBehavior( EqualityBehaviorOption.EQUALITY )
        .setEventProcessingMode( EventProcessingOption.STREAM );

KieSessionModel ksessionModel1 = kieBaseModel1.newKieSessionModel( "KSession1" )
        .setDefault( true )
        .setType( KieSessionModel.KieSessionType.STATEFUL )
        .setClockType( ClockTypeOption.get("realtime") );

KieFileSystem kfs = kieServices.newKieFileSystem();
At this point it is also necessary to add to the KieFileSystem, through its fluent API, all others Kie artifacts composing your project. These artifacts have to be added in the same position of a corresponding usual Maven project.

15.2.5. KieResources

Example 15.2. Adding Kie artifacts to a KieFileSystem

import org.kie.api.builder.KieFileSystem;
		
KieFileSystem kfs = ...
kfs.write( "src/main/resources/KBase1/ruleSet1.drl", stringContainingAValidDRL )
        .write( "src/main/resources/dtable.xls",
                kieServices.getResources().newInputStreamResource( dtableFileStream ) );
This example shows that it is possible to add the Kie artifacts both as plain Strings and as Resources. In the latter case the Resources can be created by the KieResources factory, also provided by the KieServices. The KieResources provides many convenient factory methods to convert an InputStream, a URL, a File, or a String representing a path of your file system to a Resource that can be managed by the KieFileSystem.
Normally the type of a Resource can be inferred from the extension of the name used to add it to the KieFileSystem. However it also possible to not follow the Kie conventions about file extensions and explicitly assign a specific ResourceType to a Resource as shown below:

Example 15.3. Creating and adding a Resource with an explicit type

import org.kie.api.builder.KieFileSystem;

KieFileSystem kfs = ...
kfs.write( "src/main/resources/myDrl.txt",
           kieServices.getResources().newInputStreamResource( drlStream )
                      .setResourceType(ResourceType.DRL) );
Add all the resources to the KieFileSystem and build it by passing the KieFileSystem to a KieBuilder
When the contents of a KieFileSystem are successfully built, the resulting KieModule is automatically added to the KieRepository. The KieRepository is a singleton acting as a repository for all the available KieModules.