jBPM for Developers: Part 2

Process execution

At this point, where our definitions are ready, we can create an execution of our defined processes. This can be achieved by creating a class where each instance represents one execution of our process definition—bringing our processes to life and guiding the company with their daily activities; letting us see how our processes are moving from one node to the next one. With this concept of execution, we will gain the power of interaction and influence the process execution by using the methods proposed by this class.

We are going to add all of the methods that we need to represent the executional stage of the process, adding all the data and behavior needed to execute our process definitions.

This process execution will only have a pointer to the current node in the process execution. This will let us query the process status when we want.

An important question about this comes to our minds: why do we need to interact with our processes? Why doesn't the process flow until the end when we start it?

And the answer to these important questions is: it depends. The important thing here is to notice that there will be two main types of nodes:

One that runs without external interaction (we can say that is an automatic node). These type of nodes will represent automatic procedures that will run without external interactions.

The second type of node is commonly named wait state or event wait. The activity that they represent needs to wait for a human or a system interaction to complete it. This means that the system or the human needs to create/fire an event when the activity is finished, in order to inform the process that it can continue to the next node.

Wait states versus automatic nodes

The difference between them is basically the activity nature. We need to recognize this nature in order to model our processes in the right way. As we have seen before, a "wait state" or an "event wait" situation could occur when we need to wait for some event to take place from the point of view of the process. These events are classified into two wide groups—Asynchronous System Interactions and Human tasks.

Asynchronous System Interactions

This means the situation when the process needs to interact with some other system, but the operation will be executed in some asynchronous way.

For non-advanced developers, the word "asynchronous" could sound ambiguous or without meaning. In this context, we can say that an asynchronous execution will take place when two systems communicate with each other without blocking calls. This is not the common way of execution in our Java applications. When we call a method in Java, the current thread of execution will be blocked while the method code is executed inside the same thread. See the following example:

The doBackup() method will block until the backup is finished. When this happens, the call stack will continue with the next line in the main class. This blocking call is commonly named as a synchronous call.

On the other hand, we got the non-blocking calls, where the method is called but we (the application) are not going to wait for the execution to finish, the execution will continue to the next line in the main class without waiting.

In order to achieve this behavior, we need to use another mechanism. One of the most common mechanisms used for this are messages.

Let's see this concept in the following image:

In this case, by using messages for asynchronous executions, the doBackup() method will be transformed into a message that will be taken by another thread (probably an external system) in charge of the real execution of the doBackup() code. The main class here will continue with the next line in the code. It's important for you to notice that the main thread can end before the external system finishes doing the backup. That's the expected behavior, because we are delegating the responsibility to execute the backup code in the external system. But wait a minute, how do we know if the doBackup() method execution finished successfully? In such cases, the main thread or any other thread should query the status of the backup to know whether it is ready or not.

Human tasks

Human tasks are also asynchronous, we can see exactly the same behavior that we saw before. However, in this case, the executing thread will be a human being and the message will be represented as a task in the person's task list.

As we can see in this image, a task is created when the Main thread's execution reaches the doBackup() method. This task goes directly to the corresponding user in the task list. When the user has time or is able to do that task, he/she completes it. In this case, the "Do Backup" activity is a manual task that needs to be performed by a human being.

In both the situations, we have the same asynchronous behavior, but the parties that interact change and this causes the need for different solutions.

For system-to-system interaction, probably, we need to focus on the protocols that the systems use for communication.

In human tasks, on the other hand, the main concern will probably be the user interface that handles the human interaction.

How do we know if a node is a wait state node or an automatic node?First of all, by the name. If the node represents an activity that is done by humans, it will always wait. In system interactions, it is a little more difficult to deduce this by the name (but, if we see an automatic activity that we know takes a lot of time, that will probably be an asynchronous activity which will behave as a wait state). A common example could be a backup to tape, where the backup action is scheduled in an external system. If we are not sure about the activity nature we need to ask about the activity nature to our stakeholder.

We need to understand these two behaviors in order to know how to implement each node's executional behavior, which will be related with the specific node functionality.

Creating the execution concept in Java

With this class, we will represent each execution of our process, which means that we could have a lot of instances at the same time running with the same definition. Inside the package called org.jbpm.examples.chapter02.simpleGOP.execution (provided at www.packtpub.com/files/code/5685_Code.zip), we will find the following class:

As we can see, this class contains a Definition and a Node, the idea here is to have a currentNode that represents the node inside the definition to which this execution is currently "pointing". We can say that the currentNode is a pointer to the current node inside a specific definition.

The real magic occurs inside each node. Now each node has the responsibility of deciding whether it must continue the execution to the next node or not. In order to achieve this, we need to add some methods (enter(), execute(), leave()) that will define the internal executional behavior for each node. We do this in the Node class to be sure that all the subclasses of the Node class will inherit the generic way of execution. Of course, we can change this behavior by overwriting the enter(), execute(), and leave() methods.

We can define the Node.java class (which is also found in the chapter02.simpleGOPExecution project in the code bundle) as follows:

As you can see in the Node class, which is the most basic and generic implementation, three methods are defined to specify the executional behavior of one of these nodes in our processes. If you carefully look at these three methods, you will notice that they are chained, meaning that the enter() method will be the first to be called. And at the end, it will call the execute() method, which will call the leave() method depending on the situation. The idea behind these chained methods is to demarcate different phases inside the execution of the node.

All of the subclasses of the Node class will inherit these methods, and with that the executional behavior. Also, all the subclasses could add other phases to demarcate a more complex lifecycle inside each node's execution.

The next image shows how these phases are executed inside each node.

As you can see in the image, the three methods are executed when the execution points to a specific node. Also, it is important to note that transitions also have the Take phase, which will be executed to jump from one node to the next.

All these phases inside the nodes and in the transition will let us hook custom blocks of code to be executed.

One example for what we could use these hooks for is auditing processes. We could add in the enter() method, that is the first method called in each node, a call to an audit system that takes the current timestamp and measures the time that the node uses until it finishes the execution when the leave() method is called.

Another important thing to notice in the Node class is the code inside the execute() method. A new concept appears. The Action interface that we see in that loop, represents a pluggable way to include custom specific logic inside a node without changing the node class. This allows us to extend the node functionality without modifying the business process graph. This means that we can add a huge amount of technical details without increasing the complexity of the graph. For example, imagine that in our business process each time we change node, we need to store the data collected from each node in a database. In most of the cases, this requirement is purely technical, and the business users don't need to know about that. With these actions, we achieve exactly the above. We only need to create a class with the custom logic that implements the Action interface and then adds it to the node in which we want to execute the custom logic.

The best way to understand how the execution works is by playing with the code. In the chapter02.simpleGOPExecution maven project, we have another test that shows us the behavior of the execution class. This test is called TestExecution and contains two basic tests to show how the execution works.

If you don't know how to use maven, there is a quick start guide at the end of this article. You will need to read it in order to compile and run these tests.

If you run this first test, it creates a process definition as in the definition tests, and then using the definition, it creates a new execution. This execution lets us interact with the process. As this is a simple implementation, we only have the start() method that starts the execution of our process, executing the logic inside each node. In this case, each node is responsible for continuing the execution to the next node. This means that there are no wait state nodes inside the example process. In case we have a wait state, our process will stop the execution in the first wait state. So, we need to interact with the process again in order to continue the execution.

Feel free to debug this test to see how this works. Analyze the code and follow the execution step by step. Try to add new actions to the nodes and analyze how all of the classes in the project behave.

When you get the idea, the framework internals will be easy to digest.

Homework

We are ready to create our first simple GOP language, the idea here is to get hands-on code and try to implement your own solution. Following and using the guidelines proposed in this article with minimal functionality, but with the full paradigm implemented, will represent and execute our first process. We could try to implement our example about "Recycling Thing Co.", but we will start with something easier. So, you can debug it and play with it until you get the main points of functionality. In the following sections, I will give you all the information that you need in order to implement the new words of our language and the behavior that the process will have. This is quite a lot of homework, but trust me, this is really worth it. The idea of finishing this homework is to feel comfortable with the code and the behavior of our defined processes. You will also see how the methods are chained together in order to move the process from one node to the next.

Creating a simple language

Our language will be composed with subclasses from our previous node class. Each of these subclasses will be a word in our new language. Take a look at the ActivityNode proposed in the chapter02.simpleGOPExecution project, inside the org.jbpm.examples.chapter02.simpleGOP.definition.more.expressive.power package. And when we try to represent processes with this language, we will have some kind of sentence or paragraph expressed in our business process language. As in all languages, these sentences and each word will have restrictions and correct ways of use. We will see these restrictions in the Nodes description section of the article.

So, here we must implement four basic words to our simple language. These words will be start, action, humandecision, and end to model processes like this one:

Actually, we can have any combination that we want of different types of nodes mixed in our processes. Always follow the rules/restrictions of the language that we implement. These restrictions are always related to the words' meanings. For example, if we have the word "start" (that will be a subclass of node) represented with the node: StartNode, this node implementation could not have arriving transitions. This is because the node will start the process and none of the rest of the nodes could be connected to the start node. Also, we could see a similar restriction with the end node, represented in the implementation with the EndNode class, because it is the last node in our processes and it could not have any leaving transitions. With each kind of node, we are going to see that they have different functionality and a set of restrictions that we need to respect when we are using these words in sentences defining our business processes. These restrictions could be implemented as 'not supported operation' and expressed with: throw newUnsupportedOperationException("Some message here");. Take a look at the EndNode class, you will see that the addTransition() method was being overridden to achieve that.

Nodes description

In this section, we will see the functionality of each node. You can take this functionality and follow it in order to implement each node. Also, you could think of some other restrictions that you could apply to each node. Analyze the behavior of each method and decide, for each specific node type, whether the method behavior needs to be maintained as it is in the super class or whether it needs to be overwritten.

StartNode: This will be our first node in all our processes. For this functionality, this node will behave as a wait state, waiting for an external event/signal/trigger that starts the process execution. When we create a new instance of process execution, this node is selected from the process description and set as the current node in the current execution. The start() method in the execution class will represent the event that moves the process from the first node to the second, starting the flow of the process.

EndNode: It will be our last node in all our processes. This node will end the life of our process execution instance. As you can imagine, this node will restrict the possibility of adding leaving transitions to this node.

ActionNode: This node will contain a reference to some technical code that executes custom actions that we need for fulfilling the process goal. This is a very generic node where we can add any kind of procedure. This node will behave as an automatic activity, execute all of these actions, and then leave the node.

HumanDecisionNode: This is a very simple node that gives a human being some information in order to decide which path of the process the execution will continue through. This node, which needs human interaction, will behave as a wait state node waiting for a human to decide which transition the node must take (this means that the node behaves as an OR decision, because with this node, we cannot take two or more paths at the same time).

One last thing before you start with the real implementation of these nodes. We will need to understand what the expected results are in the execution stage of our processes. The following images will show you how the resultant process must behave in the execution stage. The whole execution of the process (from the start node to the end node) will be presented in these three stages.

Stage one

The following image represents the first stage of execution of the process. In this image, you will see the common concepts that appear in the execution stage. Every time we create a new process execution, we will start with the first node, waiting for an external signal that will move the process to the next node.

An execution is created using our process definition instance in order to know which activities the process will have.

The start node is selected from the process definition and placed inside the current node reference in the execution instance. This is represented by the black arrow pointing to the Start node.

As the StartNode behaves as a wait state, it will wait and externally trigger to start the process execution. We need to know this, because may, we can think that if we create an instance of execution, it will automatically begin the execution.

Stage two

The second stage of the execution of the process is represented by the following image:

We can start the execution by calling the start() method inside the execution class. This will generate an event that will tell the process to start flowing through the nodes.

The process starts taking the first transition that the start node has, to the first action node. This node will only have an action hooked, so it will execute this action and then take the transition to the next node. This is represented with the dashed line pointing to the Action node. This node will continue the execution and will not behave as a wait state—updating the current node pointer, which has the execution, to this node.

The Human Decision node is reached, this means that some user must decide which path the process will continue through. Also, this means that the process must wait for a human being to be ready to decide. Obviously, the node will behave as a wait state, updating the current node pointer to this node.

But wait a second, another thing happens here, the process will return the execution control to the main method. What exactly does this mean?

Until here, the execution goes from one wait state (the start node) to another wait state (the human decision node) inside the method called start, enclosing all the automatic nodes' functionality and leaving the process in a wait state. Let's analyze the method call stack trace:

When the process reaches the HumanDecisionNode.execute(), it doesn't need to do anything more. It returns to the main() method and continues with the next line after the Execution.start()call.

Stage three

The following image represents the third stage of execution of the process:

Now we are waiting for a human to make a decision, but wait a second, if the thread that calls the start() method on the instance of the execution dies, we lose all the information about the current execution, and we cannot get it back later. This means that we cannot restore this execution to continue from the human decision node. On the other hand, we can just sleep the thread waiting for the human to be ready to make the decision with something like Thread.currentThread().sleep(X). Where X is expressed in milliseconds. But we really don't know how much time we must wait until the decision is taken. So, sleeping the thread is not a good option. We will need some kind of mechanism that lets us persist the execution information and allows us to restore this status when the user makes the decision. For this simple example, we just suppose that the decision occurs just after the start() method returns. So, we get the execution object, the current node (this will be the human decision node), and execute the decide() method with the name of the transition that we want to take as argument.

Let's run ((HumanDecisionNode)execution.getCurrentNode()). decide("transition to action three", execution). This is an ugly way to make the decision, because we are accessing the current node from the execution. We could create a method in the execution class that wraps this ugly call. However, for this example, it is okay. You only need to understand that the call to the decide() method is how the user interacts with the process.

When we make this decision, the next action node is an automatic node like the action one, and the process will flow until the EndNode, which is ending the execution instance, because this is the last wait state, but any action could be made to continue. As you can see, the wait states will need some kind of persistent solution in order to actually be able to wait for human or asynchronous system interactions. That is why you need to continue your testing of the execution with ((HumanDecisionNode)execution.getCurrentNode()).decide("transition to action three",execution), simulating the human interaction before the current thread dies.

Type mvn clean install into the console, this will compile the code, run the tests, and package the project

If you are using Netbeans, you can just open your project (having the maven plugin activated)

If you are using Eclipse, you need to run the project in the mvn eclipse:eclipse project directory, in order to generate the files needed for the project. Then you can just import the project into your workspace

Summary

In this article, we learnt the main points that you will need in order to understand how the framework works internally.

We have analyzed why we need the Graph Oriented Programming approach to represent and execute our business processes.

Alerts & Offers

Series & Level

We understand your time is important. Uniquely amongst the major publishers, we seek to develop and publish the broadest range of learning and information products on each technology. Every Packt product delivers a specific learning pathway, broadly defined by the Series type. This structured approach enables you to select the pathway which best suits your knowledge level, learning style and task objectives.

Learning

As a new user, these step-by-step tutorial guides will give you all the practical skills necessary to become competent and efficient.

Beginner's Guide

Friendly, informal tutorials that provide a practical introduction using examples, activities, and challenges.

Essentials

Fast paced, concentrated introductions showing the quickest way to put the tool to work in the real world.

Cookbook

A collection of practical self-contained recipes that all users of the technology will find useful for building more powerful and reliable systems.

Blueprints

Guides you through the most common types of project you'll encounter, giving you end-to-end guidance on how to build your specific solution quickly and reliably.

Mastering

Take your skills to the next level with advanced tutorials that will give you confidence to master the tool's most powerful features.

Starting

Accessible to readers adopting the topic, these titles get you into the tool or technology so that you can become an effective user.

Progressing

Building on core skills you already have, these titles share solutions and expertise so you become a highly productive power user.