Search This Blog

Monday, December 15, 2014

jBPM Services

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

A process definition is description of the steps in a procedure. For example, an insurance company could have a loan process definition that describes the steps of how the company deals with loan requests.



The loan process definition example
Figure 5.1. The loan process definition example

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.


The loan process instance example

Figure 5.2. The loan process instance example

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:


The loan executions example
Figure 5.3. The loan executions example

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 ProcessInstances and Executions.

5.2. ProcessEngine

Interacting with jBPM occurs through services. The service interfaces can be obtained from the 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

The 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

PropertyValueSource
nameInsurance claimprocess xml
keyInsurance_claimgenerated
version1generated
idInsurance_claim-1generated

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

PropertyValueSource
nameInsurance claimprocess xml
keyICLprocess xml
version1generated
idICL-1generated

5.4. Deleting a deployment

Deleting a deployment will remove it from the DB.
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

Simplest and most common way to start a new process instance for a process definition is like this:

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

If instead you want to start a new process instance in a very specific version, you can use the id of the process definition like this:

ProcessInstance processInstance = 
    executionService.startProcessInstanceById("ICL-1");

5.5.3. With a key

A new process instance can optionally be given a key. This key is a user defined reference to the execution and is sometimes referred to as the 'business key'. A business key must be unique within the scope of all versions of a process definition. Typically it is easy to find such a key in the domain of the business process. For example, an order id or an insurance claim number.

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

A map of named parameter objects can be provided when starting a new process instance. These parameters will be set as variables on the process instance between creation and start of the process instance.

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

When using a 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();
 
Do note that the above solution couples the application logic (too) closely by using knowledge about the actual process structure.

5.7. TaskService

The primary purpose of the TaskService is to provide access to task lists. The code sample will show how to get the task list for the user with id 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

During runtime execution of process instances, events are generated. And from those events, history information on both running and completed process executions are collected in the history tables. The 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 HistoryActivityInstances.

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

The management service is mostly used to manage the jobs. See javadocs for more information. This functionality is also exposed through the jBPM web console.

5.10. Query API

Starting from jBPM 4.0, a new API has been introuced with a query system that covers most of the queries you can think of. Developers who need to write company-specific queries can of course still rely on Hibernate. But for most use cases, the query API will be more then suffice. Queries can be written in a unified way on all major jBPM concepts: Process Instances, Tasks, Deployments, Historical processes, etc.
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