Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migration of unmigrated content due to installation of a new plugin

...

  • we want to be able to display tasks independent of the presence of an underlying workflow engine
    • i.e a one-shot task (e.g. a short translation assignment given to a particular user) for which setting up a whole workflow process would be overkill.
  • we want to be able to retrieve the status of a given task
  • ideally we would like the list to be updated automatically as new tasks arrive, are taken, updated or completed. 

jBPM User

...

Tasks

What is a User Task?

As the plain messages we currently use don't work well with tasks, we figured out two options

...

  • PRO
    • would be the "proper" way to implement manual tasks in jBPM (we currently fake them with a Service Task, that is a task usually performed by an automated system, which sends a message to Pulse) 
    • we'd take advantage of all the properties already available there, such as groupId, actorId, comment, etc., no need to reinvent the wheel
  • CONTRA
    • no clear documentation on how to integrate it - one is basically left with the source code only. TODO verify that this claim is actually true
    • what about tasks independent of a workflow engine presence?
    • how to make the workflow engine aware of Magnolia groups and users?

...

  • would handle any type of task, regardless of a workflow engine presence
  • easier to persist and query, most of the job already being done by the MessageStore API

...

A basic sequence diagram showing how Workflow and Pulse can collaborate.

Image Removed

A sequence diagram showing a TaskManager between Workflow and Pulse

Image Removed

jBPM User Tasks

What is a User Task?

From the official jBPM documentation:

Processes can also involve tasks that need to be executed by human actors. A User Task represents an atomic task to be executed by a human actor. It should have one incoming connection and one outgoing connection. User Tasks can be used in combination with Swimlanes to assign multiple human tasks to similar actors. Refer to the chapter on human tasks for more details. A User Task is actually nothing more than a specific type of service node (of type "Human Task"). A User Task contains the following properties:

Image Removed

From the official jBPM documentation:

Processes can also involve tasks that need to be executed by human actors. A User Task represents an atomic task to be executed by a human actor. It should have one incoming connection and one outgoing connection. User Tasks can be used in combination with Swimlanes to assign multiple human tasks to similar actors. Refer to the chapter on human tasks for more details. A User Task is actually nothing more than a specific type of service node (of type "Human Task"). A User Task contains the following properties:

Image Added


Instead of using human tasks for interacting with human actors Magnolia decided to leave this concept out and use Service Tasks for sending messages to The Pulse.

But what exactly is the difference between a Service Tasks and Human Tasks?

From a technical point of view: None.

Both Tasks are handled by a WorkItemHandler which implement the same interface.

When modelling your process it does matter. A User Task gives you much more possibilities to set parameters tailored for user interactions:

Image Added

Further more User Tasks allow defining Swim lanes inside your process. A swim lane allows you to define multiple steps or user tasks inside your process to be automatically assigned to the first user who picks up the first task of the swim lane.

Note: Human Tasks allow setting a Task Name, which could be used to define the further visual representation or the actions available for a certain task in Magnolia. This is not the case for other Service Tasks, which is why you end up creating a handler per service task. What the Implementation field can be used for has to be clarified.

Human Task WorkItemHandler 

The handler is simply registered in the RegisterableItemsFactory using Human Task as identifier. Magnolia's 5.3 version of workflow already provides a custom class called RegisteredItemsFactory.

 

Code Block
languagejava
titleorg.jbpm.runtime.manager.impl.DefaultRegisterableItemsFactory
    public Map<String, WorkItemHandler> getWorkItemHandlers(RuntimeEngine runtime) {
	    WorkItemHandler handler = getHTWorkItemHandler(runtime);
        defaultHandlers.put("Human Task", handler);
		...
	}
 
    protected WorkItemHandler getHTWorkItemHandler

Instead of using human tasks for interacting with human actors Magnolia decided to leave this concept out and use Service Tasks for sending messages to The Pulse.

But what exactly is the difference between a Service Tasks and Human Tasks?

From a technical point of view: None.

Both Tasks are handled by a WorkItemHandler which implement the same interface.

When modelling your process it does matter. A User Task gives you much more possibilities to set parameters tailored for user interactions:

Image Removed

Further more User Tasks allow defining Swim lanes inside your process. A swim lane allows you to define multiple steps or user tasks inside your process to be automatically assigned to the first user who picks up the first task of the swim lane.

Note: Human Tasks allow setting a Task Name, which could be used to define the further visual representation or the actions available for a certain task in Magnolia. This is not the case for other Service Tasks, which is why you end up creating a handler per service task. What the Implementation field can be used for has to be clarified.

Human Task WorkItemHandler 

The handler is simply registered in the RegisterableItemsFactory using Human Task as identifier. Magnolia's 5.3 version of workflow already provides a custom class called RegisteredItemsFactory.

 

Code Block
languagejava
titleorg.jbpm.runtime.manager.impl.DefaultRegisterableItemsFactory
    public Map<String, WorkItemHandler> getWorkItemHandlers(RuntimeEngine runtime) {
	     WorkItemHandler handler = getHTWorkItemHandler(runtime);
         defaultHandlers.put("Human Task", handler);
		...
	}
 
    protected WorkItemHandler getHTWorkItemHandler(RuntimeEngine runtime) {
        
        ExternalTaskEventListener listener ExternalTaskEventListener listener = new ExternalTaskEventListener();
        LocalHTWorkItemHandler humanTaskHandler = new LocalHTWorkItemHandler();

        humanTaskHandler.setRuntimeManager(((RuntimeEngineImpl)runtime).getManager());
        if (runtime.getTaskService() instanceof EventService) {
            ((EventService)runtime.getTaskService()).registerTaskEventListener(listener);
        }
		...
   }
       

...

org.jbpm.services.task.wih.ExternalTaskEventListener#processTaskState

TaskService

Anchor
taskservice
taskservice

The TaskService is registered by the RuntimeManager to the RuntimeEngine. Magnolia provides it's own RuntimeManager implementation for easy registration of a custom TaskService Implementation. It's purpose is to handle the lifecycle of User Tasks, which is pretty obvious when looking at its interface:

Code Block
languagejava
titleTaskService
/**
 * The Task Service Entry Point serves as 
 *  facade of all the other services, providing a single entry point
 *  to access to all the services
 */
public interface TaskService {
    
    void activate(long taskId, String userId);
    void claim(long taskId, String userId);
    void claimNextAvailable(String userId, String language);
    void complete(long taskId, String userId, Map<String, Object> data);
    void delegate(long taskId, String userId, String targetUserId);
    void exit(long taskId, String userId);
    void fail(long taskId, String userId, Map<String, Object> faultData);
    void forward(long taskId, String userId, String targetEntityId);
    Task getTaskByWorkItemId(long workItemId);
    Task getTaskById(long taskId);
    List<TaskSummary> getTasksAssignedAsBusinessAdministrator(String userId, String language);
    List<TaskSummary> getTasksAssignedAsPotentialOwner(String userId, String language);
    List<TaskSummary> getTasksAssignedAsPotentialOwnerByStatus(String userId, List<Status> status, String language);
    List<TaskSummary> getTasksOwned(String userId, String language);
    List<TaskSummary> getTasksOwnedByStatus(String userId, List<Status> status, String language);
    List<TaskSummary> getTasksByStatusByProcessInstanceId(long processInstanceId, List<Status> status, String language);
    List<Long> getTasksByProcessInstanceId(long processInstanceId);
    
    List<TaskSummary> getTasksByVariousFields( List<Long> workItemIds, List<Long> taskIds, List<Long> procInstIds, 
            List<String> busAdmins, List<String> potOwners, List<String> taskOwners, 
            List<Status> status, boolean union);
    
    List<TaskSummary> getTasksByVariousFields(Map <String, List<?>> parameters, boolean union);
    
    long addTask(Task task, Map<String, Object> params);
    void release(long taskId, String userId);
    void resume(long taskId, String userId);
    void skip(long taskId, String userId);
    void start(long taskId, String userId);
    void stop(long taskId, String userId);
    void suspend(long taskId, String userId);
    void nominate(long taskId, String userId, List<OrganizationalEntity> potentialOwners);
    Content getContentById(long contentId);
    Attachment getAttachmentById(long attachId);
    
}

 

UML showing the default jBPM implementation

This diagram shows how User Tasks are persisted in JPA. Please compare this with the current implementation to see where Magnolia's implementation can easily be extended for a custom TaskService implementation.

Image Removed

long taskId, String userId, List<OrganizationalEntity> potentialOwners);
    Content getContentById(long contentId);
    Attachment getAttachmentById(long attachId);
    
}

 

UML showing the default jBPM implementation

This diagram shows how User Tasks are persisted in JPA. Please compare this with the current implementation to see where Magnolia's implementation can easily be extended for a custom TaskService implementation.

Image Added

Magnolias Approach

Introduce own implementation of TaskService which delegates to Magnolia's TaskManager.

By choosing this approach we would sacrifice the idea of having a complete integration of jBPM (in case that idea ever was there..) in Magnolia. But, as part of workflow 5.3 we have laid the ground to hook into jBPM's engine at the right spots and provide custom, yet slimmed down implementations targeted towards our needs and most probably towards 97,6% of our customers needs. Still if somebody would need to have the full persistence of User Tasks as specified by jBPM registering the default CommandBasedTaskService based on a custom TaskPersistenceContext is possible, in theory.

 Image Added

Update

During implementation we realised that this will be more complicated than necessary. While registering the TaskService to the RuntimeEngine would be nice, the gain is not much more than being a bit more consistent with jBPM. And when looking at the TaskService interface we would somehow have to wrap a lot of objects to work with our own Task's implementation, which should be part of the UI.

So instead of implementing this the TaskService interface and registering it to the RuntimeEngine we are going to create a custom TaskManager interface (naming due to consistency with MessageManager/MessageStore).

Image Added

Compared to the first diagram, the TaskManager is not registered to the Runtime, but it is injected into the HTWorkItemHandler. The LifeCycleManager which takes care of notifying the process about completed tasks is replaced by TasksEventManager, which is part of the UI project and allows registering EventHandlers. This could e.g. be used to register an handler which sends mail every time a task is created, or in our case we register an handler, which takes care of notifying the process about completed user tasks.

This approach allows a very slim integration with Magnolia's TaskManager. The whole Task Management is a standalone, reusable part of the UI project and for jBPM we communicate with the standard TaskManager interfaces using the HTWorkItemHandler and event handlers registered to the TaskEventmanager

Input from architecture meeting

3.4.2014: Task related classes should go to a separate module in main

Possible Risks and Problems

Accessing the TaskManager

Accessing the TaskManager is different than in jBPM. In jBPM the TaskService is bound to a RuntimeEngine and can be obtained like this:

long taskId = ((InternalTaskService) runtime.getTaskService()).addTask(task, content);

This will not be doable with our approach. 

Extending the Task object

How to extend the Task object by workflow specific parameters, how to store it?

 

Other Possible approaches

JcrTaskPersistenceContext

By implementing a JCR implementation of the TaskPersistenceContext we would be able to create a complete storage for User Tasks. This would be the recommended approach in case we wanted to provide a complete integration of jBPM in Magnolia where customers would be able to take full advantage of jBPM and possibly also integrate 3rd party clients to connect to Magnolia's engine.

Having the persistence in place still leaves questions open on how to actually interact with the tasks. We would have to access the stored tasks, and provide a limited set of possibilities to manipulate them. 

The JPATaskPersistenceContext alone is around 550 LOC and all it does is delegate the to the EntityManager using calls like em.merge(object) em.remove( object )

Code Block
package org.kie.internal.task.api;

public interface TaskPersistenceContext {

	Task findTask(Long taskId);
	Task persistTask(Task task);
	Task updateTask(Task task);
	Task removeTask(Task task);
	Group findGroup(String groupId);
	Group persistGroup(Group group);
    Group updateGroup(Group group);
	Group removeGroup(Group group);
	User findUser(String userId);
	User persistUser(User user);
	User updateUser(User user);
	User removeUser(User user);
	OrganizationalEntity findOrgEntity(String orgEntityId);
	OrganizationalEntity persistOrgEntity(OrganizationalEntity orgEntity);
	OrganizationalEntity updateOrgEntity(OrganizationalEntity orgEntity);
	OrganizationalEntity removeOrgEntity(OrganizationalEntity orgEntity);
	Content findContent(Long contentId);
	Content persistContent(Content content);
	Content updateContent(Content content);
	Content removeContent(Content content);
	Attachment findAttachment(Long attachmentId);
	Attachment persistAttachment(Attachment attachment);
	Attachment updateAttachment(Attachment attachment);
	Attachment removeAttachment(Attachment attachment);
	Comment findComment(Long commentId);
	Comment persistComment(Comment comment);
	Comment updateComment(Comment comment);
	Comment removeComment(Comment comment);
	Deadline findDeadline(Long deadlineId);
	Deadline persistDeadline(Deadline deadline);
	Deadline updateDeadline(Deadline deadline);
	Deadline removeDeadline(Deadline deadline);
	
	/*
	 * Query related methods
	 */
	
	...
    
    /*
     * Following are optional methods that are more like extension to 
     * default data model to allow flexible add-ons
     */
	<T> T persist(T object);
	<T> T find(Class<T> entityClass, Object primaryKey);
	<T> T remove(T entity);
    <T> T merge(T entity);
    
    /*
     * life cycle methods 
     */
    boolean isOpen();
    void joinTransaction();
    void close();
}

Custom approach

Continue down the road we chose and ignore User Tasks:

<Insert magic here>

 Image Added