Bonita Engine Event handlers

An event handler is an extension to the engine that is configured to run when a specified event occurs.

For Subscription editions only.

Event handlers can help in adding new features to the Platform. You can use them to call your own services when specific events happen like when a case starts, or when a user submits a task.

An event is a change to any object in the database (user, activity, processDefinition,…​ ). You can create an event handler to track any change to any object in the database and take the appropriate action. For example:

  • Catch PROCESSINSTANCE_CREATED to detect that a process instance has started, and notify the process supervisor.

  • Catch ACTIVITYINSTANCE_STATE_UPDATED to detect that a human task become available, and send email to all the users eligible to perform it.

At the end of this page there is a list of all the events.

Constraints

The platform executes events handlers synchronously at the exact moment the related entity is updated, therefore it executes it in the same thread and same transaction that the one executing the element.

It means that:

  • Events handlers should never block, i.e. no network call, no I/O on the filesystem.

  • Events handlers should never throw exception, if it does, the related element execution will fail, meaning that the transaction is rolled back and the task (for instance) goes to the failed state, which is generally NOT what one wants.

In a cluster environment, Event handlers must be Serializable (implements Serializable and have all its fields serializable or transient) because they are shared with other nodes of the cluster.

What can be done is:

  • Calling your own services that should be retrieved statically. e.g. pushing information to a queue on which you will poll data.

What should not be done is:

  • Call Rest APIs: Network call can drastically impact Bonita platform performance.

  • Call Bonita platform Java APIs: It will not work, APIs handle the transaction and when the handler is called, we are already in transaction.

If you still want to perform Network call or Bonita platform java APIs calls, you can do it by triggering those calls asynchronously using e.g. a message queue or an ExecutorService. Please note that the handler is triggered during the execution of a transaction, so it is better to trigger the processing after the current transaction. This can be done by registering a javax.transaction.Synchronization using the javax.transaction.TransactionManager

Example: deploy an event handler

This example shows an event handler that detects changes in the state of activity instances.

Create a maven project for event handler jar

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.bonitasoft.example</groupId>
    <artifactId>event-handler-example</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.release>17</maven.compiler.release>
        <bonita.version>10.0.0</bonita.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.bonitasoft.runtime</groupId>
                <artifactId>bonita-runtime-bom</artifactId>
                <version>${bonita.version}</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.bonitasoft.engine</groupId>
            <artifactId>bonita-server</artifactId>
        </dependency>
    </dependencies>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.11.0</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>

    <!-- Add required additional repositories -->
    <repositories>
        <repository>
            <id>restlet</id>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
            <url>https://maven.restlet.talend.com/</url>
        </repository>
        <repository>
            <id>terracotta</id>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
            <url>https://repo.terracotta.org/maven2/</url>
        </repository>
    </repositories>

</project>

Create event handler class:

Create a class that implements SHandler<SEvent>.

src/main/java/org/bonitasoft/example/EventHandlerExample.java
package org.bonitasoft.example;

import java.util.UUID;

import org.bonitasoft.engine.events.model.SEvent;
import org.bonitasoft.engine.events.model.SHandler;
import org.bonitasoft.engine.events.model.SHandlerExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class EventHandlerExample implements SHandler<SEvent> {

    private static Logger logger = LoggerFactory.getLogger(EventHandlerExample.class);

    private final String identifier = UUID.randomUUID().toString();

    public EventHandlerExample() {
    }

    @Override
    public void execute(SEvent event) throws SHandlerExecutionException {
        logger.info("ExampleHandler: executing event {}", event.getType());

        // add your business logic here
    }

    @Override
    public boolean isInterested(SEvent event) {
        logger.info("ExampleHandler - event {} - asks if we are interested in handling this event instance",
         event.getType());
        // add your business logic here
        // for this example purpose, assume we are always interested
        return true;
    }

    @Override
    public String getIdentifier() {
        return identifier;
    }
}

Deploy jar

  • Build event-handle-example-1.0-SNAPSHOT.jar using mvn clean install maven command.

  • Copy event-handle-example-1.0-SNAPSHOT.jar in BUNDLE_HOME/server/webapps/bonita/WEB-INF/lib/ folder (for Tomcat bundle)

Register an event handler

An event handler is registered on an event by adding an entry to the appropriate map. The list of handlers registered can be extended in the bonita-tenant-sp-custom.xml file located in BUNDLE_HOME/setup/platform_conf/initial/tenant_template_engine folder (or platform_conf/current/tenant_template_engine if runtime has already been launched):

bonita-tenant-sp-custom.xml
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <!-- add event handler bean definition -->
    <bean id="myEventHandlerExample" class="org.bonitasoft.example.EventHandlerExample">
    </bean>

    <bean id="eventHandlers" class="org.springframework.beans.factory.config.MapFactoryBean">
        <property name="targetMapClass">
            <value>java.util.HashMap</value>
        </property>
        <property name="sourceMap">
            <map>
                <entry key="PROCESSINSTANCE_STATE_UPDATED" value-ref="myEventHandlerExample"/>
            </map>
        </property>
    </bean>

</beans>

Test it

Restart web server and run a basic process and check bonita log file in folder tomcat/logs:

INFOS: THREAD_ID=78 | HOSTNAME=gt | ExampleHandler: event PROCESSINSTANCE_STATE_UPDATED - asks if we are interested in handling this event instance
...
INFOS: THREAD_ID=78 | HOSTNAME=gt | ExampleHandler: executing event PROCESSINSTANCE_STATE_UPDATED

Filter an event

An event handler contains a filter, isInterested, which detects the relevant instances of the event. The example below shows how to use the State Id of a flow node to filter for a particular state (in this case, failed). Flownode State Ids are defined in the subclasses of org.bonitasoft.engine.core.process.instance.api.states.FlowNodeState. There is no exhaustive list; the set of states is extensible without notice.

@Override
public boolean isInterested(SEvent event) {
    boolean isInterested = false;

    // Get the object associated with the event
    Object eventObject = event.getObject();

    // Check that event is related to a task
    if (eventObject instanceof SFlowNodeInstance) {
        SFlowNodeInstance flowNodeInstance = (SFlowNodeInstance) eventObject;

        // Verify that state of the task is failed. See
        // FailedActivityStateImpl
        isInterested = (flowNodeInstance.getStateId() == 3);
    }

    return isInterested;
}

Event handlers are recursive, that is, if an event handler itself modifies something and triggers an event, the relevant event handler is called. This means you might need to include loop detection in your event handler.

Event list

This is a snapshot of the events used in the Engine.

Service

Events

ActivityInstanceServiceImpl

ACTIVITYINSTANCE_CREATED, HUMAN_TASK_INSTANCE_ASSIGNEE_UPDATED, ACTIVITYINSTANCE_STATE_UPDATED, ACTIVITY_INSTANCE_TOKEN_COUNT_UPDATED, HIDDEN_TASK_CREATED, HIDDEN_TASK_DELETED, PENDINGACTIVITYMAPPING_CREATED, PENDINGACTIVITYMAPPING_DELETED

ActorMappingServiceImpl

ACTOR_CREATED, ACTOR_DELETED, ACTOR_UPDATED, ACTOR_MEMBER_CREATED, ACTOR_MEMBER_DELETED

CategoryServiceImpl

CATEGORY_CREATED, CATEGORY_DELETED, CATEGORY_UPDATED

CommandServiceImpl

COMMAND_CREATED, COMMAND_DELETED, COMMAND_UPDATED

SCommentServiceImpl

COMMENT_CREATED, COMMENT_DELETED

ConnectorInstanceServiceImpl

CONNECTOR_INSTANCE_CREATED, CONNECTOR_INSTANCE_DELETED, CONNECTOR_INSTANCE_STATE_UPDATED, CONNECTOR_INSTANCE_UPDATED

DependencyServiceImpl

DEPENDENCY_CREATED, DEPENDENCYMAPPING_CREATED, DEPENDENCY_DELETED, DEPENDENCYMAPPING_DELETED, DEPENDENCY_UPDATED, DEPENDENCYMAPPING_UPDATED

DocumentMappingServiceImpl

DOCUMENTMAPPING_CREATED, DOCUMENTMAPPING_DELETED, DOCUMENTMAPPING_UPDATED

SEventInstanceServiceImpl

EVENT_INSTANCE_CREATED, EVENT_TRIGGER_INSTANCE_CREATED, EVENT_TRIGGER_INSTANCE_DELETED, MESSAGE_INSTANCE_CREATED, MESSAGE_INSTANCE_DELETED, MESSAGE_INSTANCE_UPDATED

ExternalIdentityMappingServiceImpl

EXTERNAL_IDENTITY_MAPPING_CREATED, EXTERNAL_IDENTITY_MAPPING_DELETED

FlowNodeInstanceServiceImpl

FLOWNODE_INSTANCE_DELETED

GatewayInstanceServiceImpl

GATEWAYINSTANCE_CREATED, GATEWAYINSTANCE_HITBYS_UPDATED, GATEWAYINSTANCE_STATE_UPDATED

IdentityServiceImpl

GROUP_CREATED, GROUP_DELETED, GROUP_UPDATED, METADATA_CREATED, METADATA_DELETED, METADATA_UPDATED, METADATAVALUE_CREATED, METADATAVALUE_DELETED, METADATAVALUE_UPDATED, ROLE_UPDATED, ROLE_CREATED, ROLE_DELETED, USER_UPDATED, USER_CREATED, USER_DELETED, USER_CONTACT_INFO_UPDATED, USER_CONTACT_INFO_CREATED, USER_CONTACT_INFO_DELETED, USERMEMBERSHIP_UPDATED, USERMEMBERSHIP_CREATED, USERMEMBERSHIP_DELETED

JobServiceImpl

JOB_DESCRIPTOR_CREATED, JOB_DESCRIPTOR_DELETED, JOB_PARAMETER_CREATED, JOB_PARAMETER_DELETED, JOB_LOG_CREATED, JOB_LOG_DELETED

JobWrapper

JOB_COMPLETED, JOB_EXECUTING

ProcessDefinitionServiceImpl

PROCESSDEFINITION_CREATED, PROCESSDEFINITION_DELETED, PROCESSDEFINITION_DEPLOY_INFO_UPDATED, PROCESSDEFINITION_IS_DISABLED_UPDATED, PROCESSDEFINITION_IS_ENABLED_UPDATED, PROCESSDEFINITION_IS_RESOLVED_UPDATED

ProcessInstanceServiceImpl

PROCESS_INSTANCE_CATEGORY_STATE_UPDATED, PROCESSINSTANCE_CREATED, PROCESSINSTANCE_DELETED, PROCESSINSTANCE_STATE_UPDATED, PROCESSINSTANCE_UPDATED

ProfileServiceImpl

PROFILE_CREATED, PROFILE_DELETED, PROFILE_UPDATED, PROFILE_MEMBER_CREATED, PROFILE_MEMBER_DELETED

SupervisorMappingServiceImpl

SUPERVISOR_CREATED, SUPERVISOR_DELETED