Workflow engine design and implementation · Parsing process definition files

In the previous article, we manually built a process object and simply printed and executed it. The way to build a process object is not very friendly. In order to build process objects more conveniently, we adopt a new method, that is, parse the process definition files mentioned in the basics and convert them into process models.
Here is a sample file to parse:
image.png
src/test/resources/leave.json

{<!-- -->
  "name": "leave",
  "displayName": "Leave",
  "instanceUrl": "leaveForm",
  "nodes": [
    {<!-- -->
      "id": "start",
      "type": "snaker:start",
      "x": 340,
      "y": 160,
      "properties": {<!-- -->
        "width": "120",
        "height": "80"
      },
      "text": {<!-- -->
        "x": 340,
        "y": 200,
        "value": "start"
      }
    },
    {<!-- -->
      "id": "apply",
      "type": "snaker:task",
      "x": 520,
      "y": 160,
      "properties": {<!-- -->
        "assignee": "approve. operator",
        "taskType": "Major",
        "performType": "ANY",
        "autoExecute": "N",
        "width": "120",
        "height": "80",
        "field": {<!-- -->
          "userKey": "1"
        }
      },
      "text": {<!-- -->
        "x": 520,
        "y": 160,
        "value": "Leave Request"
      }
    },
    {<!-- -->
      "id": "approveDept",
      "type": "snaker:task",
      "x": 740,
      "y": 160,
      "properties": {<!-- -->
        "assignmentHandler": "com.mldong.config.FlowAssignmentHandler",
        "taskType": "Major",
        "performType": "ANY",
        "autoExecute": "N",
        "width": "120",
        "height": "80"
      },
      "text": {<!-- -->
        "x": 740,
        "y": 160,
        "value": "Department Leader Approval"
      }
    },
    {<!-- -->
      "id": "end",
      "type": "snaker:end",
      "x": 980,
      "y": 160,
      "properties": {<!-- -->
        "width": "120",
        "height": "80"
      },
      "text": {<!-- -->
        "x": 980,
        "y": 200,
        "value": "End"
      }
    }
  ],
  "edges": [
    {<!-- -->
      "id": "t1",
      "type": "snaker:transition",
      "sourceNodeId": "start",
      "targetNodeId": "apply",
      "startPoint": {<!-- -->
        "x": 358,
        "y": 160
      },
      "endPoint": {<!-- -->
        "x": 460,
        "y": 160
      },
      "properties": {<!-- -->
        "height": 80,
        "width": 120
      },
      "pointsList": [
        {<!-- -->
          "x": 358,
          "y": 160
        },
        {<!-- -->
          "x": 460,
          "y": 160
        }
      ]
    },
    {<!-- -->
      "id": "t2",
      "type": "snaker:transition",
      "sourceNodeId": "apply",
      "targetNodeId": "approveDept",
      "startPoint": {<!-- -->
        "x": 580,
        "y": 160
      },
      "endPoint": {<!-- -->
        "x": 680,
        "y": 160
      },
      "properties": {<!-- -->
        "height": 80,
        "width": 120
      },
      "pointsList": [
        {<!-- -->
          "x": 580,
          "y": 160
        },
        {<!-- -->
          "x": 680,
          "y": 160
        }
      ]
    },
    {<!-- -->
      "id": "t3",
      "type": "snaker:transition",
      "sourceNodeId": "approveDept",
      "targetNodeId": "end",
      "startPoint": {<!-- -->
        "x": 800,
        "y": 160
      },
      "endPoint": {<!-- -->
        "x": 962,
        "y": 160
      },
      "properties": {<!-- -->
        "height": 80,
        "width": 120
      },
      "pointsList": [
        {<!-- -->
          "x": 800,
          "y": 160
        },
        {<!-- -->
          "x": 830,
          "y": 160
        },
        {<!-- -->
          "x": 830,
          "y": 160
        },
        {<!-- -->
          "x": 932,
          "y": 160
        },
        {<!-- -->
          "x": 932,
          "y": 160
        },
        {<!-- -->
          "x": 962,
          "y": 160
        }
      ]
    }
  ]
}

Class Diagram

Flowchart

Code Implementation

model/logicflow/LfPoint.java

package com.mldong.flow.engine.model.logicflow;

import lombok.Data;

import java.io.Serializable;
/**
 *
 * logicFlow coordinates
 * @author mldong
 * @date 2023/4/26
 */
@Data
public class LfPoint implements Serializable {<!-- -->
    private int x; // x-axis coordinates
    private int y; // y-axis coordinates
}

LogicFlow model object

model/logicflow/LfNode.java

package com.mldong.flow.engine.model.logicflow;

import cn.hutool.core.lang.Dict;
import lombok.Data;

import java.io.Serializable;
/**
 *
 * logicFlow node
 * @author mldong
 * @date 2023/4/26
 */
@Data
public class LfNode implements Serializable {<!-- -->
    private String id; // node unique id
    private String type; // node type
    private int x; // x-axis coordinates of node center point
    private int y; // node center point y-axis coordinates
    Dict properties; // node properties
    Dict text; // node text
}

model/logicflow/LfEdge.java

package com.mldong.flow.engine.model.logicflow;

import cn.hutool.core.lang.Dict;
import lombok.Data;

import java.io.Serializable;
import java.util.List;

/**
 *
 * LogicFlow side
 * @author mldong
 * @date 2022/6/12
 */
@Data
public class LfEdge implements Serializable {<!-- -->
    private String id; // the unique id of the edge
    private String type; // edge type
    private String sourceNodeId; // source node id
    private String targetNodeId; // target node id
    private Dict properties; // edge properties
    private Dict text; // edge text
    private LfPoint startPoint; // edge start point coordinates
    private LfPoint endPoint; // edge end point coordinates
    private List<LfPoint> pointsList; // collection of all points on the edge
}

model/logicflow/LfModel.java

package com.mldong.flow.engine.model.logicflow;

import com.mldong.flow.engine.model.BaseModel;
import lombok.Data;

import java.util.List;
/**
 *
 * logicFlow model
 * @author mldong
 * @date 2023/4/26
 */
@Data
public class LfModel extends BaseModel {<!-- -->
    private String type; // Process definition classification
    private String expireTime;//expiration time (constant or variable)
    private String instanceUrl; // The url to start the instance, after the front and back ends are separated, it is defined as the route name or route address
    private String instanceNoClass; // When the process is started, the serial number generation class of the process instance
    private List<LfNode> nodes; // node collection
    private List<LfEdge> edges; // collection of edges
}

Analysis class

parser/NodeParser.java

package com.mldong.flow.engine.parser;

import com.mldong.flow.engine.model.NodeModel;
import com.mldong.flow.engine.model.logicflow.LfEdge;
import com.mldong.flow.engine.model.logicflow.LfNode;

import java.util.List;
/**
 *
 * Node resolution interface
 * @author mldong
 * @date 2023/4/26
 */
public interface NodeParser {<!-- -->
    String NODE_NAME_PREFIX="snaker:"; // node name prefix
    String TEXT_VALUE_KEY = "value"; // text value
    String WIDTH_KEY = "width"; // node width
    String HEIGHT_KEY = "height"; // node height
    String PRE_INTERCEPTORS_KEY = "preInterceptors"; // pre-interceptors
    String POST_INTERCEPTORS_KEY = "postInterceptors"; // post interceptor
    String EXPR_KEY = "expr"; // expression key
    String HANDLE_CLASS_KEY = "handleClass"; // expression processing class
    String FORM_KEY = "form"; // form identifier
    String ASSIGNEE_KEY = "assignee"; // Participant
    String ASSIGNMENT_HANDLE_KEY = "assignmentHandler"; // Participant handling class
    String TASK_TYPE_KEY = "taskType"; // task type (host/co-organizer)
    String PERFORM_TYPE_KEY = "performType"; // Participation type (normal participation/countersigned participation)
    String REMINDER_TIME_KEY = "reminderTime"; // Reminder time
    String REMINDER_REPEAT_KEY = "reminderRepeat"; // Repeat reminder interval
    String EXPIRE_TIME_KEY = "expireTime"; // The expected task completion time variable key
    String AUTH_EXECUTE_KEY = "autoExecute"; // Whether to automatically execute Y/N upon expiration
    String CALLBACK_KEY = "callback"; // automatically execute the callback class
    String EXT_FIELD_KEY = "field"; // Custom extended attributes
    /**
     * Node attribute parsing method, which is parsed by the parsing class
     * @param lfNode LogicFlow node object
     * @param edges all edge objects
     */
    void parse(LfNode lfNode, List<LfEdge> edges);

    /**
     * After the parsing is completed, provide the returned NodeModel object
     * @return node model
     */
    NodeModel getModel();
}

parser/AbstractNodeParser.java

package com.mldong.flow.engine.parser;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;

import com.mldong.flow.engine.model.NodeModel;
import com.mldong.flow.engine.model.TransitionModel;
import com.mldong.flow.engine.model.logicflow.LfEdge;
import com.mldong.flow.engine.model.logicflow.LfNode;

import java.util.List;
import java.util.stream.Collectors;
/**
 *
 * General attribute parsing (basic attributes and edges)
 * @author mldong
 * @date 2023/4/26
 */
public abstract class AbstractNodeParser implements NodeParser {<!-- -->
    // node model object
    protected NodeModel nodeModel;
    @Override
    public void parse(LfNode lfNode, List<LfEdge> edges) {<!-- -->
        nodeModel = newModel();
        // parse basic information
        nodeModel.setName(lfNode.getId());
        if(ObjectUtil.isNotNull(lfNode.getText())) {<!-- -->
            nodeModel.setDisplayName(lfNode.getText().getStr(TEXT_VALUE_KEY));
        }
        Dict properties = lfNode. getProperties();
        // parse the layout properties
        int x = lfNode. getX();
        int y = lfNode. getY();
        int w = Convert.toInt(properties.get(WIDTH_KEY),0);
        int h = Convert.toInt(properties.get(HEIGHT_KEY),0);
        nodeModel.setLayout(StrUtil.format("{},{},{},{}",x,y,w,h));
        // parse interceptor
        nodeModel.setPreInterceptors(properties.getStr(PRE_INTERCEPTORS_KEY));
        nodeModel.setPostInterceptors(properties.getStr(POST_INTERCEPTORS_KEY));
        // parse the output edge
        List<LfEdge> nodeEdges = getEdgeBySourceNodeId(lfNode.getId(), edges);
        nodeEdges.forEach(edge->{<!-- -->
            TransitionModel transitionModel = new TransitionModel();
            transitionModel.setName(edge.getId());
            transitionModel.setTo(edge.getTargetNodeId());
            transitionModel.setSource(nodeModel);
            transitionModel.setExpr(edge.getProperties().getStr(EXPR_KEY));
            if(CollectionUtil.isNotEmpty(edge.getPointsList())) {<!-- -->
                //x1,y1;x2,y2;x3,y3...
                transitionModel.setG(edge.getPointsList().stream().map(point->{<!-- -->
                    return point. getX() + "," + point. getY();
                }).collect(Collectors.joining(";")));
            } else {<!-- -->
                if(ObjectUtil.isNotNull(edge.getStartPoint()) & amp; & amp; ObjectUtil.isNotNull(edge.getEndPoint())) {<!-- -->
                    int startPointX = edge.getStartPoint().getX();
                    int startPointY = edge.getStartPoint().getY();
                    int endPointX = edge. getEndPoint(). getX();
                    int endPointY = edge. getEndPoint(). getY();
                    transitionModel.setG(StrUtil.format("{},{};{},{}", startPointX, startPointY, endPointX, endPointY));
                }
            }
            nodeModel.getOutputs().add(transitionModel);
        });
        // call subclass specific parsing method
        parseNode(lfNode);
    }

    /**
     * Subclasses implement this class to complete specific parsing
     * @param lfNode
     */
    public abstract void parseNode(LfNode lfNode);

    /**
     * Create node model objects by subclasses respectively
     * @return
     */
    public abstract NodeModel newModel();
    @Override
    public NodeModel getModel() {<!-- -->
        return nodeModel;
    }
    /**
     * Get node input
     * @param targetNodeId target node id
     * @param edges
     * @return
     */
    private List<LfEdge> getEdgeByTargetNodeId(String targetNodeId,List<LfEdge> edges) {<!-- -->
        return edges.stream().filter(edge->{<!-- -->
            return edge.getTargetNodeId().equals(targetNodeId);
        }).collect(Collectors.toList());
    }
    /**
     * Get node output
     * @param sourceNodeId source node id
     * @param edges
     * @return
     */
    private List<LfEdge> getEdgeBySourceNodeId(String sourceNodeId,List<LfEdge> edges) {<!-- -->
        return edges.stream().filter(edge->{<!-- -->
            return edge.getSourceNodeId().equals(sourceNodeId);
        }).collect(Collectors.toList());
    }
}

parser/impl/StartParser.java

package com.mldong.flow.engine.parser.impl;

import com.mldong.flow.engine.model.NodeModel;
import com.mldong.flow.engine.model.StartModel;
import com.mldong.flow.engine.model.logicflow.LfNode;
import com.mldong.flow.engine.parser.AbstractNodeParser;
/**
 *
 * Start node parsing class
 * @author mldong
 * @date 2023/4/26
 */
public class StartParser extends AbstractNodeParser {<!-- -->
    @Override
    public void parseNode(LfNode lfNode) {<!-- -->

    }

    @Override
    public NodeModel newModel() {<!-- -->
        return new StartModel();
    }
}

parser/impl/EndParser.java

package com.mldong.flow.engine.parser.impl;

import com.mldong.flow.engine.model.EndModel;
import com.mldong.flow.engine.model.NodeModel;
import com.mldong.flow.engine.model.logicflow.LfNode;
import com.mldong.flow.engine.parser.AbstractNodeParser;
/**
 *
 * End node parsing class
 * @author mldong
 * @date 2023/4/26
 */
public class EndParser extends AbstractNodeParser {<!-- -->

    @Override
    public void parseNode(LfNode lfNode) {<!-- -->

    }

    @Override
    public NodeModel newModel() {<!-- -->
        return new EndModel();
    }
}

parser/impl/TaskParser.java

package com.mldong.flow.engine.parser.impl;


import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Dict;
import com.mldong.flow.engine.enums.TaskPerformTypeEnum;
import com.mldong.flow.engine.enums.TaskTypeEnum;
import com.mldong.flow.engine.model.NodeModel;
import com.mldong.flow.engine.model.TaskModel;
import com.mldong.flow.engine.model.logicflow.LfNode;
import com.mldong.flow.engine.parser.AbstractNodeParser;

/**
 *
 * Task node parsing class
 * @author mldong
 * @date 2023/4/26
 */
public class TaskParser extends AbstractNodeParser {<!-- -->

    /**
     * Parse the unique attributes of task nodes
     * @param lfNode
     */
    @Override
    public void parseNode(LfNode lfNode) {<!-- -->
        TaskModel taskModel = (TaskModel) nodeModel;
        Dict properties = lfNode. getProperties();
        taskModel.setForm(properties.getStr(FORM_KEY));
        taskModel.setAssignee(properties.getStr(ASSIGNEE_KEY));
        taskModel.setAssignmentHandler(properties.getStr(ASSIGNMENT_HANDLE_KEY));
        taskModel.setTaskType(TaskTypeEnum.codeOf(properties.getInt(TASK_TYPE_KEY)));
        taskModel.setPerformType(TaskPerformTypeEnum.codeOf(properties.getInt(PERFORM_TYPE_KEY)));
        taskModel.setReminderTime(properties.getStr(REMINDER_TIME_KEY));
        taskModel.setReminderRepeat(properties.getStr(REMINDER_REPEAT_KEY));
        taskModel.setExpireTime(properties.getStr(EXPIRE_TIME_KEY));
        taskModel.setAutoExecute(properties.getStr(AUTH_EXECUTE_KEY));
        taskModel.setCallback(properties.getStr(CALLBACK_KEY));
        // custom extension properties
        Object field = properties. get(EXT_FIELD_KEY);
        if(field!=null) {<!-- -->
            taskModel.setExt(Convert.convert(Dict.class, field));
        }
    }

    @Override
    public NodeModel newModel() {<!-- -->
        return new TaskModel();
    }
}

parser/impl/ForkParser.java

package com.mldong.flow.engine.parser.impl;


import com.mldong.flow.engine.model.ForkModel;
import com.mldong.flow.engine.model.NodeModel;
import com.mldong.flow.engine.model.logicflow.LfNode;
import com.mldong.flow.engine.parser.AbstractNodeParser;

/**
 *
 * Branch node parsing class
 * @author mldong
 * @date 2023/4/26
 */
public class ForkParser extends AbstractNodeParser {<!-- -->

    @Override
    public void parseNode(LfNode lfNode) {<!-- -->

    }

    @Override
    public NodeModel newModel() {<!-- -->
        return new ForkModel();
    }
}

parser/impl/JoinParser.java

package com.mldong.flow.engine.parser.impl;


import com.mldong.flow.engine.model.JoinModel;
import com.mldong.flow.engine.model.NodeModel;
import com.mldong.flow.engine.model.logicflow.LfNode;
import com.mldong.flow.engine.parser.AbstractNodeParser;

/**
 *
 * merge node parser
 * @author mldong
 * @date 2023/4/26
 */
public class JoinParser extends AbstractNodeParser {<!-- -->

    @Override
    public void parseNode(LfNode lfNode) {<!-- -->

    }

    @Override
    public NodeModel newModel() {<!-- -->
        return new JoinModel();
    }
}

parser/impl/DecisionParser.java

package com.mldong.flow.engine.parser.impl;


import cn.hutool.core.lang.Dict;
import com.mldong.flow.engine.model.DecisionModel;
import com.mldong.flow.engine.model.NodeModel;
import com.mldong.flow.engine.model.logicflow.LfNode;
import com.mldong.flow.engine.parser.AbstractNodeParser;

/**
 *
 * Decision node parsing class
 * @author mldong
 * @date 2023/4/26
 */
public class DecisionParser extends AbstractNodeParser {<!-- -->
    /**
     * Parse the unique attributes of decision nodes
     * @param lfNode
     */
    @Override
    public void parseNode(LfNode lfNode) {<!-- -->
        DecisionModel decisionModel = (DecisionModel) nodeModel;
        Dict properties = lfNode. getProperties();
        decisionModel.setExpr(properties.getStr(EXPR_KEY));
        decisionModel.setHandleClass(properties.getStr(HANDLE_CLASS_KEY));
    }

    @Override
    public NodeModel newModel() {<!-- -->
        return new DecisionModel();
    }
}

Service context related class

Context.java

package com.mldong.flow.engine;

import java.util.List;

/**
 *
 * Service context interface, similar to spring's ioc
 * @author mldong
 * @date 2023/4/26
 */
public interface Context {<!-- -->
    /**
     * Register with the service factory according to the service name and instance
     * @param name service name
     * @param object service instance
     */
    void put(String name, Object object);

    /**
     * Register with the service factory according to the service name and type
     * @param name service name
     * @param clazz type
     */
    void put(String name, Class<?> clazz);

    /**
     * Determine whether the given service name exists
     * @param name service name
     * @return
     */
    boolean exist(String name);

    /**
     * Find a service instance of a given type
     * @param clazz type
     * @return
     */
    <T> T find(Class<T> clazz);

    /**
     * Find all service instances of a given type
     * @param clazz type
     * @return
     */
    <T> List<T> findList(Class<T> clazz);

    /**
     * Find a service instance based on a given service name and type
     * @param name service name
     * @param clazz type
     * @return
     */
    <T> T findByName(String name, Class<T> clazz);
}

impl/SimpleContext.java

package com.mldong.flow.engine.impl;

import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import com.mldong.flow.engine.Context;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 *
 * Simple context discovery implementation class
 * @author mldong
 * @date 2023/4/26
 */
public class SimpleContext implements Context {<!-- -->
    private Dict dict = Dict. create();
    @Override
    public void put(String name, Object object) {<!-- -->
        dict. put(name, object);
    }

    @Override
    public void put(String name, Class<?> clazz) {<!-- -->
        dict.put(name, ReflectUtil.newInstance(clazz));
    }

    @Override
    public boolean exist(String name) {<!-- -->
        return ObjectUtil.isNotNull(dict.getObj(name));
    }

    @Override
    public <T> T find(Class<T> clazz) {<!-- -->
        for (Map.Entry<String, Object> entry : dict.entrySet()) {<!-- -->
            if (clazz. isInstance(entry. getValue())) {<!-- -->
                return clazz.cast(entry.getValue());
            }
        }
        return null;
    }

    @Override
    public <T> List<T> findList(Class<T> clazz) {<!-- -->
        List<T> res = new ArrayList<>();
        for (Map.Entry<String, Object> entry : dict.entrySet()) {<!-- -->
            if (clazz. isInstance(entry. getValue())) {<!-- -->
                res.add(clazz.cast(entry.getValue()));
            }
        }
        return res;
    }

    @Override
    public <T> T findByName(String name, Class<T> clazz) {<!-- -->
        for (Map.Entry<String, Object> entry : dict.entrySet()) {<!-- -->
            if (entry.getKey().equals(name) & amp; & amp; clazz.isInstance(entry.getValue())) {<!-- -->
                return clazz.cast(entry.getValue());
            }
        }
        return null;
    }
}

core/ServiceContext.java

package com.mldong.flow.engine.core;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ReflectUtil;
import com.mldong.flow.engine.Context;

import java.util.List;

/**
 *
 * Singleton service context
 * @author mldong
 * @date 2022/6/12
 */
public class ServiceContext {<!-- -->
    private static Context context;


    public static void setContext(Context context) {<!-- -->
        ServiceContext.context = context;
    }

    public static void put(String name, Object object) {<!-- -->
        Assert.notNull(context,"Unregistered service context");
        context. put(name, object);
    }

    public static void put(String name, Class<?> clazz) {<!-- -->
        Assert.notNull(context,"Unregistered service context");
        context.put(name, ReflectUtil.newInstance(clazz));
    }

    public static boolean exist(String name) {<!-- -->
        Assert.notNull(context,"Unregistered service context");
        return context.exist(name);
    }

    public static <T> T find(Class<T> clazz) {<!-- -->
        Assert.notNull(context,"Unregistered service context");
        return context. find(clazz);
    }

    public static <T> List<T> findList(Class<T> clazz) {<!-- -->
        Assert.notNull(context,"Unregistered service context");
        return context. findList(clazz);
    }

    public static <T> T findByName(String name, Class<T> clazz) {<!-- -->
        Assert.notNull(context,"Unregistered service context");
        return context.findByName(name, clazz);
    }
}

Analysis entry class

parser/ModelParser.java

package com.mldong.flow.engine.parser;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.json.JSONUtil;
import com.mldong.flow.engine.core.ServiceContext;
import com.mldong.flow.engine.model.NodeModel;
import com.mldong.flow.engine.model.ProcessModel;
import com.mldong.flow.engine.model.TaskModel;
import com.mldong.flow.engine.model.TransitionModel;
import com.mldong.flow.engine.model.logicflow.LfEdge;
import com.mldong.flow.engine.model.logicflow.LfModel;
import com.mldong.flow.engine.model.logicflow.LfNode;

import java.io.ByteArrayInputStream;
import java.util.List;

public class ModelParser {<!-- -->
    private ModelParser(){<!-- -->}

    /**
     * Parse the json definition file into a process model object
     * @param bytes
     * @return
     */
    public static ProcessModel parse(byte [] bytes) {<!-- -->
        String json = IoUtil. readUtf8(new ByteArrayInputStream(bytes));
        LfModel lfModel = JSONUtil.parse(json).toBean(LfModel.class);
        ProcessModel processModel = new ProcessModel();
        List<LfNode> nodes = lfModel. getNodes();
        List<LfEdge> edges = lfModel. getEdges();
        if(CollectionUtil.isEmpty(nodes) || CollectionUtil.isEmpty(edges) ) {<!-- -->
            return processModel;
        }
        // Process definition basic information
        processModel.setName(lfModel.getName());
        processModel.setDisplayName(lfModel.getDisplayName());
        processModel.setType(lfModel.getType());
        processModel.setInstanceUrl(lfModel.getInstanceUrl());
        processModel.setInstanceNoClass(lfModel.getInstanceNoClass());
        // process node information
        nodes.forEach(node->{<!-- -->
            String type = node.getType().replace(NodeParser.NODE_NAME_PREFIX,"");
            NodeParser nodeParser = ServiceContext.findByName(type,NodeParser.class);
            if(nodeParser!=null) {<!-- -->
                nodeParser. parse(node, edges);
                NodeModel nodeModel = nodeParser. getModel();
                processModel.getNodes().add(nodeParser.getModel());
                if (nodeModel instanceof TaskModel) {<!-- -->
                    processModel.getTasks().add((TaskModel) nodeModel);
                }
            }
        });
        // Loop the node model, construct the source and target of the input edge and output edge
        for(NodeModel node : processModel. getNodes()) {<!-- -->
            for(TransitionModel transition : node.getOutputs()) {<!-- -->
                String to = transition. getTo();
                for(NodeModel node2 : processModel. getNodes()) {<!-- -->
                    if(to.equalsIgnoreCase(node2.getName())) {<!-- -->
                        node2.getInputs().add(transition);
                        transition.setTarget(node2);
                    }
                }
            }
        }
        return processModel;
    }
}

Configuration class

cfg/Configuration.java

package com.mldong.flow.engine.cfg;


import com.mldong.flow.engine.Context;
import com.mldong.flow.engine.core.ServiceContext;
import com.mldong.flow.engine.impl.SimpleContext;
import com.mldong.flow.engine.parser.impl.*;

public class Configuration {<!-- -->
    public Configuration() {<!-- -->
        this(new SimpleContext());
    }
    public Configuration(Context context) {<!-- -->
        ServiceContext.setContext(context);
        ServiceContext.put("decision", DecisionParser.class);
        ServiceContext.put("end", EndParser.class);
        ServiceContext.put("fork", ForkParser.class);
        ServiceContext.put("join", JoinParser.class);
        ServiceContext.put("start", StartParser.class);
        ServiceContext. put("task", TaskParser. class);
    }
}

Unit test class

ModelParserTest.java

package com.mldong.flow;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Dict;
import com.mldong.flow.engine.cfg.Configuration;
import com.mldong.flow.engine.core.Execution;
import com.mldong.flow.engine.model.ProcessModel;
import com.mldong.flow.engine.parser.ModelParser;
import org.junit.Test;

/**
 *
 * Model parsing unit test
 * @author mldong
 * @date 2023/4/26
 */
public class ModelParserTest {<!-- -->
    @Test
    public void parseTest() {<!-- -->
        new Configuration();
        ProcessModel processModel = ModelParser. parse(IoUtil. readBytes(this. getClass(). getResourceAsStream("/leave. json")));
        Execution execution = new Execution();
        execution.setArgs(Dict.create());
        processModel.getStart().execute(execution);
    }
}

Run result

model:StartModel,name:start,displayName:start,time:2023-04-26 21:32:40
model:TaskModel,name:apply,displayName:leave application,time:2023-04-26 21:32:41
model:TaskModel,name:approveDept,displayName:Department Leader Approval,time:2023-04-26 21:32:42
model:EndModel,name:end,displayName:end,time:2023-04-26 21:32:42

Join an organization

Please open in WeChat:
“Lidong and His Friends”

fenchuan.jpg

Related source code

mldong-flow-demo-03

Process Designer

online experience