Chapter 15. Workflow

Workflow Tutorials

Introduction

This tutorial describes the components of the simple workflow and how to create workflows and assign them to users and groups. Workflows are based on a dependency graph model, where a task's state changes are dependent on other tasks. A task is a single unit of work and has three states: enabled, disabled, and finished. The states of a task are dependent upon its dependencies (for example, other tasks). A task is enabled if all its dependency tasks are finished. For example, see the diagram below. Assume that Task d is dependent on tasks b and c. Tasks b and c must be finished before task d is enabled.
      a
    /  |
   |   |
   c   b
    \/
     |
     d

Workflow Tasks

Currently, there two types of tasks: Task and UserTask. The base type task supports commenting on task and does nothing in the events. The second type, user, supports user and group assignments and notifications sent to assignees when a state changes in a task. The user type is a subclass of the base type task, so it also supports task commenting. To add additional functionality to these tasks, you must subclass one of these two task types. Additional functionality involves overriding the events methods: enableEvt, disableEvt, finishEvt, and rollbackEvt. The task state transitions are as follows:
 
enable  -> disable  (disableEvt)
disable -> enable  (enableEvt)
enable  -> finish  (finishEvt)
finish  -> rollback (rollbackEvt)

Simple Workflow

The following steps indicate how to create a hello world workflow and describe the task state change.

  1. Design a workflow by defining the desired tasks and the dependencies between them.

  2. Create a new named task class for the different task types. Be sure to define the BASE_DATA_OBJECT_TYPE, the getBaseDataObjectType, constructors, and clone methods. Also include the methods to invoke.

  3. Write the PDL file to support the new task type.

  4. Create an instantiator for DomainObjectFactory.

  5. Create a workflow template based on the design.

  6. Instantiate a workflow from a workflow template.

  7. Call the workflow start method to start the Engine.

  1. Design your workflow.

  2. Create a new task type by subclassing task:

    public class HelloWorldTask extends Task {
    
        //The base data object is used for the domain object
        public static final String BASE_DATA_OBJECT_TYPE = 
            "com.arsdigita.workflow.simple.HelloWorldTask";
        
        protected String getBaseDataObjectType() {
            return BASE_DATA_OBJECT_TYPE;
        }
    
        //constructors are not inherited from subclasses
        //so you'll need to add them
        public HelloWorldTask(String label, String description) {
            this(BASE_DATA_OBJECT_TYPE);
            initAttributes(label,description);
        }
    
        public HelloWorldTask(DataObject taskDataObject) {
            super(taskDataObject);
        }
    
        public HelloWorldTask() {
          this(BASE_DATA_OBJECT_TYPE);
          setState(DISABLED);
        }
    
        public HelloWorldTask(OID oid) throws 
            DataObjectNotFoundException {
            super(oid);
        }
    
        public HelloWorldTask(BigDecimal id) throws 
            DataObjectNotFoundException {
            this(new OID(BASE_DATA_OBJECT_TYPE, id));
        }
      
        protected HelloWorldTask(ObjectType type) {
            super(type);
        }
    
        protected HelloWorldTask(String typeName) {
            super(typeName);
        }
    
    
        // Add functionality in these methods 
        public void enableEvt() {
            System.out.println(getLabel()+": I am enabled");
        }
    
        public void disableEvt() {
            System.out.println(getLabel()+": I have been disabled");
        }         
    
        public void finishEvt() {
            System.out.println(getLabel()+": I am finished");
        }
    
        public void rollbackEvt() {
            System.out.println(getLabel()+":  I was rollbacked");
        }
    
        //Write clone functionality. Note the use of copyAttributes 
        //
        public Object clone() {
            HelloWorldTask taskClone = new HelloWorldTask();
            copyAttributes(taskClone);
            return taskClone;
        }
    
        //Add this method because future versions will
        protected void copyAttributes(HelloWorldTask taskClone) {
            taskClone.setLabel(getLabel());
            taskClone.setDescription(getDescription());
            taskClone.setActive(isActive());
        }
    }

    Ensure that the constructors, events, and clone methods are coded for the new task type.

  3. Write the PDL for the new subclass of task. This is a simple case; you would probably have additional tables for select and DML statements.
    object type HelloWorldTask extends Task {
    
        retrieve {
            super;
        } 
    
        insert  {
            super;
        }
    
        update {
            super;
        }
    
        delete {
           super;
        }
    
    }

  4. In your package intializer section, create and add a new instantiator.
    DomainObjectInstantiator instHelloWorldTask = 
         new ACSObjectInstantiator() {
         public DomainObject doNewInstance(DataObject 
         dataObject) {
             return new HelloWorldTask(dataObject);
         }
    };
    
    DomainObjectFactory.registerInstantiator(
         "com.arsdigita.workflow.simple.",
         instHelloWorldTask);

  5. Create the workflow template.
    public class WFTutorialHello {
        
        public void WFTutorialHello() {
            HelloWorldTask helloTask1 = 
             new HelloWorldTask("hello 1", "description");
            HelloWorldTask helloTask2 = 
             new HelloWorldTask("hello 2", "description");
    
            //Note(1): Save the task before adding the dependency
            helloTask1.save();
            helloTask2.save();
            helloTask2.addDependency(helloTask1);
            helloTask2.save();             
    
            User user = new User(__USER_ID__)
            Workflow wf = new Workflow("hello example", 
             "tutorial example of creating a task");
    
            wf.addTask(helloTask1);
            wf.addTask(helloTask2);
    
            //Note(2): The workflow mad
            helloTask1.save();
            helloTask2.save();
            wf.start();
    
            helloTask1.finish();
            helloTask2.finish();
        }
    }

There are a couple of subtle issues to note with regard to the above example. (These issues are commented with "NOTE(?)" in the code.) First, it is important to save the task before adding dependencies. Otherwise, the internal persistance will fail. Additionally, ensure that a save is called on each task after adding to workflow, since the workflow may call a couple of methods on the added task.

Adding a Task to a Workflow

To add a task, you call the addTask method in workflow. The task is inactive when created, and can be set to be active by calling the setActive(true). A task is not considered part of the workflow until it becomes active. Tasks are created in an inactive state to allow you to preview them before altering the workflow state. Once a task is active, it can affect the workflow and other dependent tasks. Manually setting the task state is required only when adding a new task to an in-progress workflow. When the workflow is started, it will automatically set all task as active.

Task Rollback

Rolling back a process to a certain task is done by enabling a task early on in the workflow. For example, to move the process to task A:
//Get reference to task A
taskA.enable();
This will set the workflow back to the state that preceded finishing task A.

Completing a Task

When a task is enabled, call the finish method to complete it:
//Get reference to task A and call finish method
taskA.finish();