Showing posts with label Oryx. Show all posts
Showing posts with label Oryx. Show all posts

Tuesday, July 17, 2012

jBPM application example demo

This is a DEMO of what is possible to do by embedding jBPM in a Centralized System to execute workflow/business processes.

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:
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.
Getting the information from the jBPM regarding workflow progress is quite easy when using the ProcessEventListener interface, what isn't as trivial is notifying the user in real time about those events due to  HTTP  request/response nature. Considering that a workflow might take hours to complete managing a connection to the client so it's possible to post notification requires some effort. If you can easily push data to your interface environment then this won't be an issue but in the html (Web Socketless) realm it's quite a nagging problem.

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));
  }
 }


  

Tuesday, May 1, 2012

jBPM tools

jBPM, Drools Guvnor and Web designer

This post will cover:
Here it's an overview of the main free BPM.

What is jBPM

jBPM is a Business Process Management System which in short words means that its main function is to run business processes. 
It's possible to look at business processes as workflows which are basically a group of sequenced activities with inputs and ouputs. 

Let's use an example to better explain how it works:
  1. A TV broadcaster wishes to capture an important event
  2. Afterwards Introduce some information about the recorded media
  3. And then publish the video
    1. To the channel's web site (normal quality)
    2. To the broadcasting station (high quality)
This workflow with 5 activities can been described as a business process using a language understood by jBPM, which is the BPMN2.0 standard (Business Process Modeling Notation).

BPMN2.0 is basically a standardized XML containing information about the visual and execution counterparts of the business process.
This way everyone that knows the BPMN2.0 standard understands the visual representation and business process management systems as the jBPMN can execute it.

The referred workflow can been seen here, modeled using the designer:



jBPM would then run the workflow and do any kind of work demanded by the activities.

In the described example that could mean start capturing the video at 9 pm on a defined feed for the duration of one hour, then ask for human input after the capture process ended and start converting the media to two different file types and finally save them to a specific location.How this is done in detail will be covered next.

So in a nutshell a workflow/business process is described using BPMN2.0, which in turn is generated by the web designer and the jBPM API has the responsibility to run it.

jBPM different components and how they interact

As we've seen jBPM has the task of processing the workflow but it has to get the generated XML description (in the BPMN2.0 standard) from somewhere. This is where the designer and Guvnor repository come in.

If you download jBPM from here and ran the ant install.demo script that's exactly what you'll have.
Drools guvnor is used as the rule based repository where the bpmn2.0 business process representations are stored along with other assets and the designer is a tool to create those assets.

The following image gives a basic scheme how the different components interact:



Running the business process

jBPM has 3 main components:

  1. The knowledge base
  2. The session
  3. The events
To keep things simple, here it is a very short summary of what they do:

The knowledge base is what you use to load the business process. It basically fetch the bpmn2 file and loads so it can be used by the workflow engine. Involves parsing and compiling the process definition.
The session provied methods to interact with the engine, for instance to start running the business process. 
Finally the events is nothing more then an interface which has methods like afterProcessCompleted.

Note: In java events are simulated by the implementation of an interface and that's exactly what this is. You declare a java class which implements this interface and pass it to the caller which in turn will fire the interface's methods.

So let's see some code on how to run a process stored in the guvnor repository:

The code assumes the presence of jBPM library and that drools guvnor has a package called defaultPackage which contains CaptureEventWorkflow business process.

String title = "CaptureEventWorkflow";
String base_url = "http://localhost:8080/drools-guvnor/org.drools.guvnor.Guvnor/webdav/packages/defaultPackage/";
/* building the knowledge base by fetching the bpmn2 process definition from drools guvnor repository */
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
kbuilder.add(ResourceFactory.newUrlResource(url+title+".bpmn2"), ResourceType.BPMN2);
KnowledgeBase kbase = kbuilder.newKnowledgeBase();
/* now that the knowledge base is ready a session is needed to interact with the workflow engine */
StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();
/* a session might have more than one process to start so providing the process id is required */
ksession.startProcess("defaultPackage.CaptureEventWorkflow");
  

Now let's assume you'd like to keep track of the process execution state. There are a couple of ways to do this, you can inquire jBPM through REST using the following url:

http://{your-address}/gwt-console-server/rs/process/instance/{your-process-instance-id}/activeNodeInfo

Or you could simply pass to the session a class that implements theProcessEventListener interface, like so:

public class EventsClass implements ProcessEventListener {

 public void afterNodeLeft(ProcessNodeLeftEvent arg0) {
  System.out.println("Node with name: "+arg0.getNodeInstance().getNodeName()+" just started");
 }
        ...
}
ksession.addEventListener(new EventsClass());
  
And keep track of the process state using the ProcessEventListener class.

Using the tools within an application

Now that we've covered how to make a business process (using the designer), load it and run it...let's take a look on how we could use these tools in an application.


Adding a new asset to Drools Guvnor (programmatically) 

To add a new asset to droosl guvnor repository it's possible to use the REST API or open the designer in New Asset Mode. 

Using Drools Guvnor REST API


To add a new process, or any kind of asset, to the drools guvnor repository a POST must be done with the asset as a byte stream or atom-xml. More info here.

The following example uses the RestEasy framework which comes by default with jboss.

Note: RestEasy can greatly simplify the REST interactions by allowing the developper to define an interface which will then be used by a proxy to make the requests.

First define the interface with the methods to use:

http://{your.ip}:{your.port}/drools-guvnor/rest/packages/{packageName}/assets  

public interface RepoClient {
 @POST
 @Path("/packages/{packageName}/assets")
 @Consumes(MediaType.APPLICATION_OCTET_STREAM)
 @Produces(MediaType.APPLICATION_ATOM_XML)
        /* The package parameter will replace the {packageName} path field
           the slug will contain the asset name and type (filename)
           the asset parameter is the byte representation   */
 String addAsset(@PathParam("package") String package,@HeaderParam("Slug") String slug, byte[] asset);
}   

To actually do the post of a new process without any elements you can do the following:

RepoClient client = ProxyFactory.create(RepoClient.class,"http://pc-lazevedo:8080/drools-guvnor/rest");
String result = client.addAsset("package", processName.replace(" ", "")+".bpmn2", "".getBytes());  
The result will be a xml with information about the generated process including the asset UUID which might be relevant to hold for later use with the StandAlone Editor.

Opening the Designer in New Asset Mode


By opening the StandAlone editor with the following url, it will automatically create a new asset.

http://{your.ip}:{your.port}/drools-guvnor/org.drools.guvnor.Guvnor/standaloneEditorServlet?client=oryx
With these Get parameters:
  • packageName: the package where the process will be hold
  • categoryName: honestly I don't know what this is for, but I created a category within the previously defined package
  • createNewAsset: true
  • assetName: the business process name (without spaces)
  • assetFormat: bpmn2
Something like:
http://localhost:8080/drools-guvnor/org.drools.guvnor.Guvnor/standaloneEditorServlet?packageName=defaultPackage&categoryName=mycategory&createNewAsset=true&assetName=myprocess&assetFormat=bpmn2&client=oryx

Using the New Asset Mode of the designer is probably the more intuitive one but due to some problems, which will be described next, I preferred to first create the asset through REST and then call the StandAlone Editor on it.

Using the Standalone web designer

In order to embed the drool designer in you application, using the standalone web designer is necessary.
This is where things get a little tricky. Regarding version 2.1 some hacks are necessary to make it work properly.
But let's start on how to open it. To do this you only need to call:


http://{your.ip}:{your.port}/drools-guvnor/org.drools.guvnor.Guvnor/standaloneEditorServlet?assetsUUIDs={asset.UUID}&client=oryx"

The assetsUUIDs is an unique ID that serves to tell what asset the designer should open. After you POST a new asset to the REST API as explained before, the returned xml has this UUID.

As for what I experienced getting this far is quite trivial the problem now is what happens when you try to save the process make changes and then save again. If you are able to do that using the Standalone Editor without any errors, then the following notes can be ignored, if not then this should useful.

Designer, correcting after save changes error

The goal is to force the designer to refresh after saving changes to fix the error when trying to "re-save".
For that there are two options, though only the second one worked for me:

Using the guvnorEditorObject

So after calling the Standalone Designer a JavaScript object  to interact with it should be available. By using a tool like FireBug or Chrome console it's possible to check what methods the object provides. The one that we are interested in is registerAfterSave... which receives as an argument a function to be called after saving the process. There you can simply pass something in the lines of



location=http://{your.ip}:{your.port}/drools-guvnor/org.drools.guvnor.Guvnor/standaloneEditorServlet?assetsUUIDs={asset.UUID}&client=oryx".

Doing location.reload() might not work because, at least with me, the Standalone editor added some Get parameters after loading and refreshing the window results in an error.

Add EventListener to the save button

Well, this is kinda of an ugly solution but honestly it was the one that worked for me. Hold the Standalone Editor in an iframe and on the main window have a javascript function running in 250ms intervals (setInterval) trying to add an event listener to the save button. Something like this:

registerEditorInterval = setInterval(registerEditor, 250);
...

var registerEditor = function()
 {
  if($("#frameEditor").contents().find(".gwt-Button").length>0)
  {
   clearInterval( registerEditorInterval  );
   $("#frameEditor").contents().find(".gwt-Button")[0].addEventListener("click",function(){
    $("#frameEditor")[0].src="<%=request.getParameter("iframeSrc") %>";
    registerIntervals();
    },false);
  }
 };

Getting an image preview of the process

One requirement for the app I'm developing with Guvnor and jBPM is that the user should be able to see the process without the need of opening the designer. For that I needed to "catch" a SVG of the diagram. Fortunetly the Designer has a JavaScript API that provides such method and this is how you can use it:


var svg = frames[0].frames[2].ORYX.EDITOR.getCanvas().getSVGRepresentation();

Again put this inside a function that runs within a defined interval because ORYX.EDITOR takes time to load.

If you would like to resize the SVG to a defined width and height without lossing its aspect ration here it is the math for that:

var svgWidth = parseFloat(svg.getAttribute("width"));
var svgHeight =parseFloat(svg.getAttribute("height"));
var holderWidth = 780;
var holderHeight = 410; 
var svgRacio = svgWidth/svgHeight;
var newY = Math.min(holderHeight,holderWidth/svgRacio);
var newX = svgRacio*newY;
svg.setAttribute("width",newX);
svg.setAttribute("height",newY);
svg.getElementsByTagName("g")[0].setAttribute("transform","scale("+newX/svgWidth+" "+newY/svgHeight+")");


Summing up

First let me start by saying that I was astonished when I realized what these tools can do for FREE. I know it's easy to complain about features that aren't working as well as they should or that things are not as strait forward as they seem in the documentation, BUT these tools are developed in an open source philosophy and have a tone of great features. Obviously, I didn't even scratch the surface with this post but only referred what I found more relevant.

What I think it needs priority attention is the Standalone editor. In my environment after opening the editor if I tried to refresh the window it was in, I'd get an error. The same would happen with multiple consecutive process savings. Without the small "improvements" I referred the Standalone editor simply wasn't working.

Another positive word must go to the support I've received in the jBPM irc channel. I needed help adding a Work Item Definition through the REST API and for two hours I received full attention and got it working.
This is what I consider truly supportive community and is crucial when adopting any kind of software.