Chapter 6. jPDL
- 6.1.
process
- 6.2. Control flow activities
-
- 6.2.1.
start
- 6.2.2.
state
- 6.2.3.
decision
- 6.2.4.
concurrency
- 6.2.5.
end
- 6.2.6.
task
- 6.2.7.
sub-process
- 6.2.8.
custom
- 6.2.1.
- 6.3. Automatic activities
-
- 6.3.1.
java
- 6.3.2.
script
- 6.3.3.
hql
- 6.3.4.
sql
- 6.3.5.
mail
- 6.3.1.
- 6.4. Common activity contents
- 6.5. Events
-
- 6.5.1. Event listener example
- 6.5.2. Event propagation
- 6.6. Asynchronous continuations
-
- 6.6.1. Async activity
- 6.6.2. Async fork
- 6.7. User code
-
- 6.7.1. User code configuration
- 6.7.2. User code classloading
The jPDL schema file contains more attributes and elements then this documentation. This part of the documentation explains the stable and supported part of jPDL. Experimental/not supported jPDL features can be found in the developers guide.
An example jPDL process file looks like this:
<?xml version="1.0" encoding="UTF-8"?> <process name="Purchase order" xmlns="http://jbpm.org/4.4/jpdl"> <start> <transition to="Verify supplier" /> </start> <state name="Verify supplier"> <transition name="Supplier ok" to="Check supplier data" /> <transition name="Supplier not ok" to="Error" /> </state> <decision name="Check supplier data"> <transition name="nok" to="Error" /> <transition name="ok" to="Completed" /> </decision> <end name="Completed" /> <end name="Error" /> </process>
6.1. process
Table 6.1.
process
attributes:Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
name | any text | required | name or label of the process used to display to the process name in user interactions. | |
key | alpha numeric characters and underscores | if omitted, the key will be generated based on the name by replacing all non-alpha-numeric characters with underscores | optional | identification to distinct different process definitions. Multiple versions of a process with the same key can be deployed. The key:name combination must remain exactly the same for all deployed versions. |
version | integer | one higher then highest version number starting with 1 if no other process is deployed with the same name/key. | optional | version number of this process |
Table 6.2.
process
elements:Element | Multiplicity | Description |
---|---|---|
description | 0..1 | description text |
activities | 1..* | a list of any activity type can be placed here. At least
one start activity must be present.
|
6.2. Control flow activities
6.2.1. start
Known limitation: for now, a process can not have more then one
start
.
Table 6.3.
start
attributes:Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
name | any text | optional | name of the activity. Since a start activity cannot have incoming transitions, the name is optional. |
6.2.2. state
state
doesn't have any extra
attributes or elements.
6.2.2.1. state
sequence
<process name="StateSequence" xmlns="http://jbpm.org/4.4/jpdl"> <start> <transition to="a" /> </start> <state name="a"> <transition to="b" /> </state> <state name="b"> <transition to="c" /> </state> <state name="c" /> </process>After you start an execution like this:
ProcessInstance processInstance = executionService.startProcessInstanceByKey("StateSequence");the created process instance will be positioned in state
a
. Providing an external trigger can
be done with the signalExecution
methods.Execution executionInA = processInstance.findActiveExecutionIn("a"); assertNotNull(executionInA); processInstance = executionService.signalExecutionById(executionInA.getId()); Execution executionInB = processInstance.findActiveExecutionIn("b"); assertNotNull(executionInB); processInstance = executionService.signalExecutionById(executionInB.getId()); Execution executionInC = processInstance.findActiveExecutionIn("c"); assertNotNull(executionInC);
6.2.2.2. state
choice
state
can be used to feed in an external choice of
the path to take.<process name="StateChoice" xmlns="http://jbpm.org/4.4/jpdl"> <start> <transition to="wait for response" /> </start> <state name="wait for response"> <transition name="accept" to="submit document" /> <transition name="reject" to="try again" /> </state> <state name="submit document" /> <state name="try again" /> </process>Let's start a new process instance for this process definition:
ProcessInstance processInstance = executionService .startProcessInstanceByKey("StateChoice");Now, the execution has arrived in the
wait for response
.
The execution will wait there until an external trigger is given. In case
a state
has multiple outgoing transitions, the signalName given
in the external trigger will be matched against the name of the outgoing transition
to take. So when we provide signalName accept
like this:
String executionId = processInstance .findActiveExecutionIn("wait for response") .getId(); processInstance = executionService.signalExecutionById(executionId, "accept"); assertTrue(processInstance.isActive("submit document"));Then the execution will continue over the outgoing transition named
accept
. Analogue, when signalName reject
is given in the signalExecutionXxx methods, the execution will continue over
the outgoing transition named reject.
6.2.3. decision
A decision activity should be configured in one of the three following ways:
6.2.3.1. Decision conditions
Table 6.5.
decision.transition.condition
attributes:Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
expr | expression | required | script that will be evaluated in the specified expression language. | |
lang | expression language | the default-expression-language taken from the script-manager configuration | optional | the language in which expr is
to be evaluated.
|
Example:
<process name="DecisionConditions" > <start> <transition to="evaluate document" /> </start> <decision name="evaluate document"> <transition to="submit document"> <condition expr="#{content=="good"}" /> </transition> <transition to="try again"> <condition expr="#{content=="not so good"}" /> </transition> <transition to="give up" /> </decision> <state name="submit document" /> <state name="try again" /> <state name="give up" /> </process>After starting a process instance with
good content
Map<String, Object> variables = new HashMap<String, Object>(); variables.put("content", "good"); ProcessInstance processInstance = executionService.startProcessInstanceByKey("DecisionConditions", variables);The activity
submit document
will be activeassertTrue(processInstance.isActive("submit document"));See the example unit test for more scenarios.
6.2.3.2. Decision expression
Table 6.6.
decision
attributes:Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
expr | expression | required | script that will be evaluated in the specified expression language. | |
lang | expression language | the default-expression-language taken from the script-manager configuration | optional | the language in which expr is
to be evaluated.
|
Example:
Figure 6.4. The decision expression example process
<process name="DecisionExpression" xmlns="http://jbpm.org/4.4/jpdl"> <start > <transition to="evaluate document"/> </start> <decision name="evaluate document" expr="#{content}" > <transition name="good" to="submit document" /> <transition name="bad" to="try again" /> <transition name="ugly" to="give up" /> </decision> <state name="submit document" /> <state name="try again" /> <state name="give up" /> </process>When you start an new process instance with good content like this
Map<String, Object> variables = new HashMap<String, Object>(); variables.put("content", "good"); ProcessInstance processInstance = executionService.startProcessInstanceByKey("DecisionExpression", variables);then the new execution will go to activity
submit document
.See the example unit test for the other scenarios.
6.2.3.3. Decision handler
DecisionHandler
interface. The decision handler
will be responsible for selecting the name of the outgoing transition.
public interface DecisionHandler { String decide(OpenExecution execution); }The handler is specified as a sub element of the decision. The configuration attributes and content of a decision
handler
element can be found in Section 6.7, “User code”.
Here's an example process of a decision using a DecisionHandler:
Figure 6.5. The decision handler example process
<process name="DecisionHandler"> <start> <transition to="evaluate document" /> </start> <decision name="evaluate document"> <handler class="org.jbpm.examples.decision.handler.ContentEvaluation" /> <transition name="good" to="submit document" /> <transition name="bad" to="try again" /> <transition name="ugly" to="give up" /> </decision> <state name="submit document" /> <state name="try again" /> <state name="give up" /> </process>The ContentEvaluation class looks like this
public class ContentEvaluation implements DecisionHandler { public String decide(OpenExecution execution) { String content = (String) execution.getVariable("content"); if (content.equals("you're great")) { return "good"; } if (content.equals("you gotta improve")) { return "bad"; } return "ugly"; } }Now, when we start a process instance and supply value
you're great
for variable content, then the
ContentEvaluation will return String good
and
the process instance will arrive in activity Submit document
.
6.2.4. concurrency
fork
and
join
activities. The next table describes the join
attributes; fork
has no specific attributes.
Table 6.7.
join
attributes:Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
multiplicity | integer or expression | nbr of incoming transitions | optional | The number of executions that should arrive in this join before the join activates and push an execution out the single outgoing transition of the join. |
lockmode | {none, read, upgrade, upgrade_nowait, write} | upgrade | optional | the hibernate lock mode applied on the parent execution to prevent that 2 concurrent transactions see each other as not yet arrived at the join, causing a process deadlock. |
6.2.4.1. Parallel split with fork
fork
activity allows a single path of execution to be
split into two or more branches which can execute activities concurrently.<process name="ConcurrencyGraphBased" xmlns="http://jbpm.org/4.4/jpdl"> <start> <transition to="fork"/> </start> <fork name="fork"> <transition to="send invoice" /> <transition to="load truck"/> <transition to="print shipping documents" /> </fork> <state name="send invoice" > <transition to="final join" /> </state> <state name="load truck" > <transition to="shipping join" /> </state> <state name="print shipping documents"> <transition to="shipping join" /> </state> <join name="shipping join" > <transition to="drive truck to destination" /> </join> <state name="drive truck to destination" > <transition to="final join" /> </state> <join name="final join" > <transition to="end"/> </join> <end name="end" /> </process>
6.2.5. end
6.2.5.1. end
process instance
Figure 6.7. The end event
<process name="EndProcessInstance" xmlns="http://jbpm.org/4.4/jpdl"> <start> <transition to="end" /> </start> <end name="end" /> </process>When a new process instance is created, it immediately ends.
6.2.5.2. end
execution
ends="execution"
Table 6.8.
end
execution attributes:Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
ends | {processinstance|execution} | processinstance | optional | specifies if the whole process instance should be ended or just the path of execution that arrives in the end activity. |
6.2.5.3. end
multiple
Figure 6.8. Multiple end events
<process name="EndMultiple" xmlns="http://;jbpm.org/4/jpdl"> <start> <transition to="get return code" /> </start> <state name="get return code"> <transition name="200" to="ok"/> <transition name="400" to="bad request"/> <transition name="500" to="internal server error"/> </state> <end name="ok"/> <end name="bad request"/> <end name="internal server error"/> </process>Now if we would start an execution and signal it to move out of the
get return code
wait state with the
following code, the execution would end with the bad request
end event.ProcessInstance processInstance =
executionService.startProcessInstanceByKey("EndMultiple"); String pid = processInstance.getId(); processInstance = executionService.signalExecutionById(pid, "400");Likewise, using the value
200
or 500
would cause the execution
to end with the ok
or with the internal server error
end events
respectively.
6.2.5.4. end
state
state
attribute of the end event or by the end-cancel
and end-error
shortcut notations.
Table 6.9.
end
execution attributes:Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
state | String | optional | the state assigned to the execution. |
Take for example the following process.
Figure 6.9. Different end states
<process name="EndState" xmlns="http://jbpm.org/4.4/jpdl"> <start> <transition to="get return code"/> </start> <state name="get return code"> <transition name="200" to="ok"/> <transition name="400" to="bad request" /> <transition name="500" to="internal server error"/> </state> <end name="ok" state="completed"/> <end-cancel name="bad request"/> <end-error name="internal server error"/> </process>This time, if we would start an execution and signal it to move out of the
get return code
wait state with the
following code, the execution would end with the cancel
state.Similarly, using the value
200
or 500
would cause the execution
to end with the completed
or with the error
states
respectively.
6.2.6. task
6.2.6.1. task
assignee
Table 6.10.
task
attributes:Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
assignee | expression | optional | userId referring to the person that is responsible for completing this task. |
<process name="TaskAssignee">
<start>
<transition to="review" />
</start>
<task name="review"
assignee="#{order.owner}">
<transition to="wait" />
</task>
<state name="wait" />
</process>
This process shows 2 aspects of task assignment. First, that the
attribute assignee
is used to indicate the user that is
responsible for completing the task. The assignee is a String property
of a task and refers to a user.
Secondly, this attribute is by default evaluated as an expression. In this case the task is assigned to
#{order.owner}
.
Which means that first an object is searched for with name order. One of
the places where this object is looked up is the process variables
associated to the task. Then the getOwner()
getter
will be used to get the userId that references the user that is
responsible for completing this task.
Here's the Order class used in our example:
public class Order implements Serializable { String owner; public Order(String owner) { this.owner = owner; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } }Next a new process instance is created with an order as a process variable.
Map<String, Object> variables = new HashMap<String, Object>(); variables.put("order", new Order("johndoe")); ProcessInstance processInstance = executionService .startProcessInstanceByKey("TaskAssignee", variables);Then the task list for
johndoe
can be obtained like this.List<Task> taskList = taskService.findPersonalTasks("johndoe");Note that it is also possible to put plain text like
assignee="johndoe"
. In that case
the task will be assigned to johndoe.
6.2.6.2. task
candidates
Table 6.11.
task
attributes:Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
candidate-groups | expression | optional | resolves to a comma separated list of groupIds. All the people in the groups will be candidates for this task. | |
candidate-users | expression | optional | resolves to a comma separated list of userIds. All the users will be candidates for this task. |
Figure 6.11. The task candidates example process
Here's an example process using task candidates:
<process name="TaskCandidates">
<start>
<transition to="review" />
</start>
<task name="review"
candidate-groups="sales-dept">
<transition to="wait" />
</task>
<state name="wait"/>
</process>
After starting, a task will be created. The task will not show up in anyone's
personal task list. Following task lists will be empty.taskService.getPersonalTasks("johndoe"); taskService.getPersonalTasks("joesmoe");
But the task will show up in the group task list of all members of the
sales-dept
group.
The in our example, the
sales-dept
has two members: johndoe and joesmoeidentityService.createGroup("sales-dept"); identityService.createUser("johndoe", "johndoe", "John", "Doe"); identityService.createMembership("johndoe", "sales-dept"); identityService.createUser("joesmoe", "joesmoe", "Joe", "Smoe"); identityService.createMembership("joesmoe", "sales-dept");
So after the process is created, the task will appear in both the group tasks for users johndoe and joesmoe
taskService.findGroupTasks("johndoe"); taskService.findGroupTasks("joesmoe");
Candidates must take a task before they can work on it. This will prevent that two candides start working on the same task. The user interface must only offer the action 'Take' for the tasks in the group task list.
taskService.takeTask(task.getDbid(), "johndoe");When a user takes a task, the assignee of that task will be set to the given user. The task will disappear from all the candidate's group task list and it will appear in the user's assigned tasks.
Users are only allowed to work on tasks in their personal task list. This should be enforced by the user interface.
Similarly, the attribute
candidate-users
can be used that
resolves to a comma separated list of userIds. The candidate-users
attribute can be used in combination with other assignment options.
6.2.6.3. task
assignment handler
AssignmentHandler
can be used to calculate the
assignee and the candidates for a task programmatically.
public interface AssignmentHandler extends Serializable {
/** sets the actorId and candidates for the given assignable. */
void assign(Assignable assignable, OpenExecution execution) throws Exception;
}
Assignable
is a common interface for Tasks and
Swimlanes. So AssignmentHandlers can be used for tasks as well as swimlanes
(see later).
assignment-handler
is a sub element of the task element.
It specifies a user code object. So the attributes and elements of assignment-handler
are documented in Section 6.7, “User code”
Let's look at the task assignment example process.
Figure 6.12. The task assignment handler example process
<process name="TaskAssignmentHandler" xmlns="http://jbpm.org/4.4/jpdl">
<start g="20,20,48,48">
<transition to="review" />
</start>
<task name="review" g="96,16,127,52">
<assignment-handler class="org.jbpm.examples.task.assignmenthandler.AssignTask">
<field name="assignee">
<string value="johndoe" />
</field>
</assignment-handler>
<transition to="wait" />
</task>
<state name="wait" g="255,16,88,52" />
</process>
The referenced class AssignTask
looks like this: public class AssignTask implements AssignmentHandler { String assignee; public void assign(Assignable assignable, OpenExecution execution) { assignable.setAssignee(assignee); } }Please note that potentially, AssignmentHandler implementations can use the process variables and any other Java API to access resources like your application database to calculate the assignee and candidate users and groups.
Starting a new process instance of the
TaskAssignmentHandler
process will immediately bring the new execution to the task activity. A new
review
task is created and at that point, the AssignTask
assignment handler is called. That will set johndoe
as
the assignee. So John Doe will find the task in his personal task list.
6.2.6.4. task
swimlanes
A swimlane can also be considered as a process role. In some cases, this might boil down to authorization roles in the identity component. But bare in mind that it is not always the same thing.
Table 6.12.
task
attributes:Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
swimlane | swimlane (string) | optional | refers to a swimlane that is declared in the process |
Swimlanes can be declared inside a process element:
Table 6.13.
swimlane
attributes:Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
name | swimlane (string) | required | Name for this swimlane. This is the name that will be referenced by task swimlane attributes. | |
assignee | expression | optional | userId referring to the person that is responsible for completing this task. | |
candidate-groups | expression | optional | resolves to a comma separated list of groupIds. All the people in the groups will be candidates for this the tasks in this swimlane. | |
candidate-users | expression | optional | resolves to a comma separated list of userIds. All the users will be candidates for the tasks in this swimlane. |
Figure 6.13. The task swimlane example process
The task swimlane example has the following process file :
<process name="TaskSwimlane" xmlns="http://jbpm.org/4.4/jpdl"> <swimlane name="sales representative" candidate-groups="sales-dept" /> <start> <transition to="enter order data" /> </start> <task name="enter order data" swimlane="sales representative"> <transition to="calculate quote"/> </task> <task name="calculate quote" swimlane="sales representative"> </task> </process>In this example we create the following information in the identity component:
identityService.createGroup("sales-dept"); identityService.createUser("johndoe", "johndoe", "John", "Doe"); identityService.createMembership("johndoe", "sales-dept");After starting a new process instance, user
johndoe
will
be a candidate for task enter order data
. Again like in the
previous task candidates example, John Doe can now take this task like this:
taskService.takeTask(taskDbid, "johndoe");Taking the task will make Lit
johndoe
the assignee for
the task. And since this task is coupled to the swimlane
sales representative
, assignee johndoe
will
also be propagated as the assignee in the swimlane.Next, John Doe can complete the task like this:
taskService.completeTask(taskDbid);Completing the task will bring the process execution to the next task, which is
calculate quote
. Also
this task is linked to the swimlane. Therefore, the task will be
assigned to johndoe
. Also the candidate users
and candidate groups of the initial assignment will be copied from
the swimlane to the task. This is relevant in case user johndoe
would release the task and offer it back to the other candidates.
6.2.6.5. task
variables
Getting task variables can be done like this:
List<Task> taskList = taskService.findPersonalTasks("johndoe"); Task task = taskList.get(0); long taskDbid = task.getDbid(); Set<String> variableNames = taskService.getVariableNames(taskDbid); Map<String, Object> variables = taskService.getVariables(taskDbid, variableNames);And setting task variables can be done like this:
variables = new HashMap<String, Object>(); variables.put("category", "small"); variables.put("lires", 923874893); taskService.setVariables(taskDbid, variables);
6.2.6.6. e-mail support in tasks
process-engine-context
section of the
configuration file.
Table 6.14.
task
elementsElement | Multiplicity | Description |
---|---|---|
notification | 0..1 | Sends a notification message when a task is assigned. If no template is referenced or supplied inline, mail support falls back on the template named task-notification. |
reminder | 0..1 | Sends a reminder message at specific intervals. If no template is referenced or supplied inline, mail support falls back on the template named task-reminder. |
Table 6.15.
notification
attributes:Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
continue | {sync | async | exclusive} | sync | optional | Specifies if an asynchronous continuation should be introduced right before sending this notification email. |
Table 6.16.
reminder
attributes:Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
duedate | duration (plain string or containing expression) | required | Delay before a reminder email should be send. | |
repeat | duration (plain string or containing expression) | optional | Delay after a subsequent reminder email should be send | |
continue | {sync | async | exclusive} | sync | optional | Specifies if an asynchronous continuation should be introduced right before sending this notification email. |
Here is a basic example that accepts the default templates.
<task name="review" assignee="#{order.owner}" <notification/> <reminder duedate="2 days" repeat="1 day"/> </task>
6.2.7. sub-process
Table 6.17.
sub-process
attributes:Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
sub-process-id | string or expression | either this or sub-process-key is required | Identifies the sub process by the id. This means that a specific version of a process definition is referenced. Sub process id can be specified as simple text or EL expression. | |
sub-process-key | string or expression | either this or sub-process-key is required | Identifies the sub process by the key. This means that the latest version of the process definition with the given key is referenced. The latest version of the process is looked up each time the activity executes. Sub process key can be specified as simple text or EL expression. | |
outcome | expression | required when transitions have outcome-value 's specified | Expression that is evaluated when the sub process
instance ends. The value is then used for outcome transition mapping.
Add outcome-value elements to the outgoing transitions
of this sub-process activity.
|
Table 6.18.
sub-process
elements:Element | Multiplicity | Description |
---|---|---|
parameter-in | 0..* | Declares a variable that is passed to the sub process instance when it is created. |
parameter-out | 0..* | Declares a variable that will be set in the super process execution when the sub process ends. |
Table 6.19.
parameter-in
attributes:Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
subvar | string | required | The name of the sub process variable in which the value is set. | |
var | string | exactly one of {'var', 'expr'} is required to specify the value | The name of the variable in the super process execution context. | |
expr | string | exactly one of {'var', 'expr'} is required to specify the value | An expression that will be resolved in the super process execution context. The resulting value will be set in the sub process variable. | |
lang | string | juel | optional | The scripting language in which the expression should be resolved. |
Table 6.20.
parameter-out
attributes:Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
var | string | required | The name of the variable in the super process execution context in which the value will be set. | |
subvar | string | exactly one of {'subvar', 'expr'} is required to specify the value | The name of the sub process variable from which the value will be taken. | |
expr | string | exactly one of {'subvar', 'expr'} is required to specify the value | An expression that will be resolved in the sub process execution context. The resulting value will be set in the super process variable. | |
lang | string | juel | optional | The scripting language in which the expression should be resolved. |
Table 6.21. Extra
transition
elements in case of outcome variable mappings:Element | Multiplicity | Description |
---|---|---|
outcome-value | 0..1 | If the outcome matches the value, this
transition is taken after the sub-process ended. The value is specified with one child
element.
|
6.2.7.1. sub-process
variables
The parent process involves a document that needs to be reviewed.
Figure 6.14. The subprocess document example process
<process name="SubProcessDocument" xmlns="http://jbpm.org/4.4/jpdl">
<start>
<transition to="review" />
</start>
<sub-process name="review"
sub-process-key="SubProcessReview">
<parameter-in var="document" subvar="document" />
<parameter-out var="reviewResult" subvar="result" />
<transition to="wait" />
</sub-process>
<state name="wait"/>
</process>
The review process is a reusable process for all kinds of reviews.
Figure 6.15. The subprocess review example process
<process name="SubProcessReview" xmlns="http://jbpm.org/4.4/jpdl"> <start> <transition to="get approval"/> </start> <task name="get approval" assignee="johndoe"> <transition to="end"/> </task> <end name="end" /> </process>The document process is started with a document variable:
Map<String, Object> variables = new HashMap<String, Object>(); variables.put("document", "This document describes how we can make more money..."); ProcessInstance processInstance = executionService .startProcessInstanceByKey("SubProcessDocument", variables);Then the parent process execution will arrive in the sub process activity. A sub process instance is created and linked with the super process execution. When the
SubProcessReview
process
instance starts, it arrives in the task
. A task will be
created for johndoe
.
List<Task> taskList = taskService.findPersonalTasks("johndoe"); Task task = taskList.get(0);We can see that the document has been passed from the super process instance to the sub process instance:
String document = (String) taskService.getVariable(task.getDbid(), "document"); assertEquals("This document describes how we can make more money...", document);Then we set a variable on the task. This is typically done through a form. But here we'll show how it is done programmatically.
Map<String, Object> variables = new HashMap<String, Object>(); variables.put("result", "accept"); taskService.setVariables(task.getDbid(), variables);Completing this task, will cause the sub process instance to end.
taskService.completeTask(task.getDbid());When the sub process ends, the super process execution will get signalled(=notified). First the
result
variable from the sub process instance
will be copied into the reviewResult
variable in the
super process execution. Then the super process execution will continue
and leave the review activity.
6.2.7.2. sub-process
outcome value
SubProcessOutcomeValueTest
example, the value
of a sub process variable is used to select the outgoing transition
of the sub-process
activity.
Figure 6.16. The subprocess document example process
<process name="SubProcessDocument"> <start> <transition to="review" /> </start> <sub-process name="review" sub-process-key="SubProcessReview" outcome="#{result}"> <transition name="ok" to="next step" /> <transition name="nok" to="update" /> <transition name="reject" to="close" /> </sub-process> <state name="next step" /> <state name="update" /> <state name="close" /> </process>The
SubProcessReview
is the same as above in the
subprocess variables example:
Figure 6.17. The subprocess review example process for outcome value
<process name="SubProcessReview" xmlns="http://jbpm.org/4.4/jpdl"> <start> <transition to="get approval"/> </start> <task name="get approval" assignee="johndoe"> <transition to="end"/> </task> <end name="end" /> </process>A new document process instance is started like usual:
ProcessInstance processInstance = executionService .startProcessInstanceByKey("SubProcessDocument");Then task is fetched from
johndoe
's task listList<Task> taskList = taskService.findPersonalTasks("johndoe"); Task task = taskList.get(0);Then the
result
variable is set and
the task is completed.
Map<String, Object> variables = new HashMap<String, Object>(); variables.put("result", "ok"); taskService.setVariables(task.getId(), variables); taskService.completeTask(task.getDbid());In this scenario, the
ok
transition is taken in
the parent process out of the sub-process review activity. The example
test case also shows other scenarios.
6.2.7.3. sub-process
outcome activity
SubProcessOutcomeActivityTest
example, the resulting end activity is used to select the outgoing transition of the sub-process
activity.
Figure 6.18. The subprocess document example process for outcome activity
<process name="SubProcessDocument"> <start> <transition to="review" /> </start> <sub-process name="review" sub-process-key="SubProcessReview"> <transition name="ok" to="next step" /> <transition name="nok" to="update" /> <transition name="reject" to="close" /> </sub-process> <state name="next step" /> <state name="update" /> <state name="close" /> </process>The
SubProcessReview
now has multiple end activities:
Figure 6.19. The subprocess review example process for outcome activity
<process name="SubProcessReview" xmlns="http://jbpm.org/4.4/jpdl">
<start>
<transition to="get approval"/>
</start>
<task name="get approval"
assignee="johndoe">
<transition name="ok" to="ok"/>
<transition name="nok" to="nok"/>
<transition name="reject" to="reject"/>
</task>
<end name="ok" />
<end name="nok" />
<end name="reject" />
</process>
A new document process instance is started like usual:
ProcessInstance processInstance = executionService .startProcessInstanceByKey("SubProcessDocument");Then task is fetched from
johndoe
's task listList<Task> taskList = taskService.findPersonalTasks("johndoe"); Task task = taskList.get(0);Then the task is completed with outcome
ok
.
taskService.completeTask(task.getDbid(), "ok");This will cause the sub process to end in end activity
ok
.
The super process execution will then take outgoing transition ok
to next step
.
The example test case also shows the other scenarios.
6.2.8. custom
A custom activity refers to user code. See Section 6.7, “User code” for more details on the specific attributes and elements. Let's look at the example:
<process name="Custom" xmlns="http://jbpm.org/4.4/jpdl"> <start > <transition to="print dots" /> </start> <custom name="print dots" class="org.jbpm.examples.custom.PrintDots"> <transition to="end" /> </custom> <end name="end" /> </process>The custom activity behaviour class
PrintDots
shows that it's possible to control the flow when implementing
custom activity behaviours. In this case the PrintDots
acitivity implementation will after printing dots wait in the activity until
a signal is given.
public class PrintDots implements ExternalActivityBehaviour { private static final long serialVersionUID = 1L; public void execute(ActivityExecution execution) { String executionId = execution.getId(); String dots = ...; System.out.println(dots); execution.waitForSignal(); } public void signal(ActivityExecution execution, String signalName, Map<String, ?> parameters) { execution.take(signalName); } }
6.3. Automatic activities
6.3.1. java
Table 6.22.
java
attributes:Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
class | classname | either 'class' or 'expr' has to be specified | The fully qualified classname. See Section 6.7.2, “User code classloading” for classloading information. The user code object will be lazy initialized and cached as part of the process definition. | |
expr | expression | either 'expr' or 'class' has to be specified | An expression that returns the target object on which the method should be invoked. | |
method | methodname | required | The name of the method to invoke | |
var | variablename | optional | The name of the variable in which the return value should be stored. |
Table 6.23.
java
elements:Element | Multiplicity | Description |
---|---|---|
field | 0..* | describes a configuration value to inject in a memberfield before the method is invoked. |
arg | 0..* | method parameters |
Consider the following example.
Figure 6.20. A java task
<process name="Java" xmlns="http://jbpm.org/4.4/jpdl"> <start > <transition to="greet" /> </start> <java name="greet" class="org.jbpm.examples.java.JohnDoe" method="hello" var="answer" > <field name="state"><string value="fine"/></field> <arg><string value="Hi, how are you?"/></arg> <transition to="shake hand" /> </java> <java name="shake hand" expr="#{hand}" method="shake" var="hand" > <arg><object expr="#{joesmoe.handshakes.force}"/></arg> <arg><object expr="#{joesmoe.handshakes.duration}"/></arg> <transition to="wait" /> </java> <state name="wait" /> </process>Classes involved:
public class JohnDoe { String state; Session session; public String hello(String msg) { if ( (msg.indexOf("how are you?")!=-1) && (session.isOpen()) ) { return "I'm "+state+", thank you."; } return null; } }
public class JoeSmoe implements Serializable { static Map<String, Integer> handshakes = new HashMap<String, Integer>(); { handshakes.put("force", 5); handshakes.put("duration", 12); } public Map<String, Integer> getHandshakes() { return handshakes; } }
public class Hand implements Serializable { private boolean isShaken; public Hand shake(Integer force, Integer duration) { if (force>3 && duration>7) { isShaken = true; } return this; } public boolean isShaken() { return isShaken; } }The first java activity
greet
specifies that during its execution an instance of the
class org.jbpm.examples.java.JohnDoe
will be instantiated and the method
hello
of this class will be invoked on the resulting object. The variable named
answer
will contain the result of the invocation.
The class above reveals that it contains two fields named
state
and session
and that the method hello
accepts one argument. During the execution the values specified in the
field
and arg
configuration elements will be used. The expected result of creating
a process instance is that the process variable answer
contains the string
I'm fine, thank you.
.
The second java activity is named
shake hand
. It will resolve
expression #{hand}
and capture the resulting object as the target object. On that object, the method
shake
will be invoked. The two arguments will be calculated by resolving
the respective expressions #{joesmoe.handshakes.force}
and
#{joesmoe.handshakes.duration}
. The resulting object is a mofied
version of the hand and var="hand"
will cause the modified
hand to overwrite the old hand
variable value.
6.3.2. script
There are 2 ways of specifying a script:
6.3.2.1. script
expression
expr
attribute.
This is for short expressions that are easier expressed in an attribute
then in a text element. If no lang
is specified,
the default-expression-language is used.
Table 6.24.
script
expression attributes:Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
expr | text | required | the expression text to evaluate. | |
lang | scripting language name as defined in Chapter 8, Scripting | the default expression language as defined in Chapter 8, Scripting | optional | the language in which the expression is specified. |
var | variablename | optional | name of the variable in which the return value should be stored. |
In the next example, we'll see how a script activity with an expression and how the result is stored in a variable.
Figure 6.21. The script.expression example process
<process name="ScriptExpression" xmlns="http://jbpm.org/4.4/jpdl"> <start> <transition to="invoke script" /> </start> <script name="invoke script" expr="Send packet to #{person.address}" var="text"> <transition to="wait" /> </script> <state name="wait"/> </process>This example uses a
Person
class that looks like this.
public class Person implements Serializable { String address; public Person(String address) { this.address = address; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } }When starting a process instance for this process, we supply a person with a given address property as variable
person
.
Map<String, Object> variables = new HashMap<String, Object>(); variables.put("person", new Person("Honolulu")); executionService.startProcessInstanceByKey("ScriptText", variables);After the execution of the script activity, variable
text
will contain 'Send packet to Honolulu'.
6.3.2.2. script
text
text
element.
This is convenient when the script text spans multiple lines.
Table 6.25.
script
text attributes:Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
lang | scripting language name as defined in Chapter 8, Scripting | the default scripting language as defined in Chapter 8, Scripting | optional | the language in which the script is specified. |
var | variablename | optional | name of the variable in which the return value should be stored. |
Table 6.26.
script
text elements:Element | Multiplicity | Description |
---|---|---|
text | 1 | contains the script text |
For example
Figure 6.22. The script.text example process
<process name="ScriptText" xmlns="http://jbpm.org/4.4/jpdl"> <start> <transition to="invoke script" /> </start> <script name="invoke script" var="text"> <text> Send packet to #{person.address} </text> <transition to="wait" /> </script> <state name="wait"/> </process>
6.3.3. hql
hql
activity, a HQL query can be performed
on the database and the result is stored in a process variable.
Table 6.27.
hql
attributes:Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
var | variablename | required | the name of the variable in which the result is stored. | |
unique | {true, false} | false | optional | a value of true means that the result from the hibernate
query should be obtained with method uniqueResult() .
The default is false and in that case the list()
method will be used to get the result.
|
Table 6.28.
hql
elements:Element | Multiplicity | Description |
---|---|---|
query | 1 | The HQL query. |
parameter | 0..* | The query parameters |
For example:
Figure 6.23. The hql example process
<process name="Hql" xmlns="http://jbpm.org/4.4/jpdl"> <start> <transition to="get process names" /> </start> <hql name="get process names" var="activities with o"> <query> select activity.name from org.jbpm.pvm.internal.model.ActivityImpl as activity where activity.name like :activityName </query> <parameters> <string name="activityName" value="%o%" /> </parameters> <transition to="count activities" /> </hql> <hql name="count activities" var="activities" unique="true"> <query> select count(*) from org.jbpm.pvm.internal.model.ActivityImpl </query> <transition to="wait" /> </hql> <state name="wait"/> </process>
6.3.4. sql
sql
activity is exactly the same as the
hql activity, with the only difference that
session.createSQLQuery(...)
is used.
6.3.5. mail
mail
activity, process authors are
able to specify the content of an email message to be sent to multiple
recipients at once. Every email message is produced from a template.
Templates may be specified inline or in the process-engine-context
section of the configuration file.
Table 6.29.
mail
attributesAttribute | Type | Default | Required? | Description |
---|---|---|---|---|
template | string | no | Reference to a mail-template element in the
configuration file. If absent, the template must be specified
inline using the child elements. |
Table 6.30.
mail
elementsElement | Multiplicity | Description |
---|---|---|
from | 0..1 | list of sender(s) |
to | 1 | list of primary recipients |
cc | 0..1 | list of carbon copy recipients |
bcc | 0..1 | list of blind carbon copy recipients |
subject | 1 | text content of this element becomes the message subject |
text | 0..1 | text content of this element becomes the message text content |
html | 0..1 | text content of this element becomes the message HTML content |
attachments | 0..1 | each attachment is configured in a separate subelement |
Table 6.31.
attachment
attributesAttribute | Type | Default | Required? | Description |
---|---|---|---|---|
name | string | no, unless expression is present | File name associated with this attachment.
If absent, data sources that encapsulate files such as resource ,
file and url provide a reasonable
fallback value. | |
description | string | no | Descriptive information associated with this attachment. | |
expression | string | one of expression , file ,
url or resource must be present | Expression that evaluates to a representation of the attachment data in the form of a Java object. Useful to extract content from process variables. | |
file | string | Path to the attachment data in the file system. The denoted file must exist. | ||
url | string | Location of the attachment data in the worldwide web. The pointed resource must exist. | ||
resource | string | Name of the resource containing the attachment data in the class path. The denoted resource must exist. | ||
mime-type | string | no, unless expression is present | MIME type of the object returned by the expression. |
Example usage:
<process name="InlineMail" xmlns="http://jbpm.org/4.4/jpdl"> <start> <transition to="send birthday reminder note" /> </start> <mail name="send birthday reminder note"> <to addresses="johnDoe@some-company.com" /> <subject>Reminder: ${person} celebrates his birthday!</subject> <text>Do not forget: ${date} is the birthday of ${person} </text> <attachments> <attachment resource="org/example/birthday_card.png"/> <attachment name="picture.jpg" expression="${picture}" mime-type="image/jpeg"/> </attachments> <transition to="end" /> </mail> <state name="end"/> </process>
6.4. Common activity contents
Table 6.32. Common activity attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
name | any text | required | name of the activity |
Table 6.33. Common activity elements:
Element | Multiplicity | Description |
---|---|---|
transition | 0..* | the outgoing transitions |
6.5. Events
The EventListener interface looks like this:
public interface EventListener extends Serializable {
void notify(EventListenerExecution execution) throws Exception;
}
All automatic activities can be used as
event listeners as well.To associate a list of event listeners with a process or an activity, use the
on
element to group the event listeners and specifiy the
event. on
can be nested as a subelement of process
or any activity.
To associate a list of event listeners with a transition
take
event, just include the event listeners directly in the transition
element.
Table 6.34.
on
attributes:Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
event | {start | end} | required | name name of the event |
Table 6.35.
on
elements:Element | Multiplicity | Description |
---|---|---|
event-listener | 0..* | An event listener implementation object. |
any automatic activity | 0..* |
Table 6.36. event listener attributes:
event-listener
is user code so it can be configured
like described in Section 6.7, “User code”.
Any automatic activities (including event-listener) that are placed on events can specify following additional attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
propagation | {enabled | disabled | true | false | on | off} | disabled | optional | indicates if the event listener should also be invoked for propagating events. |
continue | {sync | async | exclusive} | sync | optional | indicates if the execution should be continued asynchronously right before the event listener is executed. @see also Section 6.6, “Asynchronous continuations” |
6.5.1. Event listener example
Figure 6.24. The event listener example process
<process name="EventListener" xmlns="http://jbpm.org/4.4/jpdl"> <on event="start"> <event-listener class="org.jbpm.examples.eventlistener.LogListener"> <field name="msg"><string value="start on process definition"/></field> </event-listener> </on> <start> <transition to="wait"/> </start> <state name="wait"> <on event="start"> <event-listener class="org.jbpm.examples.eventlistener.LogListener"> <field name="msg"><string value="start on activity wait"/></field> </event-listener> </on> <on event="end"> <event-listener class="org.jbpm.examples.eventlistener.LogListener"> <field name="msg"><string value="end on activity wait"/></field> </event-listener> </on> <transition to="park"> <event-listener class="org.jbpm.examples.eventlistener.LogListener"> <field name="msg"><string value="take transition"/></field> </event-listener> </transition> </state> <state name="park"/> </process>
LogListener
will maintain a list of logs as a process variable:public class LogListener implements EventListener {
// value gets injected from process definition
String msg;
public void notify(EventListenerExecution execution) {
List<String> logs = (List<String>) execution.getVariable("logs");
if (logs==null) {
logs = new ArrayList<String>();
execution.setVariable("logs", logs);
}
logs.add(msg);
execution.setVariable("logs", logs);
}
}
Next, we start a new process instance.ProcessInstance processInstance =
executionService.startProcessInstanceByKey("EventListener");
Then the process instance executes up to the wait activity. So we provide a signal and that will cause it to execute till the end.
Execution execution = processInstance.findActiveExecutionIn("wait"); executionService.signalExecutionById(execution.getId());The list of log messages will now look like this:
[start on process definition, start on activity wait, end on activity wait, take transition]
6.5.2. Event propagation
By default, event listeners are only invoked for events that are fired on the elements on which the event listeners are subscribed. But by specifying
propagation="enabled"
, the event
listener will also be invoked for all events that are fired on contained elements.
6.6. Asynchronous continuations
ExecutionService.startProcessInstanceById(...)
or ExecutionService.signalProcessInstanceById(...)
will cause
the process to be executed in the thread it was called from (=client). In other words, those
methods will only return after the process execution has arrived in a wait state.
This default behaviour has a couple of advantages: user application transactions can be easily propagated to jBPM to that jBPM's DB updates are done in the user's transaction context. Secondly, it's possible for a client to get an exception in case something goes wrong during execution of the process. Usually, the automatic work that has to be done as part of the process inbetween two wait states is relatively small. Even if multiple automatic activities are executed inbetween 2 wait states. So in most situations, it's good to do all that work in a single transaction. This explains that the default behaviour of jPDL is to perform all work of the process synchronously in the thread of client.
For those cases where you don't want the call to jBPM to be blocking until all the automatic work is done, jPDL allows for very fine grained control over transaction boundaries. On various places in the process, asynchronous continuations can be introduced. Asynchronous continuations cause the transaction to commit and the jBPM method invocation will return. jBPM will then start a new transaction in a new thread and continue the rest of the automatic process work asynchronously. jBPM uses asynchronous messaging internally to accomplish this.
Upon an asynchronous continuation, an asynchronous message will be sent as part of the currently ongoing transaction. And then the originally invoked method like e.g.
startProcessInstanceById(...)
or signalProcessInstanceById(...)
will return. When the
asynchronous message is committed and then processed, it will start a new transaction
and resume execution where it left off.
Table 6.37. Attribute of any activity,
transition
or on
:Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
continue | {sync | async | exclusive} | sync | optional | indicates if an asynchronous continuation should be performed before the element is executed. |
- sync (default) keep executing the element as part of the ongoing transaction.
- async introduces an asynchronous continuation (aka safe point). The ongoing transaction is committed and the element is executed in a new transaction. Transactional asynchronous messaging is used by the jBPM implementation to achieve this.
- exclusive introduces a asynchronous continuation (aka safe point). The ongoing transaction is committed and the element is executed in a new transaction. Transactional asynchronous messaging is used by the jBPM implementation to achieve this. Exclusive messages will not be processed concurrently. jBPM will make sure that exclusive jobs for the same process instance are not executed concurrently, even if your jBPM configuration has multiple asynchronous message processors (like the JobExecutor) running on different systems. This can be used to prevent optimistic locking failures in case multiple, potentially conflicting jobs are scheduled in the same transaction.
6.6.1. Async activity
Figure 6.25. The async activity example process
<process name="AsyncActivity" xmlns="http://jbpm.org/4.4/jpdl"> <start> <transition to="generate pdf"/> </start> <java name="generate pdf" continue="async" class="org.jbpm.examples.async.activity.Application" method="generatePdf" > <transition to="calculate primes"/> </java> <java name="calculate primes" continue="async" class="org.jbpm.examples.async.activity.Application" method="calculatePrimes"> <transition to="end"/> </java> <end name="end"/> </process>
public class Application { public void generatePdf() { // assume long automatic calculations here } public void calculatePrimes() { // assume long automatic calculations here } }
ProcessInstance processInstance = executionService.startProcessInstanceByKey("AsyncActivity"); String processInstanceId = processInstance.getId();Without the asynchronous continuations, this would be an all automatic process and the process would execute all the way up to the end in method
startProcessInstanceByKey
But with
continue="async"
the execution only
goes untill it is about to execute activity generate pdf
. Then
an asynchronous continuation message is send and the startProcessInstanceByKey
method returns.
In a normal configuration, the job executor will automatically pick up the message and execute it. But for testing scenarios and for these examples we want to control when messages are executed so the job executor is not configured. Therefore we have to execute the jobs manually like this:
Job job = managementService.createJobQuery() .processInstanceId(processInstanceId) .uniqueResult(); managementService.executeJob(job.getDbid());That will bring the process until it's about to execute activity
calculate primes
and again an asynchronous message is
send.
Then the message can be looked up again and when that message is executed, that transaction will run the execution till the end.
6.6.2. Async fork
Figure 6.26. The async fork example process
<process name="AsyncFork" xmlns="http://jbpm.org/4.4/jpdl">
<start >
<transition to="fork"/>
</start>
<fork >
<on event="end" continue="exclusive" />
<transition />
<transition />
</fork>
<java class="org.jbpm.examples.async.fork.Application" >
<transition />
</java>
<java class="org.jbpm.examples.async.fork.Application" >
<transition />
</java>
<join >
<transition to="end"/>
</join>
<end />
</process>
public class Application { public void shipGoods() { // assume automatic calculations here } public void sendBill() { // assume automatic calculations here } }By placing the asynchronous continuation on the
end
event of the fork (<on event="end" continue="exclusive" />
),
each forked execution that takes a transition out of the
fork will be continued asynchronously.
Value
exclusive
was selected to serialize the executions of
the 2 asynchonous continuation jobs resulting from the fork. The respective transactions
that will execute activities ship goods
and send bill
will both arrive at the join. At the join, both
transactions will synchronize on the same execution (read: update the same execution
row in the DB), resulting in a potential optimistic locking failure.ProcessInstance processInstance =
executionService.startProcessInstanceByKey("AsyncFork"); String processInstanceId = processInstance.getId(); List<Job> jobs = managementService.createJobQuery() .processInstanceId(processInstanceId) .list(); assertEquals(2, jobs.size()); Job job = jobs.get(0); // here we simulate execution of the job, // which is normally done by the job executor managementService.executeJob(job.getDbid()); job = jobs.get(1); // here we simulate execution of the job, // which is normally done by the job executor managementService.executeJob(job.getDbid()); Date endTime = historyService .createHistoryProcessInstanceQuery() .processInstanceId(processInstance.getId()) .uniqueResult() .getEndTime(); assertNotNull(endTime);
6.7. User code
custom
event-listener
assignment-handler
in taskhandler
in decisioncondition
in transition
6.7.1. User code configuration
Table 6.38. attributes:
Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
class | classname | one of {class|expr} is required | The fully qualified classname. Instantiation is done only once and the user object is cached as part of the process definition. | |
expr | expression | one of {class|expr} is required | Expression for which the resulting value will be taken as the target object. Expressions will be evaluated for every usage. In other words, the resulting value of the evaluation will not be cached. |
Table 6.39. user code configuration elements:
Element | Multiplicity | Description |
---|---|---|
field | 0..* | describes a configuration value to be injected directly in a memberfield before this user class is used. |
property | 0..* | describes a configuration value to injected through a setter method before this user object is used. |
Table 6.40.
field
and property
attributes:Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
name | string | required | the name of the field or property. |
Table 6.41. field and property contained element:
field
and property
elements
have exactly one child element that represents the value that will be
injected.
Element | Multiplicity | Description |
---|---|---|
string | 0..1 | a java.lang.String |
int | 0..1 | a java.lang.Integer |
long | 0..1 | a java.lang.Long |
float | 0..1 | a java.lang.Float |
double | 0..1 | a java.lang.Double |
true | 0..1 | Boolean.TRUE |
false | 0..1 | Boolean.FALSE |
object | 0..1 | a object that will be instantiated with reflection |
Table 6.42. Attribute for basic type
string
, int
, long
, float
and double
:Attribute | Type | Default | Required? | Description |
---|---|---|---|---|
value | text | required | text value that will be parsed to the respective type |
6.7.2. User code classloading
Objects that are referenced by an expression are calculated dynamically.
The devguide also explains an unsupported attribute to prevent that user objects are cached.
For more information follow my Tutorial online @ http://jbpmmaster.blogspot.com/
No comments:
Post a Comment