What the system "basically" does is controlling media equipment to "convert" files.
It's a Web Application that communicates with jBoss Web Server, where jBPM and Drools Guvnor are running.
jBPM was used to design and run workflow. During execution tasks are assigned to the media equipment. To interact with the network resources a SOAP layer is used and load Balancing for parallel tasks is consider. The demo has three "sections":
- Adding service tasks to be used during business process design.
- Business process design with jBPM Web Designer
- Workflow execution and progress notifications.
The following list pairs demo features with the jBPM/Drool Guvnors components used during implementation:
- Add custom tasks to workflow - Done using Drools Guvnor Rest interface
- Embed jBPM Web Designer - Done using Standalone Web Designer
- Display business process preview - Done using Web Designer JavaScript API
- Execute long running tasks - Done using Asynchronous handlers
- Notify user about workflow events - Done using the ProcessEventListener interface
- Share data between activities automatically (without BPMN2.0 data mapping) - Done using the org.drools.definition.process Classes (WorkItemNode, Connection)
Because the three first items were already covered in a previous post I'll start with Asynchronous handlers.
jBPM Task execution with Asynchronous handlers
There really isn't much to it. To execute domain specific tasks, those that run custom code, jBPM uses the notion of WorkItemHandler which is an interface with two methods
executeWorkItem(WorkItem arg0, WorkItemManager arg1) and abortWorkItem(WorkItem arg0, WorkItemManager arg1). To run asynchronous tasks the developer just needs to use a thread to implement the executeWorkItem method. There is one thing to keep in mind though, jBPM needs to be told when a service task has been completed through the method WorkItemManger.completeWorkItem(long workItemHandlerId,Map result) . Due to this method arguments, the thread must have access to the WorkItemManager and the respective WorkItemHandler Id and needs to call it upon completion. The easiest way to do this might be extending Thread in the class that implements WorkItemHandler. Illustration code follows:
public class ActivityHandler implements WorkItemHandler {
@Override
public void executeWorkItem(WorkItem arg0, WorkItemManager arg1) {
new WorkItemExecute(arg0,arg1).start();
}
}
public class WorkItemExecuter extends Thread{
private WorkItem workItem;
private WorkItemManager workItemManager;
public WorkItemExecuter(WorkItem arg0,WorkItemManager arg1)
{
this.workItemManager = arg1;
this.workItem = arg0;
}
@Override
public void run() {
/* Execute the task
* ....*/
/* When finished warn jBPM the task is completed */
this.workItemManager.completeWorkItem(this.workItem.getId(), null);
}
}
Notify user about workflow events
The notifications to the user are made by implementing the ProcessEventListener interface. Whenever events like "before trigger node" and "event completed" are fired the corresponding interface method is called and from there it's possible to notify the user about workflow evolution or request some input needed for a certain task. The workflow event architecture was something on the lines:- beforeNodeTriggered was used to check if the node's "requirements" were fulfilled. For instance the two parallel activities needed some input and the Asset Explorer is called so video "assets could be injected" to the tasks.
- afterProcessCompleted fires the last notification informing the user that the workflow has ended.
Share data between activities automatically
For what I could understand in order to share data between processes' tasks the user must set data association during the BPMN2.0 design process. This assures for example that WorkItems are as independent from processes as possible but on the other hand demands a user interaction that might not be very intuitive. Thus, in order to map inputs and ouputs between processes tasks automatically you can use the org.drools.definition.process.Node and org.drools.definition.process.Connection. These two classes allow you to "navigate" through the workflow and find each tasks' dependencies, so you can map the output of one task as the input of the next one. Be aware of the (WorkflowProcess) cast. Code follows:
KnowledgeBase kbase = kbuilder.newKnowledgeBase();
WorkflowProcess workflow = (WorkflowProcess)kbase.getProcess(processId);
for(Node node:workflow.getNodes())
{
if(node instanceof WorkItemNode)
{
for(String key:node.getOutgoingConnections().keySet())
markOutInputs(node.getOutgoingConnections().get(key));
}
}
private void markOutInputs(List list) {
for(Connection conn:list)
{
Node to = conn.getTo();
/* only reachable workitemnodes will be affected */
if(to instanceof WorkItemNode)
{
/* The connection ends in a WorkItemNode */
/* handle data input mapping */
}
else
for(String key:to.getOutgoingConnections().keySet())
markOutInputs(to.getOutgoingConnections().get(key));
}
}