As far as the Drools Flow engine is concerned, human tasks are similar to any other external service that needs to be invoked and are implemented as an extension of normal work items. As a result, the process itself only contains an abstract description of the human tasks that need to be executed, and a work item handler is responsible for binding this abstract tasks to a specific implementation. Using our pluggable work item handler approach (see the chapter on domain-specific processes for more details), users can plug in any back-end implementation.
We do however provide an implementation of such a human task management component based on the WS-HumanTask specification. If you do not have the requirement to integrate a specific human task component yourself, you can use this service. It manages the task life cycle of the tasks (creation, claiming, completion, etc.) and stores the state of the task persistently. It also supports features like internationalization, calendar integration, different types of assignments, delegation, deadlines, etc.
Because we did not want to implement a custom solution when a standard is available, we chose to implement our service based on the WS-HumanTask (WS-HT) specification. This specification defines in detail the model of the tasks, the life cycle, and a lot of other features as the ones mentioned above. It is pretty comprehensive and can be found here.
Looking from the perspective of the process, whenever a human task node is triggered during the execution of a process instance, a human task is created. The process will only continue from that point when that human task has been completed or aborted (unless of course you specify that the process does not need to wait for the human task to complete, by setting the "Wait for completion" property to true). However, the human task usually has a separate life cycle itself. We will now shortly introduce this life cycle, as shown in the figure below. For more details, check out the WS-HumanTask specification.
![]() |
Whenever a task is created, it starts in the "Created" stage. It usually automatically transfers to the "Ready" state, at which point the task will show up on the task list of all the actors that are allowed to execute the task. There, it is waiting for one of these actors to claim the task, indicating that he or she will be executing the task. Once a user has claimed a task, the status is changed to "Reserved". Note that a task that only has one potential actor will automatically be assigned to that actor upon creation of that task. After claiming the task, that user can then at some point decide to start executing the task, in which case the task status is changed to "InProgress". Finally, once the task has been performed, the user must complete the task (and can specify the result data related to the task), in which case the status is changed to "Completed". If the task could not be completed, the user can also indicate this using a fault response (possibly with fault data associated), in which case the status is changed to "Failed".
The life cycle explained above is the normal life cycle. The service also allows a lot of other life cycle methods, like:
The task management component needs to be integrated with the Drools Flow engine just like any other external service, by registering a work item handler that is responsible for translating the abstract work item (in this case a human task) to a specific invocation. We have implemented such a work item handler (org.drools.process.workitem.wsht.WSHumanTaskHandler in the drools-process-task module) so you can easily link this work item handler like this:
StatefulKnowledgeSession session = ...; session.getWorkItemManager().registerWorkItemHandler("Human Task", new WSHumanTaskHandler());
By default, this handler will connect to the human task management component on the local machine on port 9123, but you can easily change that by invoking the setConnection(ipAddress, port) method on the WSHumanTaskHandler.
At this moment WSHumanTaskHandler is using Mina (http://mina.apache.org/) for testing the behavior in a client/server architecture. Mina uses messages between client and server to enable the client comunicate with the server. That's why WSHumanTaskHandler have a MinaTaskClient that create different messages to give the user different actions that are executed for the server.
In the client (MinaTaskClient in this implementation) we should see the implementation of the following methods for interacting with Human Tasks:
public void start( long taskId, String userId, TaskOperationResponseHandler responseHandler ) public void stop( long taskId, String userId, TaskOperationResponseHandler responseHandler ) public void release( long taskId, String userId, TaskOperationResponseHandler responseHandler ) public void suspend( long taskId, String userId, TaskOperationResponseHandler responseHandler ) public void resume( long taskId, String userId, TaskOperationResponseHandler responseHandler ) public void skip( long taskId, String userId, TaskOperationResponseHandler responseHandler ) public void delegate( long taskId, String userId, String targetUserId, TaskOperationResponseHandler responseHandler ) public void complete( long taskId, String userId, ContentData outputData, TaskOperationResponseHandler responseHandler ) ...
Using this methods we will implement any kind of GUI that the end user will use to do the task that they have assigned. If you take a look a this method signatures you will notice that almost all of this method takes the following arguments:
taskId: the id of the task with we are working. Probably you will pick this Id from the user task list in the UI (User Interface).
userId: the id of the user that is executing the action. Probably the Id of the user that is signed in the application.
responseHandler: this is the handler have responsability to catch the response and get the results or just let us know that the task is already finished.
As you can imagine all the methods create a message that will be sended to the server, and the server will execute the logic that implement the correct action. A creation of one of this messages will be like this:
public void complete(long taskId, String userId, ContentData outputData, TaskOperationResponseHandler responseHandler) { List<Object> args = new ArrayList<Object>( 5 ); args.add( Operation.Complete ); args.add( taskId ); args.add( userId ); args.add( null ); args.add( outputData ); Command cmd = new Command( counter.getAndIncrement(), CommandName.OperationRequest, args ); handler.addResponseHandler( cmd.getId(), responseHandler ); session.write( cmd ); }
Here we can see that a Command is created and the arguments of the method are inserted inside the command with the type of operation that we are trying to execute and then this command is sended to the server with session.write( cmd ) method.
If we see the server implementation, when the command is recived, we find that depends of the operation type (here Operation.Complete) will be the logic that will be executed. If we look at the class TaskServerHandler in the messageReceived method the taskOperation is executed using the taskServiceSession that is the responsible for get, persist and manipulate all the Human Task Information when the tasks are created and the user is not interacting with them.
The task management component is a completely independent service that the process engine communicates with. We therefore recommend to start it as a separate service as well. To start the task server, you can use the following code fragment:
EntityManagerFactory emf = Persistence.createEntityManagerFactory("org.drools.task"); taskService = new TaskService(emf); MinaTaskServer server = new MinaTaskServer( taskService ); Thread thread = new Thread( server ); thread.start();
The task management component uses the Java Persistence API (JPA) to store all task information in a persistent manner. To configure the persistence, you need to modify the persistence.xml configuration file accordingly. We refer to the JPA documentation on how to do that. The following fragment shows for example how to use the task management component with hibernate and an in-memory H2 database:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <persistence version="1.0" xsi:schemaLocation= "http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd" xmlns:orm="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/persistence"> <persistence-unit name="org.drools.task"> <provider>org.hibernate.ejb.HibernatePersistence</provider> <class>org.drools.task.Attachment</class> <class>org.drools.task.Content</class> <class>org.drools.task.BooleanExpression</class> <class>org.drools.task.Comment</class> <class>org.drools.task.Deadline</class> <class>org.drools.task.Comment</class> <class>org.drools.task.Deadline</class> <class>org.drools.task.Delegation</class> <class>org.drools.task.Escalation</class> <class>org.drools.task.Group</class> <class>org.drools.task.I18NText</class> <class>org.drools.task.Notification</class> <class>org.drools.task.EmailNotification</class> <class>org.drools.task.EmailNotificationHeader</class> <class>org.drools.task.PeopleAssignments</class> <class>org.drools.task.Reassignment</class> <class>org.drools.task.Status</class> <class>org.drools.task.Task</class> <class>org.drools.task.TaskData</class> <class>org.drools.task.SubTasksStrategy</class> <class>org.drools.task.OnParentAbortAllSubTasksEndStrategy</class> <class>org.drools.task.OnAllSubTasksEndParentEndStrategy</class> <class>org.drools.task.User</class> <properties> <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/> <property name="hibernate.connection.driver_class" value="org.h2.Driver"/> <property name="hibernate.connection.url" value="jdbc:h2:mem:mydb" /> <property name="hibernate.connection.username" value="sa"/> <property name="hibernate.connection.password" value="sasa"/> <property name="hibernate.connection.autocommit" value="false" /> <property name="hibernate.max_fetch_depth" value="3"/> <property name="hibernate.hbm2ddl.auto" value="create" /> <property name="hibernate.show_sql" value="true" /> </properties> </persistence-unit> </persistence>
The first time you start the task management component, you need to make sure that all the necessary users and groups are added to the database. Our implementation requires all users and groups to be predefined before trying to assign a task to that user or group. So you need to make sure you add the necessary users and group to the database using the taskSession.addUser(user) and taskSession.addGroup(group) methods. Note that you at least need an "Administrator" user as all tasks are automatically assigned to this user as the administrator role.
The drools-process-task module contains a org.drools.task.RunTaskService class in the src/test/java source folder that can be used to start a task server. It automatically adds users and groups as defined in LoadUsers.mvel and LoadGroups.mvel configuration files.
The task management component exposes various methods to manage the life cycle of the tasks through a Java API. This allows clients to integrate (at a low level) with the task management component. Note that end users should probably not interact with this low-level API directly but rather use one of the task list clients. These clients interact with the task management component using this API.
This interaction will be described with the following image:
As we can see in the image we have MinaTaskClient and MinaTaskServer. They communicate to each other sending messages to query and manipulate human tasks. Step by step the interactio n will be something like this:
Some client need to complete some task. So he/she needs to create an instace of MinaTaskClient and connect it to the MinaTaskServer to have a session to talk to each other. This is the step one in the image.
Then the client can call the method complete() in MinaTaskClient with the corresponding arguments. This will generate a new Message (or Command) that will be inserted in the session that the client open when it connects to the server. This message must specify a type that the server recognize and know what to do when the message is recieved. This is the step two in the image.
At this moment TaskServerHandler noticed that there is a new message in the session so an analysis about what kind of message is will take place. In this case is the type of Operation.Complete, because the client is finishing succesfully some task. So we need to complete the task that the user want to finish. This is achieved using the TaskServiceSession that will fire an specific type of event that will be procesed by an specific subclass of TaskEventListener. This are step three and four in the image.
When the event is recived by TaskEventListener it will know how to modify the status of the task. This is achieved using the EntityManager to retrieve and modify the status of an specific task from the database. In this case, because we are finishing a task, the status will be updated to Completed. This is step five in the image.
Now, when the changes are made we need to notify the client about that the task was succesfully ended and this is achieved creating a response message that TaskClientHandler will receive and inform MinaTaskClient. This are steps six, seven and eight in the image.