1. Introduction

1.1. License

Flowable is distributed under the Apache V2 license.

1.3. Sources

The distribution contains most of the sources as JAR files. The source code of Flowable can be found on https://github.com/flowable/flowable-engine

1.4. Experimental Release

This is the first release of Flowable containing the CMMN engine, which is currently marked as experimental. Similar to experimental features in the other engines, this means that no stability guarantees are given with regards to backwards compatibility (packages, classes, data), as we want to use this release to gather feedback from the community before making the API’s fully final. However, future release notes will describe the changes if any such changes are made.

In the next releases, we will enhance and expand on the CMMN 1.1 support and continue to add more features.

1.5. Required software

Flowable runs on a JDK higher than or equal to version 7. Go to Oracle Java SE downloads and click on button "Download JDK". There are installation instructions on that page as well. To verify that your installation was successful, run java -version on the command line. That should print the installed version of your JDK.

1.6. Reporting problems

Questions and comments can be discussed on the on the Flowable forum. Issues can be created in our Github issue tracker.

1.7. Internal implementation classes

In the JAR file, all classes in packages that have .impl. in them are implementation classes and should be considered internal. No stability guarantees are given on classes or interfaces that are in implementation classes.

2. Configuration

2.1. Creating a CmmnEngine

The Flowable CMMN engine is configured through an XML file called flowable.cmmn.cfg.xml. Note that this is not applicable if you’re using the Spring style of building a process engine.

The easiest way to obtain a CmmnEngine is to use the org.flowable.cmmn.engine.CmmnEngineConfiguration class:

1
CmmnEngine cmmnEngine = CmmnEngineConfiguration.createStandaloneCmmnEngineConfiguration();

This will look for a flowable.cmmn.cfg.xml file on the classpath and construct an engine based on the configuration in that file. The following snippet shows an example configuration. The following sections will give a detailed overview of the configuration properties.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="cmmnEngineConfiguration" class="org.flowable.cmmn.engine.CmmnEngineConfiguration"> <property name="jdbcUrl" value="jdbc:h2:mem:flowable;DB_CLOSE_DELAY=1000" /> <property name="jdbcDriver" value="org.h2.Driver" /> <property name="jdbcUsername" value="sa" /> <property name="jdbcPassword" value="" /> <property name="databaseSchemaUpdate" value="true" /> </bean> </beans>

Note that the configuration XML is in fact a Spring configuration. This does not mean that Flowable can only be used in a Spring environment! We are simply leveraging the parsing and dependency injection capabilities of Spring internally for building up the engine.

The CmmnEngineConfiguration object can also be created programmatically using the configuration file. It is also possible to use a different bean id (for example, see line 3).

1 2 3 4 5 6
CmmnEngineConfiguration. createCmmnEngineConfigurationFromResourceDefault(); createCmmnEngineConfigurationFromResource(String resource); createCmmnEngineConfigurationFromResource(String resource, String beanName); createCmmnEngineConfigurationFromInputStream(InputStream inputStream); createCmmnEngineConfigurationFromInputStream(InputStream inputStream, String beanName);

It is also possible not to use a configuration file, and create a configuration based on defaults (see the different supported classes for more information).

1 2
CmmnEngineConfiguration.createStandaloneCmmnEngineConfiguration(); CmmnEngineConfiguration.createStandaloneInMemCmmnEngineConfiguration();

All these CmmnEngineConfiguration.createXXX() methods return a CmmnEngineConfiguration that can be tweaked further if needed. After calling the buildCmmnEngine() operation, a CmmnEngine is created:

1 2 3 4
CmmnEngine cmmnEngine = CmmnEngineConfiguration.createStandaloneInMemCmmnEngineConfiguration() .setDatabaseSchemaUpdate(CmmnEngineConfiguration.DB_SCHEMA_UPDATE_TRUE) .setJdbcUrl("jdbc:h2:mem:my-own-db;DB_CLOSE_DELAY=1000") .buildCmmnEngine();

2.2. CmmnEngineConfiguration bean

The flowable.cmmn.cfg.xml must contain a bean that has the id 'cmmnEngineConfiguration'.

1
<bean id="cmmnEngineConfiguration" class="org.flowable.cmmn.engine.CmmnEngineConfiguration">

This bean is then used to construct the CmmnEngine. There are multiple classes available that can be used to define the cmmnEngineConfiguration. These classes represent different environments, and set defaults accordingly. It’s best practice to select the class that best matches your environment, to minimize the number of properties needed to configure the engine. The following classes are currently available:

  • org.flowable.cmmn.engine.impl.cfg.StandaloneInMemCmmnEngineConfiguration: this is a convenience class for unit testing purposes. Flowable will take care of all transactions. An H2 in-memory database is used by default. The database will be created and dropped when the engine boots and shuts down. When using this, no additional configuration is probably needed.

  • org.flowable.cmmn.spring.SpringCmmnEngineConfiguration: To be used when the CMMN engine is used in a Spring environment. See the Spring integration section for more information.

2.3. Database configuration

There are two ways to configure the database that the Flowable CMMN engine will use. The first option is to define the JDBC properties of the database:

  • jdbcUrl: JDBC URL of the database.

  • jdbcDriver: implementation of the driver for the specific database type.

  • jdbcUsername: username to connect to the database.

  • jdbcPassword: password to connect to the database.

The data source that is constructed based on the provided JDBC properties will have the default MyBatis connection pool settings. The following attributes can optionally be set to tweak that connection pool (taken from the MyBatis documentation):

  • jdbcMaxActiveConnections: The number of active connections that the connection pool at maximum at any time can contain. Default is 10.

  • jdbcMaxIdleConnections: The number of idle connections that the connection pool at maximum at any time can contain.

  • jdbcMaxCheckoutTime: The amount of time in milliseconds a connection can be checked out from the connection pool before it is forcefully returned. Default is 20000 (20 seconds).

  • jdbcMaxWaitTime: This is a low level setting that gives the pool a chance to print a log status and re-attempt the acquisition of a connection in the case that it is taking unusually long (to avoid failing silently forever if the pool is misconfigured) Default is 20000 (20 seconds).

Example database configuration:

1 2 3 4
<property name="jdbcUrl" value="jdbc:h2:mem:flowable;DB_CLOSE_DELAY=1000" /> <property name="jdbcDriver" value="org.h2.Driver" /> <property name="jdbcUsername" value="sa" /> <property name="jdbcPassword" value="" />

Our benchmarks have shown that the MyBatis connection pool is not the most efficient or resilient when dealing with a lot of concurrent requests. As such, it is advised to us a javax.sql.DataSource implementation and inject it into the process engine configuration (For example DBCP, C3P0, Hikari, Tomcat Connection Pool, etc.):

1 2 3 4 5 6 7 8 9 10 11 12
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" > <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/flowable" /> <property name="username" value="flowable" /> <property name="password" value="flowable" /> <property name="defaultAutoCommit" value="false" /> </bean> <bean id="cmmnEngineConfiguration" class="org.flowable.cmmn.engine.CmmnEngineConfiguration"> <property name="dataSource" ref="dataSource" /> ...

Note that Flowable does not ship with a library that allows you to define such a data source. So you need to make sure that the libraries are on your classpath.

The following properties can be set, regardless of whether you are using the JDBC or data source approach:

  • databaseType: it’s normally not necessary to specify this property, as it is automatically detected from the database connection metadata. Should only be specified when automatic detection fails. Possible values: {h2, mysql, oracle, postgres, mssql, db2}. This setting will determine which create/drop scripts and queries will be used. See the supported databases section for an overview of which types are supported.

  • databaseSchemaUpdate: sets the strategy to handle the database schema on process engine boot and shutdown.

    • false (default): Checks the version of the DB schema against the library when the process engine is being created and throws an exception if the versions don’t match.

    • true: Upon building the process engine, a check is performed and an update of the schema is performed if it is necessary. If the schema doesn’t exist, it is created.

    • create-drop: Creates the schema when the process engine is being created and drops the schema when the process engine is being closed.

2.4. JNDI Datasource Configuration

By default, the database configuration for Flowable is contained within the db.properties files in the WEB-INF/classes of each web application. This isn’t always ideal because it requires users to either modify the db.properties in the Flowable source and recompile the WAR file, or explode the WAR and modify the db.properties on every deployment.

By using JNDI (Java Naming and Directory Interface) to obtain the database connection, the connection is fully managed by the Servlet Container and the configuration can be managed outside the WAR deployment. This also allows more control over the connection parameters than what is provided by the db.properties file.

2.4.1. Configuration

Configuration of the JNDI data source will differ depending on what servlet container application you are using. The instructions below will work for Tomcat, but for other container applications, please refer to the documentation for your container app.

If using Tomcat, the JNDI resource is configured within $CATALINA_BASE/conf/[enginename]/[hostname]/[warname].xml (for the Flowable UI this will usually be $CATALINA_BASE/conf/Catalina/localhost/flowable-app.xml). The default context is copied from the Flowable WAR file when the application is first deployed, so if it already exists, you will need to replace it. To change the JNDI resource so that the application connects to MySQL instead of H2, for example, change the file to the following:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
<?xml version="1.0" encoding="UTF-8"?> <Context antiJARLocking="true" path="/flowable-app"> <Resource auth="Container" name="jdbc/flowableDB" type="javax.sql.DataSource" description="JDBC DataSource" url="jdbc:mysql://localhost:3306/flowable" driverClassName="com.mysql.jdbc.Driver" username="sa" password="" defaultAutoCommit="false" initialSize="5" maxWait="5000" maxActive="120" maxIdle="5"/> </Context>

2.4.2. JNDI properties

To configure a JNDI data source, use following properties in the properties file for the Flowable UI:

  • datasource.jndi.name: the JNDI name of the data source.

  • datasource.jndi.resourceRef: Set whether the lookup occurs in a J2EE container, for example, the prefix "java:comp/env/" needs to be added if the JNDI name doesn’t already contain it. Default is "true".

2.5. Supported databases

Listed below are the types (case sensitive!) that Flowable uses to refer to databases.

Flowable database type Example JDBC URL Notes

h2

jdbc:h2:tcp://localhost/flowable

Default configured database

mysql

jdbc:mysql://localhost:3306/flowable?autoReconnect=true

Tested using mysql-connector-java database driver

oracle

jdbc:oracle:thin:@localhost:1521:xe

postgres

jdbc:postgresql://localhost:5432/flowable

db2

jdbc:db2://localhost:50000/flowable

mssql

jdbc:sqlserver://localhost:1433;databaseName=flowable (jdbc.driver=com.microsoft.sqlserver.jdbc.SQLServerDriver) OR jdbc:jtds:sqlserver://localhost:1433/flowable (jdbc.driver=net.sourceforge.jtds.jdbc.Driver)

Tested using Microsoft JDBC Driver 4.0 (sqljdbc4.jar) and JTDS Driver

2.6. Creating the database tables

The easiest way to create the database tables for your database is to:

  • Add the flowable-cmmn-engine JARs to your classpath

  • Add a suitable database driver

  • Add a Flowable configuration file (flowable.cmmn.cfg.xml) to your classpath, pointing to your database (see database configuration section)

  • Execute the main method of the DbSchemaCreate class

However, often only database administrators can execute DDL statements on a database. On a production system, this is also the wisest of choices. The SQL DDL statements can be found on the Flowable downloads page or inside the Flowable distribution folder, in the database subdirectory. The scripts are also in the engine JAR (flowable-cmmn-engine-x.jar), in the package org/flowable/cmmn/db/create . The SQL files are of the form

flowable.{db}.cmmn.create.sql

Where db is any of the supported databases.

2.7. Database table names explained

The database names of the Flowable CMMN Engine all start with ACT_CMMN_. The second part is a two-character identification of the use case of the table. This use case will also roughly match the service API.

  • ACT_CMMN_*: Tables without an additional prefix contain static information such as case definitions and case resources (images, rules, etc.).

  • ACT_RU_*: RU stands for runtime. These are the runtime tables that contain the runtime data of case instances, plan items, and so on. Flowable only stores the runtime data during case instance execution and removes the records when a case instance ends. This keeps the runtime tables small and fast.

  • ACT_HI_*: HI stands for history. These are the tables that contain historic data, such as past case instances, plan items, and so on.

2.8. Database upgrade

Make sure you make a backup of your database (using your database backup capabilities) before you run an upgrade.

By default, a version check will be performed each time a process engine is created. This typically happens once at boot time of your application or of the Flowable webapps. If the Flowable library notices a difference between the library version and the version of the Flowable database tables, then an exception is thrown.

To upgrade, you have to start by putting the following configuration property in your flowable.cmmn.cfg.xml configuration file:

1 2 3 4 5 6 7 8 9 10
<beans > <bean id="cmmnEngineConfiguration" class="org.flowable.cmmn.engine.CmmnEngineConfiguration"> <!-- ... --> <property name="databaseSchemaUpdate" value="true" /> <!-- ... --> </bean> </beans>

Also, include a suitable database driver for your database to the classpath. Upgrade the Flowable libraries in your application. Or start up a new version of Flowable and point it to a database that contains data from an older version. With databaseSchemaUpdate set to true, Flowable will automatically upgrade the DB schema to the newest version the first time when it notices that libraries and DB schema are out of sync.

As an alternative, you can also run the upgrade DDL statements. It’s also possible to run the upgrade database scripts available on the Flowable downloads page.

2.9. History configuration

Customizing the configuration of history storage is optional. This allows you to tweak settings that influence the history capabilities of the engine. See history configuration for more details.

1
<property name="history" value="audit" />

2.10. Exposing configuration beans in expressions and scripts

By default, all beans that you specify in the flowable.cmmn.cfg.xml configuration or in your own Spring configuration file are available to expressions and scripts. If you want to limit the visibility of beans in your configuration file, you can configure a property called beans in your process engine configuration. The beans property in CmmnEngineConfiguration is a map. When you specify that property, only beans specified in that map will be visible to expressions and scripts. The exposed beans will be exposed with the names as you specify in the map.

2.11. Deployment cache configuration

All case definitions are cached (after they’re parsed) to avoid hitting the database every time a case definition is needed and because case definition data doesn’t change. By default, there is no limit on this cache. To limit the case definition cache, add following property:

1
<property name="caseDefinitionCacheLimit" value="10" />

Setting this property will swap the default hashmap cache with a LRU cache that has the provided hard limit. Of course, the best value for this property depends on the total amount of case definitions stored and the number of case definitions actually used at runtime by all the runtime case instances.

You can also inject your own cache implementation. This must be a bean that implements the org.flowable.engine.common.impl.persistence.deploy.DeploymentCache interface:

1 2 3
<property name="caseDefinitionCache"> <bean class="org.flowable.MyCache" /> </property>

2.12. Logging

All logging (flowable, spring, mybatis, …​) is routed through SLF4J and allows for selecting the logging-implementation of your choice.

By default no SFL4J-binding JAR is present in the flowable-cmmn-engine dependencies, this should be added in your project in order to use the logging framework of your choice. If no implementation JAR is added, SLF4J will use a NOP-logger, not logging anything at all, other than a warning that nothing will be logged. For more info on these bindings http://www.slf4j.org/codes.html#StaticLoggerBinder.

With Maven, add for example a dependency like this (here using log4j), note that you still need to add a version:

1 2 3 4
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </dependency>

The flowable-ui and flowable-rest webapps are configured to use Log4j-binding. Log4j is also used when running the tests for all the flowable-* modules.

Important note when using a container with commons-logging in the classpath: In order to route the spring-logging through SLF4J, a bridge is used (see http://www.slf4j.org/legacy.html#jclOverSLF4J). If your container provides a commons-logging implementation, please follow directions on this page: http://www.slf4j.org/codes.html#release to ensure stability.

Example when using Maven (version omitted):

1 2 3 4
<dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> </dependency>

3. The Flowable CMMN API

3.1. The Process CMMN Engine API and services

The engine API is the most common way of interacting with Flowable. The main starting point is the CmmnEngine, which can be created in several ways as described in the configuration section. From the CmmnEngine, you can obtain the various services that contain the case/CMMN methods. CmmnEngine and the services objects are thread safe, so you can keep a reference to one of those for a whole server.

api.services
1 2 3 4 5 6 7
CmmnEngine cmmnEngine = CmmnEngineConfiguration.createStandaloneCmmnEngineConfiguration(); CmmnRuntimeService runtimeService = cmmnEngine.getCmmnRuntimeService(); CmmnRepositoryService repositoryService = cmmnEngine.getCmmnRepositoryService(); CmmnTaskService taskService = cmmnEngine.getCmmnTaskService(); CmmnManagementService managementService = cmmnEngine.getCmmnManagementService(); CmmnHistoryService historyService = cmmnEngine.getCmmnHistoryService();

CmmnEngineConfiguration.createStandaloneCmmnEngineConfiguration() will initialize and build a CMMN engine and afterwards always return the CMMN engine.

The CmmnEngineConfiguration class will scan for all flowable.cmmn.cfg.xml and flowable-cmmn-context.xml files. For all flowable.cmmn.cfg.xml files, the CMMN engine will be built in the typical Flowable way: CmmnEngineConfiguration.createCmmnEngineConfigurationFromInputStream(inputStream).buildCmmnEngine(). For all flowable-cmmn-context.xml files, the CMMN engine will be built in the Spring way: first the Spring application context is created and then the CMMN engine is obtained from that application context.

All services are stateless. This means that you can easily run Flowable on multiple nodes in a cluster, each going to the same database, without having to worry about which machine actually executed previous calls. Any call to any service is idempotent regardless of where it is executed.

The CmmnRepositoryService is probably the first service needed when working with the Flowable CMMN engine. This service offers operations for managing and manipulating deployments and case definitions. Without going into much detail here, a case definition is a Java counterpart of the CMMN 1.1 case. It is a representation of the structure and behavior of each of the steps of a case. A deployment is the unit of packaging within the Flowable CMMN engine. A deployment can contain multiple CMMN 1.1 XML files and any other resource. The choice of what is included in one deployment is up to the developer. It can range from a single process CMMN 1.1 XML file to a whole package of cases and relevant resources (for example, the deployment hr-cases could contain everything related to HR cases). The CmmnRepositoryService can deploy such packages. Deploying a deployment means it is uploaded to the engine, where all cases are inspected and parsed before being stored in the database. From that point on, the deployment is known to the system and any process included in the deployment can now be started.

Furthermore, this service allows you to:

  • Query on deployments and case definitions known to the engine.

  • Retrieve various resources, such as files contained within the deployment or case diagrams that were auto-generated by the engine.

  • Retrieve a POJO version of the case definition, which can be used to introspect the case using Java rather than XML.

While the CmmnRepositoryService is mostly about static information (data that doesn’t change, or at least not a lot), the CmmnRuntimeService is quite the opposite. It deals with starting new case instances of case definitions. As said above, a case definition defines the structure and behavior of the different steps in a case. A case instance is one execution of such a case definition. For each case definition there typically are many instances running at the same time. The CmmnRuntimeService also is the service which is used to retrieve and store case variables. This is data that is specific to the given case instance and can be used by various constructs in the case (for example, a plan transition condition often uses process variables to determine which path is chosen to continue the case). The CmmnRuntimeservice also allows you to query on case instances and plan items. Plan items are a representation of the enabled plan items of CMMN 1.1. Lastly, the CmmnRuntimeService is used whenever a case instance is waiting for an external trigger and the case needs to be continued. A case instance can have various wait states and this service contains various operations to signal to the instance that the external trigger is received and the case instance can be continued.

Tasks that need to be performed by human users of the system are core to a CMMN engine such as Flowable. Everything around tasks is grouped in the CmmnTaskService, such as:

  • Querying tasks assigned to users or groups

  • Creating new standalone tasks. These are tasks that are not related to a process instances.

  • Manipulating to which user a task is assigned or which users are in some way involved with the task.

  • Claiming and completing a task. Claiming means that someone decided to be the assignee for the task, meaning that this user will complete the task. Completing means doing the work of the tasks. Typically this is filling in a form of sorts.

The CmmnHistoryService exposes all historical data gathered by the Flowable CMMN engine. When executing cases, a lot of data can be kept by the engine (this is configurable), such as case instance start times, who did which tasks, how long it took to complete the tasks, which path was followed in each case instance, and so on. This service exposes mainly query capabilities to access this data.

The CmmnManagementService is typically not needed when coding custom application using Flowable. It allows the retrieval of information about the database tables and table metadata.

For more detailed information on the service operations and the engine API, see the javadocs.

3.2. Exception strategy

The base exception in Flowable is the org.flowable.engine.common.api.FlowableException, an unchecked exception. This exception can be thrown at all times by the API, but expected exceptions that happen in specific methods are documented in the the javadocs. For example, an extract from CmmnTaskService:

1 2 3 4 5 6
/** * Called when the task is successfully executed. * @param taskId the id of the task to complete, cannot be null. * @throws FlowableObjectNotFoundException when no task exists with the given id. */ void complete(String taskId);

In the example above, when an id is passed for which no task exists, an exception will be thrown. Also, since the javadoc explicitly states that taskId cannot be null, an FlowableIllegalArgumentException will be thrown when null is passed.

Even though we want to avoid a big exception hierarchy, the following subclasses are thrown in specific cases. All other errors that occur during process-execution or API-invocation that don’t fit into the possible exceptions below, are thrown as regular FlowableExceptionss.

  • FlowableWrongDbException: Thrown when the Flowable engine discovers a mismatch between the database schema version and the engine version.

  • FlowableOptimisticLockingException: Thrown when an optimistic locking occurs in the data store caused by concurrent access of the same data entry.

  • FlowableClassLoadingException: Thrown when a class requested to load was not found or when an error occurred while loading it (e.g. JavaDelegates, TaskListeners, …​).

  • FlowableObjectNotFoundException: Thrown when an object that is requested or actioned does not exist.

  • FlowableIllegalArgumentException: An exception indicating that an illegal argument has been supplied in a Flowable API-call, an illegal value was configured in the engine’s configuration or an illegal value has been supplied or an illegal value is used in a process-definition.

  • FlowableTaskAlreadyClaimedException: Thrown when a task is already claimed, when the taskService.claim(…​) is called.

3.3. Query API

There are two ways of querying data from the engine: the query API and native queries. The Query API allows you to program completely typesafe queries with a fluent API. You can add various conditions to your queries (all of which are applied together as a logical AND) and precisely one ordering. The following code shows an example:

1 2 3 4
List<Task> tasks = taskService.createTaskQuery() .taskAssignee("kermit") .orderByDueDate().asc() .list();

Sometimes you need more powerful queries, for example, queries using an OR operator or restrictions you cannot express using the Query API. For these cases, we have native queries, which allow you to write your own SQL queries. The return type is defined by the Query object you use and the data is mapped into the correct objects (Task, CaseInstance, …​). Since the query will be fired at the database you have to use table and column names as they are defined in the database; this requires some knowledge about the internal data structure and it is recommended to use native queries with care. The table names can be retrieved through the API to keep the dependency as small as possible.

1 2 3 4 5 6 7 8 9 10
List<Task> tasks = taskService.createNativeTaskQuery() .sql("SELECT count(*) FROM " + managementService.getTableName(Task.class) + " T WHERE T.NAME_ = #{taskName}") .parameter("taskName", "gonzoTask") .list(); long count = taskService.createNativeTaskQuery() .sql("SELECT count(*) FROM " + managementService.getTableName(Task.class) + " T1, " + managementService.getTableName(VariableInstanceEntity.class) + " V1 WHERE V1.TASK_ID_ = T1.ID_") .count();

3.4. Variables

Every case instance needs and uses data to execute the steps it’s made up of. In Flowable, this data is called variables, which are stored in the database. Variables can be used in expressions (for example, in a condition of a transition), in Java service tasks when calling external services (for example to provide the input or store the result of the service call), and so on.

A case instance can have variables (called case variables), but also plan items (which are enabled plan items) and human tasks can have variables. A case instance can have any number of variables. Each variable is stored in a row in the ACT_RU_VARIABLE database table.

The createCaseInstanceBuilder method has optional methopds to provide the variables when the case instance is created and started. For example, from the CmmnRuntimeService:

1
CaseInstance caseInstance = runtimeService.createCaseInstanceBuilder().variable("var1", "test").start();

Variables can be added during case execution. For example, (CmmnRuntimeService):

1
void setVariables(String caseInstanceId, Map<String, ? extends Object> variables);

Variables can also be retrieved, as shown below. Note that similar methods exist on the CmmnTaskService.

1 2
Map<String, Object> getVariables(String caseInstanceId); Object getVariable(String caseInstanceId, String variableName);

Variables are often used in Java service tasks, expressions, scripts, and so on.

3.5. Transient variables

Transient variables are variables that behave like regular variables, but are not persisted. Typically, transient variables are used for advanced use cases. When in doubt, use a regular case variable.

The following applies for transient variables:

  • There is no history stored at all for transient variables.

  • Like regular variables, transient variables are put on the highest parent when set. This means that when setting a variable on an plan item, the transient variable is actually stored on the case instance execution. Like regular variables, a local variant of the method exists if the variable is set on the specific plan item or task.

  • A transient variable can only be accessed before the next wait state in the case definition. After that, they are gone. Here, the wait state means the point in the case instance where it is persisted to the data store.

  • Transient variables can only be set by the setTransientVariable(name, value), but transient variables are also returned when calling getVariable(name) (a getTransientVariable(name) also exists, that only checks the transient variables). The reason for this is to make the writing of expressions easy and existing logic using variables works for both types.

  • A transient variable shadows a persistent variable with the same name. This means that when both a persistent and transient variable is set on a case instance and getVariable("someVariable") is called, the transient variable value will be returned.

You can set and get transient variables in most places where regular variables are exposed:

  • On DelegatePlanItemInstance in PlanItemJavaDelegate implementations

  • When starting a case instance through the runtime service

  • When completing a task

The methods follow the naming convention of the regular case variables:

1
CaseInstance caseInstance = runtimeService.createCaseInstanceBuilder().transientVariable("var1", "test").start();

3.6. Expressions

Flowable uses UEL for expression-resolving. UEL stands for Unified Expression Language and is part of the EE6 specification (see the EE6 specification for detailed information).

Expressions can be used in, for example, Java Service tasks, and plan item transitions. Although there are two types of expressions, value-expression and method-expression, Flowable abstracts this so they can both be used where an expression is expected.

  • Value expression: resolves to a value. By default, all case variables are available to use. Also, all spring-beans (if using Spring) are available to use in expressions. Some examples:

${myVar}
${myBean.myProperty}
  • Method expression: invokes a method with or without parameters. When invoking a method without parameters, be sure to add empty parentheses after the method-name (as this distinguishes the expression from a value expression). The passed parameters can be literal values or expressions that are resolved themselves. Examples:

${printer.print()}
${myBean.addNewOrder('orderName')}
${myBean.doSomething(myVar, execution)}

Note that these expressions support resolving primitives (including comparing them), beans, lists, arrays and maps.

On top of all process variables, there are a few default objects available that can be used in expressions:

  • caseInstance: The DelegateCaseInstance holds additional information about the ongoing case instance.

  • planItemInstance: The DelegatePlanItemInstance holds additional information about the current plan item.

3.7. Unit testing

Cases are an integral part of software projects and they should be tested in the same way normal application logic is tested: with unit tests. Since Flowable is an embeddable Java engine, writing unit tests for business processes is as simple as writing regular unit tests.

Flowable supports JUnit versions 4 styles of unit testing.

The CmmnTestRunner can be used to initialise the CMMN engine and with the FlowableCmmnTestCase test base class you can easily write unit tests against a running CMMN engine.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
public class MyBusinessProcessTest extends FlowableCmmnTestCase { @Test @CmmnDeployment public void ruleUsageExample() { CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder() .caseDefinitionKey("myCase") .start(); assertNotNull(caseInstance); Task task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).singleResult(); assertEquals("Task 1", task.getName()); assertEquals("JohnDoe", task.getAssignee()); cmmnTaskService.complete(task.getId()); assertEquals(0, cmmnRuntimeService.createCaseInstanceQuery().count()); } }

4. Spring integration

While you can definitely use Flowable without Spring, we’ve provided some very nice integration features that are explained in this chapter.

4.1. CmmnEngineFactoryBean

The CmmnEngine can be configured as a regular Spring bean. The starting point of the integration is the class org.flowable.cmmn.spring.CmmnEngineFactoryBeann. That bean takes a CMMN engine configuration and creates the CMMN engine. This means that the creation and configuration of properties for Spring is the same as documented in the configuration section. For Spring integration, the configuration and engine beans will look like this:

1 2 3 4 5 6 7
<bean id="cmmnEngineConfiguration" class="org.flowable.cmmn.spring.SpringCmmnEngineConfiguration"> ... </bean> <bean id="cmmnEngine" class="org.flowable.cmmn.spring.CmmnEngineFactoryBean"> <property name="cmmnEngineConfiguration" ref="cmmnEngineConfiguration" /> </bean>

Note that the cmmnEngineConfiguration bean now uses the org.flowable.cmmn.spring.SpringCmmnEngineConfiguration class.

4.2. Default Spring configuration

The section shown below contains the dataSource, transactionManager, cmmnEngine and the Flowable engine services.

When passing the DataSource to the SpringCmmnEngineConfiguration (using property "dataSource"), Flowable uses a org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy internally, which wraps the passed DataSource. This is done to make sure the SQL connections retrieved from the DataSource and the Spring transactions play well together. This implies that it’s no longer necessary to proxy the dataSource yourself in Spring configuration, although it’s still possible to pass a TransactionAwareDataSourceProxy into the SpringCmmnEngineConfiguration. In this case, no additional wrapping will occur.

Make sure when declaring a TransactionAwareDataSourceProxy in Spring configuration yourself that you don’t use it for resources that are already aware of Spring transactions (e.g. DataSourceTransactionManager need the un-proxied dataSource).

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> <bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource"> <property name="driverClass" value="org.h2.Driver" /> <property name="url" value="jdbc:h2:mem:flowable;DB_CLOSE_DELAY=1000" /> <property name="username" value="sa" /> <property name="password" value="" /> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <bean id="cmmnEngineConfiguration" class="org.flowable.cmmn.spring.SpringCmmnEngineConfiguration"> <property name="dataSource" ref="dataSource" /> <property name="transactionManager" ref="transactionManager" /> <property name="databaseSchemaUpdate" value="true" /> </bean> <bean id="cmmnEngine" class="org.flowable.cmmn.spring.CmmnEngineFactoryBean"> <property name="cmmnEngineConfiguration" ref="cmmnEngineConfiguration" /> </bean> <bean id="cmmnRepositoryService" factory-bean="cmmnEngine" factory-method="getCmmnRepositoryService" /> <bean id="cmmnRuntimeService" factory-bean="cmmnEngine" factory-method="getCmmnRuntimeService" /> <bean id="cmmnTaskService" factory-bean="cmmnEngine" factory-method="getCmmnTaskService" /> <bean id="cmmnHistoryService" factory-bean="cmmnEngine" factory-method="getCmmnHistoryService" /> <bean id="cmmnManagementService" factory-bean="cmmnEngine" factory-method="getCmmnManagementService" /> ...

First, the application context is created using any of the ways supported by Spring. In this example, you could use a classpath XML resource to configure our Spring application context:

1 2
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext( "org/flowable/cmmn/examples/spring/SpringIntegrationTest-context.xml");

or, as it’s a test:

1 2
@ContextConfiguration( "classpath:org/flowable/cmmn/spring/test/SpringIntegrationTest-context.xml")

4.3. Expressions

When using the CmmnEngineFactoryBean, all expressions in the CMMN processes will also see all the Spring beans, by default. It’s possible to limit the beans (even none) you want to expose in expressions using a map that you can configure. The example below exposes a single bean (printer), available to use under the key "printer". To have NO beans exposed at all, just pass an empty list as beans property on the SpringCmmnEngineConfiguration. When no beans property is set, all Spring beans in the context will be available.

1 2 3 4 5 6 7 8 9 10
<bean id="cmmnEngineConfiguration" class="org.flowable.cmmn.spring.SpringCmmnEngineConfiguration"> ... <property name="beans"> <map> <entry key="printer" value-ref="printer" /> </map> </property> </bean> <bean id="printer" class="org.flowable.cmmn.examples.spring.Printer" />

Now the exposed beans can be used in expressions:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
... <case id="myCase"> <casePlanModel id="myPlanModel" name="My CasePlanModel"> <planItem id="planItem1" name="Task One" definitionRef="serviceTask" /> <planItem id="planItem2" name="The Case" definitionRef="task"> <entryCriterion sentryRef="sentry1" /> </planItem> <sentry id="sentry1"> <planItemOnPart sourceRef="planItem1"> <standardEvent>complete</standardEvent> </planItemOnPart> </sentry> <task id="serviceTask" flowable:type="java" flowable:expression="${printer.printMessage(var1)}" flowable:resultVariableName="customResponse" /> <task id="task" name="The Task" isBlocking="true" /> </casePlanModel> </case>

Where Printer looks like this:

1 2 3 4 5 6
public class Printer { public void printMessage(String var) { System.out.println("hello " + var); } }

And the Spring bean configuration (also shown above) looks like this:

1 2 3 4 5 6
<beans> ... <bean id="printer" class="org.flowable.cmmn.examples.spring.Printer" /> </beans>

4.4. Automatic resource deployment

Spring integration also has a special feature for deploying resources. In the CMMN engine configuration, you can specify a set of resources. When the CMMN engine is created, all those resources will be scanned and deployed. There is filtering in place that prevents duplicate deployments. Only when the resources have actually changed will new deployments be deployed to the Flowable DB. This makes sense in a lot of use cases, where the Spring container is rebooted frequently (for example, testing).

Here’s an example:

1 2 3 4 5 6 7 8 9
<bean id="cmmnEngineConfiguration" class="org.flowable.cmmn.spring.SpringCmmnEngineConfiguration"> ... <property name="deploymentResources" value="classpath*:/org/flowable/cmmn/spring/test/autodeployment/autodeploy.*.cmmn" /> </bean> <bean id="cmmnEngine" class="org.flowable.cmmn.spring.CmmnEngineFactoryBean"> <property name="cmmnEngineConfiguration" ref="cmmnEngineConfiguration" /> </bean>

By default, the configuration above will group all of the resources matching the filter into a single deployment to the Flowable engine. The duplicate filtering to prevent re-deployment of unchanged resources applies to the whole deployment. In some cases, this may not be what you want. For instance, if you deploy a set of process resources this way and only a single case definition in those resources has changed, the deployment as a whole will be considered new and all of the case definitions in that deployment will be re-deployed, resulting in new versions of each of the case definitions, even though only one was actually changed.

To be able to customize the way deployments are determined, you can specify an additional property in the SpringCmmnEngineConfiguration, deploymentMode. This property defines the way deployments will be determined from the set of resources that match the filter. There are 3 values that are supported by default for this property:

  • default: Group all resources into a single deployment and apply duplicate filtering to that deployment. This is the default value and it will be used if you don’t specify a value.

  • single-resource: Create a separate deployment for each individual resource and apply duplicate filtering to that deployment. This is the value you would use to have each process definition be deployed separately and only create a new case definition version if it has changed.

  • resource-parent-folder: Create a separate deployment for resources that share the same parent folder and apply duplicate filtering to that deployment. This value can be used to create separate deployments for most resources, but still be able to group some by placing them in a shared folder. Here’s an example of how to specify the single-resource configuration for deploymentMode:

1 2 3 4 5 6
<bean id="cmmnEngineConfiguration" class="org.flowable.cmmn.spring.SpringCmmnEngineConfiguration"> ... <property name="deploymentResources" value="classpath*:/flowable/*.cmmn" /> <property name="deploymentMode" value="single-resource" /> </bean>

In addition to using the values listed above for deploymentMode, you may require customized behavior towards determining deployments. If so, you can create a subclass of SpringCmmnEngineConfiguration and override the getAutoDeploymentStrategy(String deploymentMode) method. This method determines which deployment strategy is used for a certain value of the deploymentMode configuration.

4.5. Unit testing

When integrating with Spring, business processes can be tested very easily using the standard Flowable testing facilities. The following example shows how a case definition is tested in a typical Spring-based unit test:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
public class MyBusinessProcessTest { @Rule public FlowableCmmnRule cmmnRule = new FlowableCmmnRule("org/flowable/spring/test/el/SpringBeanTest-context.xml"); @Test public void simpleCaseTest() { cmmnRule.getCmmnRepositoryService().createDeployment().addClasspathResource("org/flowable/spring/test/el/springExpression.cmmn").deploy(); CmmnRuntimeService cmmnRuntimeService = cmmnRule.getCmmnRuntimeService(); CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder() .caseDefinitionKey("myCase") .variable("var1", "John Doe") .start(); Assert.assertNotNull(caseInstance); } }

5. Deployment

5.1. External resources

Case definitions live in the Flowable database. These case definitions can reference delegation classes when using Service Tasks or Spring beans from the Flowable configuration file. These classes and the Spring configuration file have to be available to all CMMN engines that may execute the case definitions.

5.1.1. Java classes

All custom classes that are used in your case definition (for example, PlanItemJavaDelegates used in Service Tasks) should be present on the engine’s classpath when an instance of the case is started.

During deployment of a case definition however, those classes don’t have to be present on the classpath. This means that your delegation classes don’t have to be on the classpath when deploying a new case definition, for example.

When you are using the demo setup and you want to add your custom classes, you should add a JAR containing your classes to the flowable-task or flowable-rest webapp lib. Don’t forget to include the dependencies of your custom classes (if any) as well. Alternatively, you can include your dependencies in the libraries directory of your Tomcat installation, ${tomcat.home}/lib.

5.1.2. Using Spring beans from a process

When expressions or scripts use Spring beans, those beans have to be available to the engine when executing the process definition. If you are building your own webapp and you configure your CMMN engine in your context as described in the spring integration section, that is straightforward. But bear in mind that you also should update the Flowable task and rest webapps with that context if you use it.

5.1.3. Creating a single app

Instead of making sure that all CMMN engines have all the delegation classes on their classpath and use the right Spring configuration, you may consider including the Flowable REST webapp inside your own webapp so that there is only a single CmmnEngine.

5.2. Versioning of case definitions

CMMN doesn’t have a notion of versioning. That is actually good, because the executable CMMN file will probably live in a version control system repository (such as Subversion, Git or Mercurial) as part of your development project. However, versions of case definitions are created in the engine as part of deployment. During deployment, Flowable will assign a version to the CaseDefinition before it is stored in the Flowable DB.

For each case definition in a deployment, the following steps are performed to initialize the properties key, version, name and id:

  • The case definition id attribute in the XML file is used as the case definition key property.

  • The case definition name attribute in the XML file is used as the case definition name property. If the name attribute is not specified, then the id attribute is used as the name.

  • The first time a case with a particular key is deployed, version 1 is assigned. For all subsequent deployments of case definitions with the same key, the version will be set 1 higher than the highest currently deployed version. The key property is used to distinguish case definitions.

Take for example the following case

1 2 3
<definitions id="myDefinitions" > <case id="myCase" name="My important case" > ...

When deploying this case definition, the case definition in the database will look like this:

id key name version

676

myCase

My important case

1

Suppose we now deploy an updated version of the same case (for example, changing some human tasks), but the id of the case definition remains the same. The case definition table will now contain the following entries:

id key name version

676

myCase

My important case

1

870

myCase

My important case

2

When the runtimeService.createCaseInstanceBuilder().caseDefinitionKey("myCase").start() is called, it will now use the case definition with version 2, as this is the latest version of the case definition.

Should we create a second case, as defined below and deploy this to Flowable, a third row will be added to the table.

1 2 3
<definitions id="myNewDefinitions" > <case id="myNewCase" name="My important case" > ...

The table will look like this:

id key name version

676

myCase

My important case

1

870

myCase

My important case

2

1033

myNewCase

My important case

1

Note how the key for the new case is different from our first case. Even though the name is the same (we should probably have changed that too), Flowable only considers the id attribute when distinguishing cases. The new case is therefore deployed with version 1.

5.3. Category

Both deployments and case definitions have user-defined categories. The case definition category is initialized with the value of the targetNamespace attribute in the CMMN XML: <definitions …​ targetNamespace="yourCategory" …​

The deployment category can also be specified in the API like this:

1 2 3 4 5
repositoryService .createDeployment() .category("yourCategory") ... .deploy();

6. CMMN 1.1

6.1. What is CMMN?

The Case Management Model and Notation (CMMN) is a standard notation and formal specification by the Object Management Group for repesenting case models.

Flowable contains:

  • A CMMN 1.1 modeler to create CMMN 1.1 case models

  • A Java engine that can import and execute CMMN 1.1 case models

  • A demonstration UI that executes the case models, allowing users to see and complete human tasks (and their forms)

6.2. Basic concepts and terminology

The following figure shows a simple CMMN 1.1 diagram:

cmmn basic concepts

A case model is always visualized as some sort of folder that contains all the case elements. Every case model contains a plan model onto which items will be planned.

The elements of a plan model are called plan items. Each plan item has a plan item definition that gives its type and possible configuration options at runtime. For example, in the figure above, there are three human task plan items and one milestone. Other examples of plan items are process tasks, case tasks and stages.

After having deployed a case model to the Flowable CMMN engine, it’s possible to start case instances based on this case model. The plan items defined in the case model similarly have plan item instance runtime representations that are exposed by, and can be queried using, the Flowable API.

Plan items can have sentries: a plan item is said to have entry criteria when a sentry "guards" its activation. These criteria specify conditions that must be satisfied to trigger the sentry. For example, in the figure above, the "Milestone One" plan item is available after a case instance is started, but it is activated (in CMMN 1.1 specification terminology: it moves from the available state to the active state) when both human task A and B are completed. Note that sentries can have complex expression in their if part, which are not visualized, allowing for much more complex functionality. Also note that there can be multiple sentries, although only one needs to be satisfied to trigger a state transition.

Plan items and the plan model can also have sentries with exit criteria, which specify conditions that trigger an exit from that particular plan item. In the figure above, the whole plan model is exited (as are all the child elements that are active at that moment), when human task C completes.

CMMN 1.1 defines a standard XML format in an XSD that is part of the specification. For information, the example in the figure above is represented in XML as shown below.

Some observations:

  • The four plan items above are in the XML and they reference their definition with a definitionRef. The actual definitions are at the bottom of the casePlanModel element

  • The plan items have criteria (entry or exit) that reference a sentry (not the other way around)

  • The XML also contains information on how the diagram is visualized (x and y coordinates, widths and heights, and so on), which are omitted below. These elements are important when exchanging case models with other CMMN 1.1 modeling tools to preserve the correct visual representation

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
<?xml version="1.0" encoding="UTF-8"?> <definitions xmlns="http://www.omg.org/spec/CMMN/20151109/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:flowable="http://flowable.org/cmmn" xmlns:cmmndi="http://www.omg.org/spec/CMMN/20151109/CMMNDI" xmlns:dc="http://www.omg.org/spec/CMMN/20151109/DC" xmlns:di="http://www.omg.org/spec/CMMN/20151109/DI" targetNamespace="http://www.flowable.org/casedef"> <case id="simpleExample" name="Simple Example"> <casePlanModel id="casePlanModel" name="My Case"> <planItem id="planItem1" name="Human task A" definitionRef="sid-88199E7C-7655-439C-810B-8849FC52D3EB"></planItem> <planItem id="planItem2" name="Milestone One" definitionRef="sid-8BF8A774-A8A7-4F1A-95CF-1E0D61EE5A47"> <entryCriterion id="sid-62CC4A6D-B29B-4129-93EA-460253C45CDF" sentryRef="sentry1"></entryCriterion> </planItem> <planItem id="planItem3" name="Human task B" definitionRef="sid-A1FB8733-0DBC-4B38-9830-CBC4D0C4B802"></planItem> <planItem id="planItem4" name="Human task C" definitionRef="sid-D3970AFC-7391-4BA7-95BA-51C64D2F41E9"></planItem> <sentry id="sentry1"> <planItemOnPart id="sentryOnPart1" sourceRef="planItem1"> <standardEvent>complete</standardEvent> </planItemOnPart> <planItemOnPart id="sentryOnPart2" sourceRef="planItem3"> <standardEvent>complete</standardEvent> </planItemOnPart> </sentry> <sentry id="sentry2"> <planItemOnPart id="sentryOnPart3" sourceRef="planItem4"> <standardEvent>complete</standardEvent> </planItemOnPart> </sentry> <humanTask id="sid-88199E7C-7655-439C-810B-8849FC52D3EB" name="Human task A"></humanTask> <milestone id="sid-8BF8A774-A8A7-4F1A-95CF-1E0D61EE5A47" name="Milestone One"></milestone> <humanTask id="sid-A1FB8733-0DBC-4B38-9830-CBC4D0C4B802" name="Human task B"></humanTask> <humanTask id="sid-D3970AFC-7391-4BA7-95BA-51C64D2F41E9" name="Human task C"></humanTask> <exitCriterion id="sid-422626DB-9B40-49D8-955E-641AB96A5BFA" sentryRef="sentry2"></exitCriterion> </casePlanModel> </case> <cmmndi:CMMNDI> <cmmndi:CMMNDiagram id="CMMNDiagram_simpleExample"> ... </cmmndi:CMMNDiagram> </cmmndi:CMMNDI> </definitions>

6.3. Programmatic example

In this section we’re going to build a simple case model and execute it programmatically through the Java API’s of the Flowable CMMN engine in a simple command line example.

The case model we’ll build is a (simplified) employee onboarding case with two stages: a phase before and phase after the potential employee has started. In the first stage, someone from the HR departement will complete the tasks, while in the second stage it is the employee completing them. Also, at any point in time, the potential employee can reject the job and stop the whole case instance.

Note that only stages and human tasks are used. In a real case model, there will most likely other plan item types too, like milestones, nested stages, automated tasks, etc.

cmmn.programmatic.example

The XML for this case models is the following:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
<?xml version="1.0" encoding="UTF-8"?> <definitions xmlns="http://www.omg.org/spec/CMMN/20151109/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:flowable="http://flowable.org/cmmn" xmlns:cmmndi="http://www.omg.org/spec/CMMN/20151109/CMMNDI" xmlns:dc="http://www.omg.org/spec/CMMN/20151109/DC" xmlns:di="http://www.omg.org/spec/CMMN/20151109/DI" targetNamespace="http://www.flowable.org/casedef"> <case id="employeeOnboarding" name="Simple Example"> <casePlanModel id="casePlanModel" name="My Case"> <planItem id="planItem5" name="Prior to starting" definitionRef="sid-025D29E8-BA9B-403D-A684-8C5B52185642"></planItem> <planItem id="planItem8" name="After starting" definitionRef="sid-8459EF32-4F4C-4E9B-A6E9-87FDC2299044"> <entryCriterion id="sid-50B5F12D-FE75-4D05-9148-86574EE6C073" sentryRef="sentry2"></entryCriterion> </planItem> <planItem id="planItem9" name="Reject job" definitionRef="sid-134E885A-3D58-417E-81E2-66A3E12334F9"></planItem> <sentry id="sentry2"> <planItemOnPart id="sentryOnPart4" sourceRef="planItem5"> <standardEvent>complete</standardEvent> </planItemOnPart> </sentry> <sentry id="sentry3"> <planItemOnPart id="sentryOnPart5" sourceRef="planItem9"> <standardEvent>complete</standardEvent> </planItemOnPart> </sentry> <stage id="sid-025D29E8-BA9B-403D-A684-8C5B52185642" name="Prior to starting"> <planItem id="planItem1" name="Create email address" definitionRef="sid-EA434DDD-E1BE-4AC1-8520-B19ACE8782D2"></planItem> <planItem id="planItem2" name="Allocate office" definitionRef="sid-505BA223-131A-4EF0-ABAD-485AEB0F2C96"></planItem> <planItem id="planItem3" name="Send joining letter to candidate" definitionRef="sid-D28DBAD5-0F5F-45F4-8553-3381199AC45F"> <entryCriterion id="sid-4D88C79D-8E31-4246-9541-A4F6A5720AC8" sentryRef="sentry1"></entryCriterion> </planItem> <planItem id="planItem4" name="Agree start date" definitionRef="sid-97A72C46-C0AD-477F-86DD-85EF643BB97D"></planItem> <sentry id="sentry1"> <planItemOnPart id="sentryOnPart1" sourceRef="planItem1"> <standardEvent>complete</standardEvent> </planItemOnPart> <planItemOnPart id="sentryOnPart2" sourceRef="planItem2"> <standardEvent>complete</standardEvent> </planItemOnPart> <planItemOnPart id="sentryOnPart3" sourceRef="planItem4"> <standardEvent>complete</standardEvent> </planItemOnPart> </sentry> <humanTask id="sid-EA434DDD-E1BE-4AC1-8520-B19ACE8782D2" name="Create email address" flowable:candidateGroups="hr"></humanTask> <humanTask id="sid-505BA223-131A-4EF0-ABAD-485AEB0F2C96" name="Allocate office" flowable:candidateGroups="hr"></humanTask> <humanTask id="sid-D28DBAD5-0F5F-45F4-8553-3381199AC45F" name="Send joining letter to candidate" flowable:candidateGroups="hr"></humanTask> <humanTask id="sid-97A72C46-C0AD-477F-86DD-85EF643BB97D" name="Agree start date" flowable:candidateGroups="hr"></humanTask> </stage> <stage id="sid-8459EF32-4F4C-4E9B-A6E9-87FDC2299044" name="After starting"> <planItem id="planItem6" name="New starter training" definitionRef="sid-DF7B9582-11A6-40B4-B7E5-EC7AC6029387"></planItem> <planItem id="planItem7" name="Fill in paperwork" definitionRef="sid-7BF2B421-7FA0-479D-A8BD-C22EBD09F599"></planItem> <humanTask id="sid-DF7B9582-11A6-40B4-B7E5-EC7AC6029387" name="New starter training" flowable:assignee="${potentialEmployee}"></humanTask> <humanTask id="sid-7BF2B421-7FA0-479D-A8BD-C22EBD09F599" name="Fill in paperwork" flowable:assignee="${potentialEmployee}"></humanTask> </stage> <humanTask id="sid-134E885A-3D58-417E-81E2-66A3E12334F9" name="Reject job" flowable:assignee="${potentialEmployee}"></humanTask> <exitCriterion id="sid-18277F30-E146-4B3E-B3C9-3F1E187EC7A8" sentryRef="sentry3"></exitCriterion> </casePlanModel> </case> </definitions>

First of all, create a new project and add the flowable-cmmn-engine dependency (here shown for Maven). The H2 dependency is also added, as H2 will be used as embedded database later on.

1 2 3 4 5 6 7 8 9 10
<dependency> <groupId>org.flowable</groupId> <artifactId>flowable-cmmn-engine</artifactId> <version>${flowable.version}</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>${h2.version}</version> </dependency>

The Flowable CMMN API is designed to be consistent with the other Flowable API’s and concepts. As such, people that know the BPMN or DMN API’s will have no problem finding their way around. As with the other engines, the first line of code is creating a CmmnEngine. Here, the default in-memory configuration is used, which uses H2 as database:

1 2 3 4 5 6
public class Main { public static void main(String[] args) { CmmnEngine cmmnEngine = new StandaloneInMemCmmnEngineConfiguration().buildCmmnEngine(); } }

Note that the CmmnEngineConfiguration exposes many configuration options for tweaking various settings of the CMMN engine.

Put the XML from above in a file, for example my-case.cmmn (or .cmmn.xml). For Maven, it should be placed in the src/main/resources folder.

To make the engine aware of the case model, it needs to be deployed first. This is done through the CmmnRepositoryService:

1 2 3 4
CmmnRepositoryService cmmnRepositoryService = cmmnEngine.getCmmnRepositoryService(); CmmnDeployment cmmnDeployment = cmmnRepositoryService.createDeployment() .addClasspathResource("my-case.cmmn") .deploy();

Deploying the XML will return a CmmnDeployment. A deployment can contain many case models and artifacts. The specific case model definition of above is stored as a CaseDefinition. This can be verified by doing a CaseDefinitionQuery:

1 2
List<CaseDefinition> caseDefinitions = cmmnRepositoryService.createCaseDefinitionQuery().list(); System.out.println("Found " + caseDefinitions.size() + " case definitions");

Having a CaseDefinition in the engine, it’s now possible to start a CaseInstance for this case model definition. Either the result from the query is used and passed into the following snippet of code, or the key of the case definition is used directly (as done below).

Note that we’re also passing data, an identifier to the potentialEmployee as a variable when starting the CaseInstance. This variable will later be used in the human tasks to assign the task to the correct person (see the assignee="${potentialEmployee}" attribute on human tasks).

1 2 3 4 5
CmmnRuntimeService cmmnRuntimeService = cmmnEngine.getCmmnRuntimeService(); CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder() .caseDefinitionKey("employeeOnboarding") .variable("potentialEmployee", "johnDoe") .start();

After the CaseInstance is started, the engine will determine which of the plan items of the model should be activated:

  • The first stage has no entry criteria, so it’s activated

  • The child human tasks of the first stage have no entry criteria, so three of them are expected to be active

The plan items are represented at runtime by PlanItemInstances and can be queried through the CmmnRuntimeService:

1 2 3 4 5 6 7 8
List<PlanItemInstance> planItemInstances = cmmnRuntimeService.createPlanItemInstanceQuery() .caseInstanceId(caseInstance.getId()) .orderByName().asc() .list(); for (PlanItemInstance planItemInstance : planItemInstances) { System.out.println(planItemInstance.getName()); }

which prints out

After starting
Agree start date
Allocate office
Create email address
Prior to starting
Reject job
Send joining letter to candidate

Some things might be unexpected here:

  • The stages are also plan items and thus have a representation as PlanItemInstance. Note that that child plan item instances will have the stage as parent when calling .getStageInstanceId().

  • The Send joining letter to candidate is returned in the result. The reason for that is that, in accordance with the CMMN 1.1 specification, this plan item instance is in the available state, but not yet in the active state.

Indeed, when the code above is changed to

1 2 3 4 5
for (PlanItemInstance planItemInstance : planItemInstances) { System.out.println(planItemInstance.getName() + ", state=" + planItemInstance.getState() + ", parent stage=" + planItemInstance.getStageInstanceId()); }

The output now becomes:

After starting, state=available, parent stage=null
Agree start date, state=active, parent stage=fe37ac97-b016-11e7-b3ad-acde48001122
Allocate office, state=active, parent stage=fe37ac97-b016-11e7-b3ad-acde48001122
Create email address, state=active, parent stage=fe37ac97-b016-11e7-b3ad-acde48001122
Prior to starting, state=active, parent stage=null
Reject job, state=active, parent stage=fe37ac97-b016-11e7-b3ad-acde48001122
Send joining letter to candidate, state=available, parent stage=fe37ac97-b016-11e7-b3ad-acde48001122

To only show the active plan item instances, the query can be adapted by adding planItemInstanceStateActive():

1 2 3 4 5
List<PlanItemInstance> planItemInstances = cmmnRuntimeService.createPlanItemInstanceQuery() .caseInstanceId(caseInstance.getId()) .planItemInstanceStateActive() .orderByName().asc() .list();

The output is now

Agree start date
Allocate office
Create email address
Prior to starting
Reject job

Of course, the PlanItemInstance is the low level representation, but each plan item also has a plan item definition that defines what type it is. In this case, we only have human tasks. It is possible to interact with the CaseInstance via its plan item instances, for example by triggering them programmatically (e.g. CmmnRuntimeService.triggerPlanItemInstance(String planItemInstanceId)). However, most likely the interaction will happen through the results of the actual plan item definition, here the human tasks.

Querying for tasks is done in the exact same way as for the BPMN engine (in fact, the task service is a shared component and tasks created in BPMN or CMMN can be queried through both engines):

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
CmmnTaskService cmmnTaskService = cmmnEngine.getCmmnTaskService(); List<Task> hrTasks = cmmnTaskService.createTaskQuery() .taskCandidateGroup("hr") .caseInstanceId(caseInstance.getId()) .orderByTaskName().asc() .list(); for (Task task : hrTasks) { System.out.println("Task for HR : " + task.getName()); } List<Task> employeeTasks = cmmnTaskService.createTaskQuery() .taskAssignee("johndoe") .orderByTaskName().asc() .list(); for (Task task : employeeTasks) { System.out.println("Task for employee: " + task); }

Which outputs:

Task for HR : Agree start date
Task for HR : Allocate office
Task for HR : Create email address

Task for employee: Reject job

When the three tasks of HR are completed, the Send joining letter to candidate taks should be available:

1 2 3 4 5 6 7 8 9 10 11 12 13
for (Task task : hrTasks) { cmmnTaskService.complete(task.getId()); } hrTasks = cmmnTaskService.createTaskQuery() .taskCandidateGroup("hr") .caseInstanceId(caseInstance.getId()) .orderByTaskName().asc() .list(); for (Task task : hrTasks) { System.out.println("Task for HR : " + task.getName()); }

And indeed, the expected task is now created:

Task for HR : Send joining letter to candidate

Completing this task will now move the case instance into the second stage, as the sentry for the first stage is satisfied. The Reject job tasks is automatically completed by the system and the two tasks for the employee are now created:

1 2 3
Task for employee: Fill in paperwork Task for employee: New starter training Task for employee: Reject job

Completing all the tasks will now end the case instance:

List<Task> tasks = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).listPage(0, 1);
while (!tasks.isEmpty()) {
    cmmnTaskService.complete(tasks.get(0).getId());
    tasks = cmmnTaskService.createTaskQuery()
        .caseInstanceId(caseInstance.getId())
        .listPage(0, 1);
}

While executing case instances, the engine also stores historic information, that can be queried via a query API:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
CmmnHistoryService cmmnHistoryService = cmmnEngine.getCmmnHistoryService(); HistoricCaseInstance historicCaseInstance = cmmnHistoryService.createHistoricCaseInstanceQuery() .caseInstanceId(caseInstance.getId()) .singleResult(); System.out.println("Case instance execution took " + (historicCaseInstance.getEndTime().getTime() - historicCaseInstance.getStartTime().getTime()) + " ms"); List<HistoricTaskInstance> historicTaskInstances = cmmnHistoryService.createHistoricTaskInstanceQuery() .caseInstanceId(caseInstance.getId()) .orderByTaskCreateTime().asc() .list(); for (HistoricTaskInstance historicTaskInstance : historicTaskInstances) { System.out.println("Task completed: " + historicTaskInstance.getName()); }

Which outputs:

Case instance execution took 149 ms
Task completed: Reject job
Task completed: Agree start date
Task completed: Allocate office
Task completed: Create email address
Task completed: Send joining letter to candidate
Task completed: New starter training
Task completed: Fill in paperwork

Of course, this is but a small part of the available API’s and constructs available in the Flowable CMMN Engine. Please check the other sections for more detailed information

6.4. CMMN 1.1 Constructs

This chapter covers the CMMN 1.1 constructs supported by Flowable, as well as extensions to the CMMN 1.1 standard.

6.4.1. Stage

A stage is used to group plan items together. It is typicaly used to define "phases" in a case instance.

A stage is a plan item itself, and thus can have entry and exit criteria. Plan items contained within a stage are only available when the parent stage moves to the active state. Stages can be nested in other stages.

A stage is visualized as a rectangle with angled corners:

cmmn.stage

6.4.2. Task

A "manual" task, meaning the task will happen external to the engine.

Properties:

  • name: expression that will be resolved at runtime as the name of the human task

  • blocking: a boolean value determining whether the task blocks

  • blockingExpression: an expression that evaluates to a boolean indicating whether the tasks blocks

If a task is non-blocking, the engine will simply complete it automatically when executing it. If a task is blocking, a PlanItemInstance for this task will remain in the active state until it is programmatically triggered by the CmmnRuntimeService.triggerPlanItemInstance(String planItemInstanceId) method.

A task is visualized as a rounded rectangle:

cmmn.task

6.4.3. Human task

A human task is used to model work that needs to be done by a human, typically through a form. When the engine arrives at such a human task, a new entry is created in the task list of any users or groups assigned to that task.

A human task is a plan item, which means that beyond a human task entry aso a PlanItemInstance is created and it can be queried via the PlanItemInstanceQuery.

Human tasks can be queried throught the org.flowable.task.api.TaskQuery API. Historic task data can be queries through the org.flowable.task.api.history.HistoricTaskInstanceQuery.

Properties:

  • name: expression that will be resolved at runtime as the name of the human task.

  • blocking: a boolean value determining whether the task blocks.

  • blockingExpression: an expression that evaluates to a boolean indicating whether the tasks blocks.

  • assignee : an expression (can be a static text value) that is used to determine to who the human task is assigned.

  • owner : an expression (can be a static text value) that is used to determine to who is the owner of the human task.

  • candidateUsers : an expression (can be a static text value) that resolves to a comma-seperated list of Strings that is used to determine which users are candidate for this human task.

  • candidateGroups : an expression (can be a static text value) that resolves to a comma-seperated list of Strings that is used to determine to which groups the task is assigned.

  • form key: an expression that determines a key when using forms. Can be retrieved via the API afterwards.

  • Due date an expression that resolves to java.util.Date or a ISO-8601 date string.

  • Priority: an expression that resolves to an integer. Can be used in the TaskQuery API to filter tasks.

A human task is visualized as a rounded rectangle with a user icon in the top left corner:

cmmn.humantask

6.4.4. Java Service task

A service task is used to execute custom logic.

Custom logic is placed in a class that implements the org.flowable.cmmn.api.delegate.PlanItemJavaDelegate interface.

1 2 3 4 5 6 7 8
public class MyJavaDelegate implements PlanItemJavaDelegate { public void execute(DelegatePlanItemInstance planItemInstance) { String value = (String) planItemInstance.getVariable("someVariable"); ... } }

For lower-level implementations that cannot be covered by using the PlanItemJavaDelegate approach, the CmmnActivityBehavior can be used (similar to JavaDelegate vs ActivityBehavior in the BPMN engine).

Properties:

  • name: name of the service task.

  • class: the Java class implementing the custom logic.

  • class fields: allows to add parameters when calling the custom logic.

  • Delegate expression: an expression that resolves to a class implementing the PlanItemJavaDelegate interface.

A service task is visualized as a rounded rectangle with a cog icon in the top left corner:

cmmn.servicetask

6.4.5. Milestone

A milestone is used to mark arriving at a certain point in the case instance. At runtime, they are represented as MilestoneInstances and they can be queried through the MilestoneInstanceQuery via the CmmnRuntimeService. There is also a historical counterpart via the CmmnHistoryService.

A human task is a plan item, which means that beyond a milestone entry aso a PlanItemInstance is created and it can be queried via the PlanItemInstanceQuery.

Properties:

  • name: an expression or static text that determines the name of the mile stone.

A milestone is visualized as a rounded rectangle with, slightly more rounded than a task:

cmmn.milestone

6.4.6. Case task

A case task is used to start a child case within the context of another case. The CaseInstanceQuery has parent options to find these cases.

When the case task is blocking, the PlanItemInstance will be active until the child case has completely finished. If the case task is non-blocking, the child case is started and the plan item instance automatically completes. When the child case instance is ended, there is no impact on the parent case.

Properties:

  • name: an expression or static text that determines the name

  • blocking: a boolean value determining whether the task blocks

  • blockingExpression: an expression that evaluates to a boolean indicating whether the tasks blocks

  • Case reference: the key of the case definition that is used to start the child case instance. Can be an expression.

A case task is visualized as a rounded rectangle with a case icon in the top left corner:

cmmn.casetask

6.4.7. Process task

A process task is used to start a process instance within the context of a case.

When the process task is blocking, the PlanItemInstance will be active until the process instance has completely finished. If the process task is non-blocking, the process instance is started and the plan item instance automatically completes. When the process instance is ended, there is no impact on the parent case.

Properties:

  • name: an expression or static text that determines the name

  • blocking: a boolean value determining whether the task blocks

  • blockingExpression: an expression that evaluates to a boolean indicating whether the tasks blocks

  • Process reference: the key of the process definition that is used to start the process instance. Can be an expression.

A process task is visualized as a rounded rectangle with an arrow icon in the top left corner:

cmmn.processtask

6.4.8. Entry criterion

Entry criteria form a sentry for a given plan item instance. They consist of two parts:

  • One or more parts depending on other plan items: these define dependencies on state transitions of other plan items. For example, one human task can depend on the state transition complete of three other human tasks to become active itself.

  • One optional if part or condition: this is an expression that allows to define a complex condition.

A sentry is satisfied when all of its criteria are resolved to true. When a certain criteria evaluates to true, this is stored and remembered for future evaluations. Note that entry criteria evaluations of all plan item instances in the available state are executed whenever something changes in the case instance.

Multiple sentries are possible on a plan item. However, when one is satisfied, the plan item moves from state available to active.

An entry criteria is visualized as a diamond shape (white color inside) on the border of a plan item:

cmmn.entrycriteria

6.4.9. Exit criterion

Exit criteria form a sentry for a given plan item instance. They consist of two parts:

  • One or more parts depending on other plan items: these define dependencies on state transitions of other plan items. For example, one human task can depend on reaching a certain milestone to be automatically terminated.

  • One optional if part or condition: this is an expression that allows to define a complex condition.

A sentry is satisfied when all of its criteria are resolved to true. When a certain criteria evaluates to true, this is stored and remembered for future evaluations. Note that exit criteria evaluations of all plan item instances in the active state are executed whenever something changes in the case instance.

Multiple sentries are possible on a plan item. However, when one is satisfied, the plan item moves from state active to exit.

An exit criteria is visualized as a diamond shape (white color inside) on the border of a plan item:

cmmn.exitcriteria