提交 f4063be5 编写于 作者: T Tijs Rademakers

More CMMN docs

上级 92f00e28
......@@ -20,3 +20,4 @@ modules/flowable-ui-idm/flowable-ui-idm-app/.extract
modules/flowable-app-rest/src/main/webapp/docs/specfile
userguide-dmn/src/en/output
userguide-form/src/en/output
userguide-cmmn/src/en/output
......@@ -367,6 +367,11 @@
<copy todir="${target.distro.root}/docs/userguide">
<fileset dir="../userguide/target/html/en" />
</copy>
<ant antfile="../userguide-cmmn/build.xml" inheritall="false" />
<mkdir dir="${target.distro.root}/docs/userguide-cmmn" />
<copy todir="${target.distro.root}/docs/userguide-cmmn">
<fileset dir="../userguide-cmmn/target/html/en" />
</copy>
<ant antfile="../userguide-dmn/build.xml" inheritall="false" />
<mkdir dir="${target.distro.root}/docs/userguide-dmn" />
<copy todir="${target.distro.root}/docs/userguide-dmn">
......@@ -397,6 +402,10 @@
<copy todir="${flowable.website}/userguide" overwrite="true">
<fileset dir="../userguide/target/html/en" />
</copy>
<mkdir dir="${flowable.website}/userguide-cmmn" />
<copy todir="${flowable.website}/userguide-cmmn" overwrite="true">
<fileset dir="../userguide-cmmn/target/html/en" />
</copy>
<mkdir dir="${flowable.website}/userguide-dmn" />
<copy todir="${flowable.website}/userguide-dmn" overwrite="true">
<fileset dir="../userguide-dmn/target/html/en" />
......
<?xml version="1.0" encoding="UTF-8"?>
<project name="userguide" default="build.userguide">
<target name="build.userguide" depends="build.docs" />
<target name="clean" description="Cleans up generated files.">
<delete dir="target/html" />
<delete dir="src/en/output" />
</target>
<target name="build.docs" depends="clean">
<mkdir dir="target/html" />
<exec dir="src/en" executable="./generate-all.sh" />
<copy todir="target/html/en">
<fileset dir="src/en/output">
<include name="**/**" />
</fileset>
</copy>
</target>
</project>
== Configuration
[[configuration]]
=== 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 <<springintegration,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:
[source,java,linenums]
----
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.
[source,xml,linenums]
----
<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).
[source,java,linenums]
----
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 <<configurationClasses,the different supported classes>> for more information).
[source,java,linenums]
----
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:
[source,java,linenums]
----
CmmnEngine cmmnEngine = CmmnEngineConfiguration.createStandaloneInMemCmmnEngineConfiguration()
.setDatabaseSchemaUpdate(CmmnEngineConfiguration.DB_SCHEMA_UPDATE_TRUE)
.setJdbcUrl("jdbc:h2:mem:my-own-db;DB_CLOSE_DELAY=1000")
.buildCmmnEngine();
----
[[configurationRoot]]
=== CmmnEngineConfiguration bean
The +flowable.cmmn.cfg.xml+ must contain a bean that has the id +$$'cmmnEngineConfiguration'$$+.
[source,xml,linenums]
----
<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: [[configurationClasses]]
* *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 <<springintegration,the Spring integration section>> for more information.
[[databaseConfiguration]]
=== 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 link:$$http://www.mybatis.org/$$[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:
[source,xml,linenums]
----
<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.):
[source,xml,linenums]
----
<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 <<supporteddatabases,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.
[[jndiDatasourceConfig]]
=== 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.
[[jndi_configuration]]
==== 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:
[source,xml,linenums]
----
<?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>
----
==== 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".
[[supporteddatabases]]
=== Supported databases
Listed below are the types (case sensitive!) that Flowable uses to refer to databases.
[[databaseTypes]]
[options="header"]
|===============
|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
|===============
[[creatingDatabaseTable]]
=== 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 <<databaseConfiguration,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 <<supporteddatabases,supported databases>>.
[[database.tables.explained]]
=== 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.
[[databaseUpgrade]]
=== 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:
[source,xml,linenums]
----
<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.
[[historyConfiguration]]
=== History configuration
Customizing the configuration of history storage is optional. This allows you to tweak settings that influence the <<history,history capabilities>> of the engine. See <<historyConfig,history configuration>> for more details.
[source,xml,linenums]
----
<property name="history" value="audit" />
----
[[exposingConfigurationBeans]]
=== 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.
[[caseDefinitionCacheConfiguration]]
=== 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:
[source,xml,linenums]
----
<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:
[source,xml,linenums]
----
<property name="caseDefinitionCache">
<bean class="org.flowable.MyCache" />
</property>
----
[[loggingConfiguration]]
=== 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 link:$$http://www.slf4j.org/codes.html#StaticLoggerBinder$$[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:
[source,xml,linenums]
----
<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 link:$$http://www.slf4j.org/legacy.html#jclOverSLF4J$$[http://www.slf4j.org/legacy.html#jclOverSLF4J]). If your container provides a commons-logging implementation, please follow directions on this page: link:$$http://www.slf4j.org/codes.html#release$$[http://www.slf4j.org/codes.html#release] to ensure stability.
Example when using Maven (version omitted):
[source,xml,linenums]
----
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</dependency>
----
[[chapterApi]]
== The Flowable CMMN API
[[apiEngine]]
=== 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,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.
image::images/api.services.png[align="center"]
[source,java,linenums]
----
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 link:$$flowable/index.html$$[the javadocs].
=== 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 link:$$http://flowable.org/javadocs/index.html$$[ the javadocs]. For example, an extract from ++CmmnTaskService++:
[source,java,linenums]
----
/**
* 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 ++FlowableExceptions++s.
* ++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.
[[queryAPI]]
=== 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:
[source,java,linenums]
----
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.
[source,java,linenums]
----
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();
----
[[apiVariables]]
=== 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_:
[source,java,linenums]
----
CaseInstance caseInstance = runtimeService.createCaseInstanceBuilder().variable("var1", "test").start();
----
Variables can be added during case execution. For example, (_CmmnRuntimeService_):
[source,java,linenums]
----
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_.
[source,java,linenums]
----
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.
[[apiTransientVariables]]
=== 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:
[source,java,linenums]
----
CaseInstance caseInstance = runtimeService.createCaseInstanceBuilder().transientVariable("var1", "test").start();
----
[[apiExpressions]]
=== Expressions
Flowable uses UEL for expression-resolving. UEL stands for _Unified Expression Language_ and is part of the EE6 specification (see link:$$http://docs.oracle.com/javaee/6/tutorial/doc/gjddd.html$$[ 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.
[[apiUnitTesting]]
=== 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.
[source,java,linenums]
----
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());
}
}
----
[[springintegration]]
== Spring integration
While you can definitely use Flowable without Spring, we've provided some very nice integration features that are explained in this chapter.
=== 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,configuration section>>. For Spring integration, the configuration and engine beans will look like this:
[source,xml,linenums]
----
<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.
=== 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).*
[source,xml,linenums]
----
<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:
[source,java,linenums]
----
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext(
"org/flowable/cmmn/examples/spring/SpringIntegrationTest-context.xml");
----
or, as it's a test:
[source,java,linenums]
----
@ContextConfiguration(
"classpath:org/flowable/cmmn/spring/test/SpringIntegrationTest-context.xml")
----
[[springExpressions]]
=== 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.*
[source,xml,linenums]
----
<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:
[source,xml,linenums]
----
...
<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:
[source,java,linenums]
----
public class Printer {
public void printMessage(String var) {
System.out.println("hello " + var);
}
}
----
And the Spring bean configuration (also shown above) looks like this:
[source,xml,linenums]
----
<beans>
...
<bean id="printer" class="org.flowable.cmmn.examples.spring.Printer" />
</beans>
----
=== 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:
[source,xml,linenums]
----
<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++:
[source,xml,linenums]
----
<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.
[[springUnitTest]]
=== 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:
[source,java,linenums]
----
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);
}
}
----
[[chDeployment]]
== Deployment
=== 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.
==== 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+.
==== 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 <<springintegration,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.
==== 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+.
[[versioningOfCaseDefinitions]]
=== 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
[source,xml,linenums]
----
<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:
[options="header"]
|===============
|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:
[options="header"]
|===============
|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.
[source,xml,linenums]
----
<definitions id="myNewDefinitions" >
<case id="myNewCase" name="My important case" >
...
----
The table will look like this:
[options="header"]
|===============
|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.
[[deploymentCategory]]
=== 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:
[source,java,linenums]
----
repositoryService
.createDeployment()
.category("yourCategory")
...
.deploy();
----
include::ch01-Introduction.adoc[]
include::ch02-Configuration.adoc[]
include::ch03-API.adoc[]
include::ch04-Spring.adoc[]
include::ch05-Deployment.adoc[]
include::ch06-cmmn.adoc[]
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册