001/*
002 *    Copyright 2024-2025, Warm-Flow (290631660@qq.com).
003 *
004 *    Licensed under the Apache License, Version 2.0 (the "License");
005 *    you may not use this file except in compliance with the License.
006 *    You may obtain a copy of the License at
007 *
008 *       https://www.apache.org/licenses/LICENSE-2.0
009 *
010 *    Unless required by applicable law or agreed to in writing, software
011 *    distributed under the License is distributed on an "AS IS" BASIS,
012 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *    See the License for the specific language governing permissions and
014 *    limitations under the License.
015 */
016package org.dromara.warm.flow.core.utils;
017
018import org.dromara.warm.flow.core.FlowFactory;
019import org.dromara.warm.flow.core.constant.ExceptionCons;
020import org.dromara.warm.flow.core.dto.FlowCombine;
021import org.dromara.warm.flow.core.entity.Definition;
022import org.dromara.warm.flow.core.entity.Node;
023import org.dromara.warm.flow.core.entity.Skip;
024import org.dromara.warm.flow.core.enums.ActivityStatus;
025import org.dromara.warm.flow.core.enums.NodeType;
026import org.dromara.warm.flow.core.enums.SkipType;
027import org.dom4j.Document;
028import org.dom4j.DocumentHelper;
029import org.dom4j.Element;
030import org.dom4j.io.SAXReader;
031
032import java.io.IOException;
033import java.io.InputStream;
034import java.math.BigDecimal;
035import java.util.*;
036import java.util.concurrent.atomic.AtomicInteger;
037
038/**
039 * 流程配置帮助类
040 *
041 * @author zhoukai
042 */
043public class FlowConfigUtil {
044    /**
045     * 无参构造
046     */
047    private FlowConfigUtil() {
048
049    }
050
051    /**
052     * 读取配置
053     *
054     * @param is
055     * @throws Exception
056     */
057    public static FlowCombine readConfig(InputStream is) throws Exception {
058        Definition definition = readDocument(is);
059        return structureFlow(definition);
060    }
061
062    @SuppressWarnings("unchecked")
063    public static Definition readDocument(InputStream is) throws Exception {
064        AssertUtil.isNull(is, "文件不存在!");
065        // 获取流程节点
066        Element definitionElement = new SAXReader().read(is).getRootElement();
067        AssertUtil.isNull(definitionElement, "流程为空!");
068
069        // 读取流程定义
070        Definition definition = FlowFactory.newDef();
071        definition.setFlowCode(definitionElement.attributeValue("flowCode"));
072        definition.setFlowName(definitionElement.attributeValue("flowName"));
073        definition.setVersion(definitionElement.attributeValue("version"));
074        definition.setCategory(definitionElement.attributeValue("category"));
075        definition.setFormCustom(definitionElement.attributeValue("formCustom"));
076        definition.setFormPath(definitionElement.attributeValue("formPath"));
077        definition.setExt(definitionElement.attributeValue("ext"));
078        definition.setActivityStatus(ActivityStatus.ACTIVITY.getKey());
079        definition.setListenerType(definitionElement.attributeValue("listenerType"));
080        definition.setListenerPath(definitionElement.attributeValue("listenerPath"));
081
082        List<Element> nodesElement = definitionElement.elements();
083        // 遍历一个流程中的各个节点
084        List<Node> nodeList = definition.getNodeList();
085        for (Element nodeElement : nodesElement) {
086            Node node = initNodeAndCondition(nodeElement);
087            nodeList.add(node);
088        }
089        try {
090            if (is != null) {
091                is.close();
092            }
093        } catch (IOException ex) {
094            ex.printStackTrace();
095        }
096        return definition;
097    }
098
099    /**
100     * 读取工作节点和跳转条件
101     *
102     * @param nodeElement
103     * @return
104     */
105    private static Node initNodeAndCondition(Element nodeElement) {
106        Node node = FlowFactory.newNode();
107        node.setNodeType(NodeType.getKeyByValue(nodeElement.attributeValue("nodeType")));
108        node.setNodeCode(nodeElement.attributeValue("nodeCode"));
109        node.setNodeName(nodeElement.attributeValue("nodeName"));
110        node.setPermissionFlag(nodeElement.attributeValue("permissionFlag"));
111        if (StringUtils.isNotEmpty(nodeElement.attributeValue("nodeRatio"))) {
112            node.setNodeRatio(new BigDecimal(nodeElement.attributeValue("nodeRatio")));
113        } else {
114            node.setNodeRatio(new BigDecimal("0"));
115        }
116        node.setCoordinate(nodeElement.attributeValue("coordinate"));
117        node.setSkipAnyNode(nodeElement.attributeValue("skipAnyNode"));
118        node.setListenerType(nodeElement.attributeValue("listenerType"));
119        node.setListenerPath(nodeElement.attributeValue("listenerPath"));
120        node.setHandlerType(nodeElement.attributeValue("handlerType"));
121        node.setHandlerPath(nodeElement.attributeValue("handlerPath"));
122        node.setFormCustom(nodeElement.attributeValue("formCustom"));
123        node.setFormPath(nodeElement.attributeValue("formPath"));
124        FlowFactory.dataFillHandler().idFill(node);
125
126        List<Element> skipsElement = nodeElement.elements();
127        List<Skip> skips = node.getSkipList();
128        // 遍历节点下的跳转条件
129        for (Element skipElement : skipsElement) {
130            Skip skip = FlowFactory.newSkip();
131            if ("skip".equals(skipElement.getName())) {
132                skip.setNowNodeCode(node.getNodeCode());
133                skip.setNowNodeType(node.getNodeType());
134                skip.setNextNodeCode(skipElement.getText());
135                // 条件约束
136                skip.setSkipName(skipElement.attributeValue("skipName"));
137                skip.setSkipType(skipElement.attributeValue("skipType"));
138                skip.setCoordinate(skipElement.attributeValue("coordinate"));
139                skip.setSkipCondition(skipElement.attributeValue("skipCondition"));
140                skips.add(skip);
141            }
142        }
143        return node;
144    }
145
146    @SuppressWarnings("unchecked")
147    public static Document createDocument(Definition definition) {
148        // 创建document对象
149        Document document = DocumentHelper.createDocument();
150        // 创建根节点bookRoot
151        Element definitionElement = document.addElement("definition");
152        // 向子节点中添加属性
153        definitionElement.addAttribute("flowCode", definition.getFlowCode());
154        definitionElement.addAttribute("flowName", definition.getFlowName());
155        definitionElement.addAttribute("version", definition.getVersion());
156        definitionElement.addAttribute("category", definition.getCategory());
157        definitionElement.addAttribute("formCustom", definition.getFormCustom());
158        definitionElement.addAttribute("formPath", definition.getFormPath());
159        definitionElement.addAttribute("listenerType", definition.getListenerType());
160        definitionElement.addAttribute("listenerPath", definition.getListenerPath());
161        definitionElement.addAttribute("ext", definition.getExt());
162
163        List<Node> nodeList = definition.getNodeList();
164        for (Node node : nodeList) {
165            // 向节点中添加子节点
166            Element nodeElement = definitionElement.addElement("node");
167            nodeElement.addAttribute("nodeType", NodeType.getValueByKey(node.getNodeType()));
168            nodeElement.addAttribute("nodeCode", node.getNodeCode());
169            nodeElement.addAttribute("nodeName", node.getNodeName());
170            nodeElement.addAttribute("permissionFlag", node.getPermissionFlag());
171            if (Objects.nonNull(node.getNodeRatio())) {
172                nodeElement.addAttribute("nodeRatio", node.getNodeRatio().toString());
173            }
174            nodeElement.addAttribute("coordinate", node.getCoordinate());
175            nodeElement.addAttribute("skipAnyNode", node.getSkipAnyNode());
176            nodeElement.addAttribute("listenerType", node.getListenerType());
177            nodeElement.addAttribute("listenerPath", node.getListenerPath());
178            nodeElement.addAttribute("handlerType", node.getHandlerType());
179            nodeElement.addAttribute("handlerPath", node.getHandlerPath());
180            nodeElement.addAttribute("formCustom", node.getFormCustom());
181            nodeElement.addAttribute("formPath", node.getFormPath());
182
183            List<Skip> skipList = node.getSkipList();
184            if (CollUtil.isNotEmpty(skipList)) {
185                for (Skip skip : skipList) {
186                    Element skipElement = nodeElement.addElement("skip");
187                    skipElement.addAttribute("coordinate", skip.getCoordinate());
188                    if (StringUtils.isNotEmpty(skip.getSkipType())) {
189                        AssertUtil.isFalse(StringUtils.isNotEmpty(skip.getNextNodeCode()), "下一个流程节点编码为空");
190                        skipElement.addAttribute("skipType", skip.getSkipType());
191                    }
192                    if (StringUtils.isNotEmpty(skip.getSkipName())) {
193                        skipElement.addAttribute("skipName", skip.getSkipName());
194                    }
195                    if (StringUtils.isNotEmpty(skip.getSkipCondition())) {
196                        skipElement.addAttribute("skipCondition", skip.getSkipCondition());
197                    }
198                    skipElement.addText(skip.getNextNodeCode());
199                }
200            }
201        }
202        return document;
203    }
204
205    private static FlowCombine structureFlow(Definition definition) {
206        // 获取流程
207        FlowCombine combine = new FlowCombine();
208        // 流程定义
209        combine.setDefinition(definition);
210        // 所有的流程节点
211        List<Node> allNodes = combine.getAllNodes();
212        // 所有的流程连线
213        List<Skip> allSkips = combine.getAllSkips();
214
215        String flowName = definition.getFlowName();
216        AssertUtil.isEmpty(definition.getFlowCode(), "【" + flowName + "】流程flowCode为空!");
217        // 发布
218        definition.setIsPublish(0);
219        definition.setUpdateTime(new Date());
220        FlowFactory.dataFillHandler().idFill(definition);
221
222        List<Node> nodeList = definition.getNodeList();
223        // 每一个流程的开始节点个数
224        int startNum = 0;
225        Set<String> nodeCodeSet = new HashSet<>();
226        // 便利一个流程中的各个节点
227        for (Node node : nodeList) {
228            initNodeAndCondition(node, definition.getId(), definition.getVersion());
229            if (NodeType.isStart(node.getNodeType())) {
230                startNum++;
231                AssertUtil.isTrue(startNum > 1, "[" + flowName + "]" + ExceptionCons.MUL_START_NODE);
232            }
233            // 保证不存在重复的nodeCode
234            AssertUtil.contains(nodeCodeSet, node.getNodeCode(),
235                    "【" + flowName + "】" + ExceptionCons.SAME_NODE_CODE);
236            nodeCodeSet.add(node.getNodeCode());
237            allNodes.add(node);
238            allSkips.addAll(node.getSkipList());
239        }
240        Map<String, Integer> skipMap = StreamUtils.toMap(allNodes, Node::getNodeCode, Node::getNodeType);
241        allSkips.forEach(allSkip -> allSkip.setNextNodeType(skipMap.get(allSkip.getNextNodeCode())));
242        AssertUtil.isTrue(startNum == 0, "[" + flowName + "]" + ExceptionCons.LOST_START_NODE);
243        // 校验跳转节点的合法性
244        checkSkipNode(allSkips);
245        // 校验所有目标节点是否都存在
246        validaIsExistDestNode(allSkips, nodeCodeSet);
247        return combine;
248    }
249
250    /**
251     * 校验跳转节点的合法性
252     *
253     * @param allSkips
254     */
255    private static void checkSkipNode(List<Skip> allSkips) {
256        Map<String, List<Skip>> allSkipMap = StreamUtils.groupByKey(allSkips, Skip::getNowNodeCode);
257        // 不可同时通过或者退回到多个中间节点,必须先流转到网关节点
258        allSkipMap.forEach((key, values) -> {
259            AtomicInteger passNum = new AtomicInteger();
260            AtomicInteger rejectNum = new AtomicInteger();
261            for (Skip value : values) {
262                if (NodeType.isBetween(value.getNowNodeType()) && NodeType.isBetween(value.getNextNodeType())) {
263                    if (SkipType.isPass(value.getSkipType())) {
264                        passNum.getAndIncrement();
265                    } else {
266                        rejectNum.getAndIncrement();
267                    }
268                }
269            }
270            AssertUtil.isTrue(passNum.get() > 1 || rejectNum.get() > 1, ExceptionCons.MUL_SKIP_BETWEEN);
271        });
272    }
273
274    /**
275     * 校验所有的目标节点是否存在
276     *
277     * @param allSkips
278     * @param nodeCodeSet
279     */
280    private static void validaIsExistDestNode(List<Skip> allSkips, Set<String> nodeCodeSet) {
281        for (int i = 0; i < allSkips.size(); i++) {
282            String nextNodeCode = allSkips.get(i).getNextNodeCode();
283            AssertUtil.isTrue(!nodeCodeSet.contains(nextNodeCode), "【" + nextNodeCode + "】" + ExceptionCons.NULL_NODE_CODE);
284        }
285    }
286
287
288    /**
289     * 读取工作节点和跳转条件
290     *
291     * @param node
292     * @param definitionId
293     * @param version
294     * @return
295     */
296    private static void initNodeAndCondition(Node node, Long definitionId, String version) {
297        String nodeName = node.getNodeName();
298        String nodeCode = node.getNodeCode();
299        List<Skip> skipList = node.getSkipList();
300        if (!NodeType.isEnd(node.getNodeType())) {
301            AssertUtil.isEmpty(skipList, "开始和中间节点必须有跳转规则");
302        }
303        AssertUtil.isEmpty(nodeCode, "[" + nodeName + "]" + ExceptionCons.LOST_NODE_CODE);
304
305        node.setVersion(version);
306        node.setDefinitionId(definitionId);
307        node.setUpdateTime(new Date());
308
309        // 中间节点的集合, 跳转类型和目标节点不能重复
310        Set<String> betweenSet = new HashSet<>();
311        // 网关的集合 跳转条件和下目标节点不能重复
312        Set<String> gateWaySet = new HashSet<>();
313        int skipNum = 0;
314        // 遍历节点下的跳转条件
315        for (Skip skip : skipList) {
316            if (NodeType.isStart(node.getNodeType())) {
317                skipNum++;
318                AssertUtil.isTrue(skipNum > 1, "[" + node.getNodeName() + "]" + ExceptionCons.MUL_START_SKIP);
319            }
320            AssertUtil.isEmpty(skip.getNextNodeCode(), "【" + nodeName + "】" + ExceptionCons.LOST_DEST_NODE);
321            FlowFactory.dataFillHandler().idFill(skip);
322            // 流程id
323            skip.setDefinitionId(definitionId);
324            // 节点id
325            skip.setNodeId(node.getId());
326            if (NodeType.isGateWaySerial(node.getNodeType())) {
327                String target = skip.getSkipCondition() + ":" + skip.getNextNodeCode();
328                AssertUtil.contains(gateWaySet, target, "[" + nodeName + "]" + ExceptionCons.SAME_CONDITION_NODE);
329                gateWaySet.add(target);
330            } else if (NodeType.isGateWayParallel(node.getNodeType())) {
331                String target = skip.getNextNodeCode();
332                AssertUtil.contains(gateWaySet, target, "[" + nodeName + "]" + ExceptionCons.SAME_DEST_NODE);
333                gateWaySet.add(target);
334            } else {
335                String value = skip.getSkipType() + ":" + skip.getNextNodeCode();
336                AssertUtil.contains(betweenSet, value, "[" + nodeName + "]" + ExceptionCons.SAME_CONDITION_VALUE);
337                betweenSet.add(value);
338            }
339        }
340    }
341
342}