Chapter 5. Services
- 5.1. Process definition, process instance and executions
- 5.2. ProcessEngine
- 5.3. Deploying a process
- 5.4. Deleting a deployment
- 5.5. Starting a new process instance
-
- 5.5.1. In latest
- 5.5.2. Specific process version
- 5.5.3. With a key
- 5.5.4. With variables
- 5.6. Signalling a waiting execution
- 5.7. TaskService
- 5.8. HistoryService
- 5.9. ManagementService
- 5.10. Query API
5.1. Process definition, process instance and executions
loan
process definition that describes the steps of how the company deals
with loan requests.One process instance represents one particular run of a process definition. For example, the loan request of John Doe last Friday to finance his new boat is represented in one process instance of the loan process definition.
A process instance contains all the runtime state. The most prominent property is the pointer that keeps track of the current activity.
Suppose that wiring the money and archiving can be done in parallel. Then the main process instance will have two child executions to keep track of the state like this:
More general, a process instance is the root of a tree of executions. When a new process instance is started, the process instance is in fact the root execution scope. Only leaf executions can be active.
The motivation to work with a tree structure like this is that this conceptually remains simple in the case where there is only one path of execution. The services API doesn't need to make a functional difference between process instances and executions. Therefore, the API has only one Execution type to refer to both
ProcessInstance
s and
Execution
s.
5.2. ProcessEngine
ProcessEngine
which is build from a Configuration
.
A
ProcessEngine
is thread safe and can be stored in a
static member field or even better in JNDI or some other central location.
One ProcessEngine
object can be used by all requests and
threads in an application. Here's how you can obtain a ProcessEngine
The code snippets in this section and the next section about process deployments are taken from the example
org.jbpm.examples.services.ServicesTest
ProcessEngine processEngine = new Configuration() .buildProcessEngine();
The previous code snippet shows how to build a
ProcessEngine
from the default configuration file jbpm.cfg.xml
which is
expected in the root of the classpath. If you want to specify another
resource location, use the setResource
method like this:ProcessEngine processEngine = new Configuration() .setResource("my-own-configuration-file.xml") .buildProcessEngine();There are other
setXxxx
methods that allow to specify
the configuration content as an InputStream
, an
xmlString
, InputSource
,
URL
or File
.
From a
ProcessEngine
the following services
can be obtained:RepositoryService repositoryService = processEngine.getRepositoryService(); ExecutionService executionService = processEngine.getExecutionService(); TaskService taskService = processEngine.getTaskService(); HistoryService historyService = processEngine.getHistoryService(); ManagementService managementService = processEngine.getManagementService();
Process engine objects defined in the configuration can also be retrieved by type (
processEngine.get(Class<T>)
)
or by name (processEngine.get(String)
)5.3. Deploying a process
RepositoryService
groups all methods to manage
the repository of deployments. In this first example, we'll deploy one process
resource from the classpath with the RepositoryService
:String deploymentId = repositoryService.createDeployment() .addResourceFromClasspath("org/jbpm/examples/services/Order.jpdl.xml") .deploy();
Analogue to the
addResourceFromClasspath
method above,
the source of the processes definitions XML can be picked up from a file, url, string,
input stream or zip input stream.
Each deployment is composed of a set of named resources. The content of each resource is a byte array. jPDL process files are recognized by their extension
.jpdl.xml
. Other resource types are task forms and java classes.
A deployment works with a set of named resources and can potentially contain multiple process descriptions and multiple other artifact types. The jPDL deployer will recognise process files based on the
.jpdl.xml
extension automatically.
During deployment, an
id
is assigned to the process
definitions. The id
will have format
{key}-{version}
with a dash between key and version
If
key
is not provided, it is generated automatically
based on the name. All non alpha numeric characters in the name will be replaced
by underscores to generate the key.The same
name
can only be associated to one
key
and vice versa.
If
version
is not provided, a version
will be automatically be assigned. For version
assignment, the versions of all deployed process definitions with the same name will
be taken into account. The assigned version
will be one higher
than the highest version
number of deployed process definitions
with the same key
. If no process definitions with a similar
key
have been deployed, version number 1 is assigned.
In this first example, we'll supply a name and nothing else.
<process name="Insurance claim"> ... </process>
Let's assume that this is the first time that this process gets deployed. Then it will get the following properties:
Table 5.1. Process properties without key
Property | Value | Source |
---|---|---|
name | Insurance claim | process xml |
key | Insurance_claim | generated |
version | 1 | generated |
id | Insurance_claim-1 | generated |
And as a second example, we'll show how you can get shorter ids by specifying a process key:
<process name="Insurance claim" key="ICL"> ... </process>
Then the process definition properties look like this:
Table 5.2. Process properties with key
Property | Value | Source |
---|---|---|
name | Insurance claim | process xml |
key | ICL | process xml |
version | 1 | generated |
id | ICL-1 | generated |
5.4. Deleting a deployment
repositoryService.deleteDeployment(deploymentId);That method will throw an exception when there are still active process executions for process definitions in that deployment.
If you want to cascade deletion of a deployment to all the process instances of all the process definitions, use
deleteDeploymentCascade
.
5.5. Starting a new process instance
5.5.1. In latest
ProcessInstance processInstance =
executionService.startProcessInstanceByKey("ICL");
In this case, the service method will first look up the latest version of the processes with key
ICL
. Then a new
process instance is started in that latest process definition.
When a new version of the insurance claim process is deployed, all invocations of
startProcessInstanceByKey
will automatically switch to the newly deployed version.
5.5.2. Specific process version
ProcessInstance processInstance = executionService.startProcessInstanceById("ICL-1");
5.5.3. With a key
ProcessInstance processInstance = executionService.startProcessInstanceByKey("ICL", "CL92837");
The key is used to create the id of the process instance. The format used is
{process-key}.{execution-id}
.
With a dot between process-key and execution-id.
So execution created in the previous code snippet will have id
ICL.CL92837
.If no user defined key is provided, the DB primary key is taken as the key. In that case, the id can be retrieved like this:
ProcessInstance processInstance = executionService.startProcessInstanceByKey("ICL"); String pid = processInstance.getId();
It is a best practice to use a user defined business key. Typically in your application domain, finding such a key is not difficult. By providing a user defined key, you can then compose the id of the execution, rather then performing a query based on the process variables - which is also more costly performance-wise.
5.5.4. With variables
Map<String,Object> variables = new HashMap<String,Object>(); variables.put("customer", "John Doe"); variables.put("type", "Accident"); variables.put("amount", new Float(763.74)); ProcessInstance processInstance = executionService.startProcessInstanceByKey("ICL", variables);
5.6. Signalling a waiting execution
state
activity, the execution (or process instance)
will halt when it arrives in a state, waiting for a signal (aka external trigger).
The method signalExecution
and alike can be used for that. Executions are
referenced by an execution id (String).
In some cases, the execution that arrives in a state will be the process instance itself. But that is not always the case. In case of timers or concurrency, a process is the root execution of a tree of executions. So you have to make sure that you signal the right path of execution.
The preferred way to capture the right execution is by associating an event listener to the state activity like this:
<state name="wait"> <on event="start"> <event-listener class="org.jbpm.examples.StartExternalWork" /> </on> ... </state>
In event listener
StartExternalWork
you can kick off what needs to
be done externally. In that event listener you can also obtain the exact execution id
with execution.getId()
. It's that executionId that you'll need to provide
with the signal later on when the external work is done:executionService.signalExecutionById(executionId);
There is an alternatively (less preferrable) way to obtain the executionId when the execution arrives in the
state
activity.
It's only possible to obtain the execution id this way if you know after which jBPM
API call the execution will have entered the state
activity:
// assume that we know that after the next call
// the process instance will arrive in state
external work
ProcessInstance processInstance =
executionService.startProcessInstanceById(processDefinitionId);
// or ProcessInstance processInstance =
// executionService.signalProcessInstanceById(executionId);
Execution execution = processInstance.findActiveExecutionIn("external work");
String executionId = execution.getId();
5.7. TaskService
johndoe
.List<Task> taskList = taskService.findPersonalTasks("johndoe");
Typically tasks are associated with a form and displayed in some user interface. The form needs to be able to read and write data related to the task.
// read task variables Set<String> variableNames = taskService.getVariableNames(taskId); variables = taskService.getVariables(taskId, variableNames);
// write task variables variables = new HashMap<String, Object>(); variables.put("category", "small"); variables.put("lires", 923874893); taskService.setVariables(taskId, variables);
The taskService is also used to complete tasks
taskService.completeTask(taskId); taskService.completeTask(taskId, variables); taskService.completeTask(taskId, outcome); taskService.completeTask(taskId, outcome, variables);
The API allows to provide a map of variables that will be added as process variables before the task is completed. It is also possible to provide an 'outcome', that will be used to determine which outgoing transition will be chosen. The logic is as follows:
If a task has one outgoing transition without a name then:
- taskService.getOutcomes() returns a collection that includes one null value
- taskService.completeTask(taskId) will take that outgoing transition
- taskService.completeTask(taskId, null) will take that outgoing transition
- taskService.completeTask(taskId, "anyvalue") will result in an exception
If a task has one outgoing transition with a name then:
- taskService.getOutcomes() returns a collection that includes only the name of the transition
- taskService.completeTask(taskId) will take the single outgoing transition
- taskService.completeTask(taskId, null) will will result in an exception (as there is no transition without a name)
- taskService.completeTask(taskId, "anyvalue") will result in an exception
- taskService.completeTask(taskId, "myName") will take the transition with the given name
If a task has multiple outgoing transitions. One transition has no a name and the other transition have a name:
- taskService.getOutcomes() returns a collection that includes a null value and the names of the other transitions
- taskService.completeTask(taskId) will take the transition without a name
- taskService.completeTask(taskId, null) will take the transition without a name
- taskService.completeTask(taskId, "anyvalue") will result in an exception
- taskService.completeTask(taskId, "myName") will take the 'myName' transition
If a task has multiple outgoing transitions and all of them are uniquely named, then:
- taskService.getOutcomes() returns a collection that includes all the names of all the transitions
- taskService.completeTask(taskId) will result in an exception, since there is no transition without a name
- taskService.completeTask(taskId, null) will result in an exception, since there is no unnamed transition
- taskService.completeTask(taskId, "anyvalue") will result in an exception
- taskService.completeTask(taskId, "myName") will take the 'myName' transition
Tasks can also be offered to a set of candidates. Candidates can be users or groups. Users can take tasks for which they are a candidate. Taking a task means that this user will be set as the assignee. After that, other users will be blocked from taking the task.
People should not work on a task unless they are assigned to that task. The user interface should display forms and allow users to complete tasks if they are assigned to it. For unassigned tasks for which the user is a candidate, the only action that should be exposed is 'take'.
More on tasks in Section 6.2.6, “
task
” 5.8. HistoryService
HistoryService
provides
access to that information.
Querying for all process instances for a specific process definition can be done like this:
List<HistoryProcessInstance> historyProcessInstances = historyService .createHistoryProcessInstanceQuery() .processDefinitionId("ICL-1") .orderAsc(HistoryProcessInstanceQuery.PROPERTY_STARTTIME) .list();
Also individual activity executions are stored in the history information as
HistoryActivityInstance
s.List<HistoryActivityInstance> histActInsts = historyService .createHistoryActivityInstanceQuery() .processDefinitionId("ICL-1") .activityName("a") .list();
Convenience methods
avgDurationPerActivity
and
choiceDistribution
are also available. See javadocs for more
information on those methods.
Sometimes there is a need to get complete list of nodes executed for a given process instance. Following query can be used to get list of all nodes executed:
List<HistoryActivityInstance> histActInsts = historyService .createHistoryActivityInstanceQuery() .processInstanceId("ICL.12345") .list();
Above query is a bit different then querying by execution id. Sometimes execution id is different than process instance id, for instance when an activity has a timer then execution id will get additional suffix, which makes that node excluded from a result list while querying by execution id.
5.9. ManagementService
5.10. Query API
For example:
List<ProcessInstance> results = executionService.createProcessInstanceQuery() .processDefinitionId("my_process_definition") .notSuspended() .page(0, 50) .list();
This example returns all the process instances of the given process definition which are not suspended. The result is also paged, and the first page of 50 results is given.
Querying tasks is done in completely the same way:
List<Task> myTasks = taskService.createTaskQuery() .processInstanceId(piId) .assignee("John") .page(100, 120) .orderDesc(TaskQuery.PROPERTY_DUEDATE) .list();
This query will give all the tasks for a given process instance assigned to John, paged of course, in a descending order based on the duedate.
Every service has operations of creating such unified queries (eg. querying jobs through the
ManagementService
, querying completed
process instances through the HistoryService
. Do check
the Javadoc of the services to learn everything about the query API.
For more information follow my Tutorial online @ http://jbpmmaster.blogspot.com/
No comments:
Post a Comment