Workflow engine design and implementation·Simple execution of process

Foreword

The model has been highly abstracted above, and this article will further explain the model behavior. Here begins with a question:

How to let the process go from the start node to the end node according to the arrow point?

StartModel(start)->TaskModel(apply)->TaskModel(deptApprove)->EndModel(end)

Execution process analysis

Object graph:

Timing diagram:

Description of execution process:

  1. The start node calls the execution method of the output edge t1
  2. The output side of t1 calls the execution method of the leave application node
  3. The leave node calls the execution method of the output edge t2
  4. The t2 output side invokes the execution method of the department leader’s approval
  5. The department leader approves and calls the execution method of the output edge t3
  6. t3 calls the execution method of the end node

Code Implementation Steps

The execute method of the node model is added to print the code and name of the current object.

public abstract class NodeModel extends BaseModel implements Action {<!-- -->
    private String layout;// layout attributes (x,y,w,h)
    // input edge set
    private List<TransitionModel> inputs = new ArrayList<TransitionModel>();
    // output edge set
    private List<TransitionModel> outputs = new ArrayList<TransitionModel>();
    private String preInterceptors; // Node pre-interceptors
    private String postInterceptors; // node post interceptor
    
    /**
    * Customized execution method by subclass
    * @param execution
    */
    abstract void exec(Execution execution);
    @Override
    public void execute(Execution execution) {<!-- -->
        // 1. Call the pre-interceptor
        // 2. Call the exec method of the subclass
        // 3. Call post interceptor
        System.out.println(StrUtil.format("model:{},name:{},displayName:{}", this.getClass().getSimpleName(), getName(),getDisplayName()));
        exec(execution);
    }
}

The process model adds the method of getting the start node

public class ProcessModel extends BaseModel {<!-- -->
    private String type; // Process definition classification
    private String instanceUrl; // The form key to be filled in to start the instance
    private String expireTime; // expected completion time variable key
    private String instanceNoClass; // instance number generator implementation class
    // All nodes of the process definition
    private List<NodeModel> nodes = new ArrayList<NodeModel>();
    // All task nodes defined by the process
    private List<TaskModel> tasks = new ArrayList<TaskModel>();

    /**
     * get start node
     * @return
     */
    public StartModel getStart() {<!-- -->
        StartModel startModel = null;
        for (int i = 0; i < nodes. size(); i ++ ) {<!-- -->
            NodeModel nodeModel = nodes. get(i);
            if(nodeModel instanceof StartModel) {<!-- -->
                startModel = (StartModel) nodeModel;
                break;
            }
        }
        return startModel;
    }
}

The process model gets the start node object and calls the execution method

processModel.getStart().execute(new Execution());

At this time, only the start node information is printed

model:StartModel,name:start,displayName:start

The execute method of the node model object adds traversal to call the execution method of the next node

public abstract class NodeModel extends BaseModel implements Action {<!-- -->
    private String layout;// layout attributes (x,y,w,h)
    // input edge set
    private List<TransitionModel> inputs = new ArrayList<TransitionModel>();
    // output edge set
    private List<TransitionModel> outputs = new ArrayList<TransitionModel>();
    private String preInterceptors; // Node pre-interceptors
    private String postInterceptors; // node post interceptor
    
    /**
    * Customized execution method by subclass
    * @param execution
    */
    abstract void exec(Execution execution);
    @Override
    public void execute(Execution execution) {<!-- -->
        // 1. Call the pre-interceptor
        // 2. Call the exec method of the subclass
        // 3. Call post interceptor
        System.out.println(StrUtil.format("model:{},name:{},displayName:{}", this.getClass().getSimpleName(), getName(),getDisplayName()));
        outputs.forEach(tr->{<!-- -->
            tr.getTarget().execute(execution);
        });
        exec(execution);
    }
}

result:

model:StartModel,name:start,displayName:start
model:TaskModel,name:apply,displayName:leave application
model:TaskModel,name:deptApprove,displayName:dept leader approval
model:EndModel,name:end,displayName:end

In order to highlight the role of the edge, we can implement the execution method of the edge:

public class TransitionModel extends BaseModel implements Action {<!-- -->
    private NodeModel source; // edge source node reference
    private NodeModel target; // edge target node reference
    private String to; // target node name
    private String expr; // edge expression
    private String g; // set of edge point coordinates (x1, y1; x2, y2, x3, y3...) start, corner, end
    private boolean enabled; // Whether it can be executed
    @Override
    public void execute(Execution execution) {<!-- -->
        if(!enabled) return;
        target. execute(execution);
    }
}

Then transform the node model and add the runOutTransition method

public abstract class NodeModel extends BaseModel implements Action {<!-- -->
    private String layout;// layout attributes (x,y,w,h)
    // input edge set
    private List<TransitionModel> inputs = new ArrayList<TransitionModel>();
    // output edge set
    private List<TransitionModel> outputs = new ArrayList<TransitionModel>();
    private String preInterceptors; // Node pre-interceptors
    private String postInterceptors; // node post interceptor
    
    /**
    * Customized execution method by subclass
    * @param execution
    */
    abstract void exec(Execution execution);
    @Override
    public void execute(Execution execution) {<!-- -->
        // 1. Call the pre-interceptor
        // 2. Call the exec method of the subclass
        // 3. Call post interceptor
        System.out.println(StrUtil.format("model:{},name:{},displayName:{}", this.getClass().getSimpleName(), getName(),getDisplayName()));
        // Execute output edge
        runOutTransition(execution);
        exec(execution);
    }
    /**
     * Execute output edge
     */
    protected void runOutTransition(Execution execution) {<!-- -->
        outputs.forEach(tr->{<!-- -->
            tr.setEnabled(true);
            tr. execute(execution);
        });
    }

}

The final effect is:

model:StartModel,name:start,displayName:start
model:TaskModel,name:apply,displayName:leave application
model:TaskModel,name:deptApprove,displayName:dept leader approval
model:EndModel,name:end,displayName:end

How to block the process?

The execution process of the above example is too smooth. In the real workflow scene, there will be some blocking tasks. Blocking means that the execution method of the node is called. If the conditions are not met, the process cannot be driven to the next node. So how do we use programs to simulate this process?

First transform the node model

Not every node executes in the same way, we need to perform different output processing on different nodes, so here

  • Temporarily remove the print statement of the original node model and the method of calling the execution edge
  • Override the toString() method
public abstract class NodeModel extends BaseModel implements Action {<!-- -->
    private String layout;// layout attributes (x,y,w,h)
    // input edge set
    private List<TransitionModel> inputs = new ArrayList<TransitionModel>();
    // output edge set
    private List<TransitionModel> outputs = new ArrayList<TransitionModel>();
    private String preInterceptors; // Node pre-interceptors
    private String postInterceptors; // node post interceptor
    
    /**
    * Customized execution method by subclass
    * @param execution
    */
    abstract void exec(Execution execution);
    @Override
    public void execute(Execution execution) {<!-- -->
        // 1. Call the pre-interceptor
        // 2. Call the exec method of the subclass
        // 3. Call post interceptor
        exec(execution);
    }
    /**
     * Execute output edge
     */
    protected void runOutTransition(Execution execution) {<!-- -->
        outputs.forEach(tr->{<!-- -->
            tr.setEnabled(true);
            tr. execute(execution);
        });
    }
    @Override
    public String toString() {<!-- -->
        return StrUtil.format("model:{},name:{},displayName:{},time:{}", this.getClass().getSimpleName(), getName(),getDisplayName(), DateUtil.dateSecond ());
    }
}

Implement the exec method of the start node

The exec of the start node mainly executes the following logic:

  • output super.toString()
  • call runOutTransition
public class StartModel extends NodeModel {<!-- -->
    @Override
    void exec(Execution execution) {<!-- -->
        System.out.println(super.toString());
        runOutTransition(execution);
    }
}

Implement the exec method of the end node

The end node has no output edge, so only super.toString() is output

public class EndModel extends NodeModel {<!-- -->
    @Override
    public void exec(Execution execution) {<!-- -->
        System.out.println(super.toString());
    }
}

Implement the exec method of the task node

Task nodes are special, we can do the following to make them temporarily blocked:

public class TaskModel extends NodeModel {<!-- -->
    private String form; // Form ID
    private String assignee; // participant
    private String assignmentHandler; // participant processing class
    private TaskTypeEnum taskType; // task type (host/co-organizer)
    private TaskPerformTypeEnum performType; // Participation type (normal participation/countersigned participation)
    private String reminderTime; // reminder time
    private String reminderRepeat; // Repeat reminder interval
    private String expireTime; // Expect task completion time variable key
    private String autoExecute; // Whether to automatically execute Y/N upon expiration
    private String callback; // automatically execute the callback class
    private Dict ext = Dict.create(); // custom extension attributes
    @Override
    public void exec(Execution execution) {<!-- -->
        // Execute task node custom execution logic
        try {<!-- -->
            Thread. sleep(3000);
        } catch (InterruptedException e) {<!-- -->
            e.printStackTrace();
        }
        System.out.println(super.toString());
        runOutTransition(execution);
    }
}

At this time, the printed results are as follows:

model:StartModel,name:start,displayName:start,time:2023-04-25 22:27:45
model: TaskModel, name: apply, displayName: leave application, time: 2023-04-25 22:27:48
model:TaskModel,name:deptApprove,displayName:dept Approve,time:2023-04-25 22:27:51
model:EndModel,name:end,displayName:end,time:2023-04-25 22:27:51

Join an organization

Please open in WeChat:
“Lidong and His Friends”

fenchuan.jpg

Related source code

mldong-flow-demo-02

Process Designer

online experience