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));
}
}
Hi Luis, great post!
ReplyDeleteI have just one comment about the WorkItemHandler which is creating a thread and starting it, you should avoid doing that and allow an external component to resume your process when the external task is over. I wouldn't recommend coding the work item in that way, because if you execute 1000 process instances you will get 1000 threads created.
The rest of the article really rocks man! keep up the good work!
Nice recommendation...but how exactly would be the best way to pause the process and resume it externally ? I mean if I do something like .wait() I'll have the same problem of keeping resources located to the thread.
DeleteMany thanks
Hi Luis,
DeleteI tried the asynchronous workitemhandler example above and it doesn't work. The workitemmanager doesn't persist in the thread. Any other ideas?
Hi Tim,
DeleteSorry but I didn't fully understand what you meant.
Do you mean that when you pass the workitemmanager through the Thread constructor and then persist it in a private atribute, the value is not defined at the time of the thread execution?
Hi Luis, can upload source code of this example?!
ReplyDeleteThanks
The example that I've shown here belongs to a rather complex and extensive web application developed during my internship at a great company called MOG Technologies. Because it uses proprietary software I can't release the code.
DeleteBut I'm more than glade to help with any questions regarding the use of jbpm
Hello Luis, I must mention that your approach to asynchronous handlers does not work. Notice that the WorkItemHandler is a singleton (after all you have to instantiate it and register it by hand). If you extend Thread and call start inside the executeWorkItem method as you have done, when executeWorkItemHandler is called again, it is being called on the same instance of the WorkItemHandler, which calls start on the same thread, leading to a java.lang.IllegalThreadStateException being thrown.
ReplyDeleteThe right way to do it, as far as I can tell, is to have a separate class that has a WorkItem and a WorkItem handler as private fields, which extends Thread. Then you can put all the task logic inside the run() method and everything works nice and dandy. Let me know what you think,
Best,
-Leo
The approach you have suggest is in fact how I implemented but to keep things simple I wrote on the blog a different version where you correctly found a mistake.
DeleteThe code has been edited.
If I got the issue correctly calling a new Thread from another class that implements WorkItemHandler during the executeWorkItem should resolve it. The Thread has access to the workItemManger and the WorkItem in order to warn the execution has ended.