浏览代码

enhancement #I455ZS 规则重复嵌套校验

bryan31 3 年之前
父节点
当前提交
d70d3aca5e

+ 7 - 1
liteflow-core/src/main/java/com/yomahub/liteflow/core/FlowExecutor.java

@@ -100,13 +100,16 @@ public class FlowExecutor {
                 rulePathList.add(path);
 
                 //支持多类型的配置文件,分别解析
-                if (liteflowConfig.isSupportMultipleType()){
+                if (liteflowConfig.isSupportMultipleType()) {
                     if (ObjectUtil.isNotNull(parser)) {
                         parser.parseMain(ListUtil.toList(path));
                     } else {
                         throw new ConfigErrorException("parse error, please check liteflow config property");
                     }
                 }
+            } catch (CyclicDependencyException e){
+                LOG.error(e.getMessage());
+                throw e;
             } catch (Exception e) {
                 String errorMsg = StrUtil.format("init flow executor cause error,cannot find the parse for path {}", path);
                 LOG.error(errorMsg, e);
@@ -130,6 +133,9 @@ public class FlowExecutor {
                 } else {
                     throw new ConfigErrorException("parse error, please check liteflow config property");
                 }
+            } catch (CyclicDependencyException e){
+                LOG.error(e.getMessage());
+                throw e;
             } catch (Exception e) {
                 String errorMsg = StrUtil.format("init flow executor cause error,can not parse rule file {}", rulePathList);
                 LOG.error(errorMsg, e);

+ 22 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/exception/CyclicDependencyException.java

@@ -0,0 +1,22 @@
+
+package com.yomahub.liteflow.exception;
+
+public class CyclicDependencyException extends RuntimeException {
+
+	private static final long serialVersionUID = 1L;
+
+	/** 异常信息 */
+	private String message;
+
+	public CyclicDependencyException(String message) {
+		this.message = message;
+	}
+
+	public String getMessage() {
+		return message;
+	}
+
+	public void setMessage(String message) {
+		this.message = message;
+	}
+}

+ 60 - 60
liteflow-core/src/main/java/com/yomahub/liteflow/parser/XmlFlowParser.java

@@ -11,6 +11,7 @@ import com.yomahub.liteflow.entity.flow.Condition;
 import com.yomahub.liteflow.entity.flow.Executable;
 import com.yomahub.liteflow.entity.flow.Node;
 import com.yomahub.liteflow.enums.NodeTypeEnum;
+import com.yomahub.liteflow.exception.CyclicDependencyException;
 import com.yomahub.liteflow.exception.ExecutableItemNotFoundException;
 import com.yomahub.liteflow.exception.NodeTypeNotSupportException;
 import com.yomahub.liteflow.exception.ParseException;
@@ -54,68 +55,62 @@ public abstract class XmlFlowParser extends FlowParser {
 
     //xml形式的主要解析过程
     public void parseDocument(List<Document> documentList) throws Exception {
-        try {
-            //先进行Spring上下文中的节点的判断
-            for (Entry<String, NodeComponent> componentEntry : ComponentScanner.nodeComponentMap.entrySet()) {
-                if (!FlowBus.containNode(componentEntry.getKey())) {
-                    FlowBus.addSpringScanNode(componentEntry.getKey(), componentEntry.getValue());
-                }
+        //先进行Spring上下文中的节点的判断
+        for (Entry<String, NodeComponent> componentEntry : ComponentScanner.nodeComponentMap.entrySet()) {
+            if (!FlowBus.containNode(componentEntry.getKey())) {
+                FlowBus.addSpringScanNode(componentEntry.getKey(), componentEntry.getValue());
             }
+        }
 
-            for (Document document : documentList) {
-                Element rootElement = document.getRootElement();
-                Element nodesElement = rootElement.element("nodes");
-                // 当存在<nodes>节点定义时,解析node节点
-                if (ObjectUtil.isNotNull(nodesElement)){
-                    List<Element> nodeList = nodesElement.elements("node");
-                    String id, name, clazz, type, script;
-                    for (Element e : nodeList) {
-                        id = e.attributeValue("id");
-                        name = e.attributeValue("name");
-                        clazz = e.attributeValue("class");
-                        type = e.attributeValue("type");
+        for (Document document : documentList) {
+            Element rootElement = document.getRootElement();
+            Element nodesElement = rootElement.element("nodes");
+            // 当存在<nodes>节点定义时,解析node节点
+            if (ObjectUtil.isNotNull(nodesElement)){
+                List<Element> nodeList = nodesElement.elements("node");
+                String id, name, clazz, type, script;
+                for (Element e : nodeList) {
+                    id = e.attributeValue("id");
+                    name = e.attributeValue("name");
+                    clazz = e.attributeValue("class");
+                    type = e.attributeValue("type");
 
-                        //初始化type
-                        if (StrUtil.isBlank(type)){
-                            type = NodeTypeEnum.COMMON.getCode();
-                        }
-                        NodeTypeEnum nodeTypeEnum = NodeTypeEnum.getEnumByCode(type);
-                        if (ObjectUtil.isNull(nodeTypeEnum)){
-                            throw new NodeTypeNotSupportException(StrUtil.format("type [{}] is not support", type));
-                        }
+                    //初始化type
+                    if (StrUtil.isBlank(type)){
+                        type = NodeTypeEnum.COMMON.getCode();
+                    }
+                    NodeTypeEnum nodeTypeEnum = NodeTypeEnum.getEnumByCode(type);
+                    if (ObjectUtil.isNull(nodeTypeEnum)){
+                        throw new NodeTypeNotSupportException(StrUtil.format("type [{}] is not support", type));
+                    }
 
-                        //这里区分是普通java节点还是脚本节点
-                        //如果是脚本节点,又区分是普通脚本节点,还是条件脚本节点
-                        if (nodeTypeEnum.equals(NodeTypeEnum.COMMON) && StrUtil.isNotBlank(clazz)){
-                            if (!FlowBus.containNode(id)){
-                                FlowBus.addCommonNode(id, name, clazz);
-                            }
-                        }else{
-                            if (!FlowBus.containNode(id)){
-                                script = e.getTextTrim();
-                                if (nodeTypeEnum.equals(NodeTypeEnum.SCRIPT)){
-                                    FlowBus.addCommonScriptNode(id, name, script);
-                                }else if(nodeTypeEnum.equals(NodeTypeEnum.COND_SCRIPT)){
-                                    FlowBus.addCondScriptNode(id, name, script);
-                                }
+                    //这里区分是普通java节点还是脚本节点
+                    //如果是脚本节点,又区分是普通脚本节点,还是条件脚本节点
+                    if (nodeTypeEnum.equals(NodeTypeEnum.COMMON) && StrUtil.isNotBlank(clazz)){
+                        if (!FlowBus.containNode(id)){
+                            FlowBus.addCommonNode(id, name, clazz);
+                        }
+                    }else{
+                        if (!FlowBus.containNode(id)){
+                            script = e.getTextTrim();
+                            if (nodeTypeEnum.equals(NodeTypeEnum.SCRIPT)){
+                                FlowBus.addCommonScriptNode(id, name, script);
+                            }else if(nodeTypeEnum.equals(NodeTypeEnum.COND_SCRIPT)){
+                                FlowBus.addCondScriptNode(id, name, script);
                             }
                         }
                     }
                 }
+            }
 
-                // 解析chain节点
-                List<Element> chainList = rootElement.elements("chain");
-                for (Element e : chainList) {
-                    String chainName = e.attributeValue("name");
-                    if (!FlowBus.containChain(chainName)) {
-                        parseOneChain(e, documentList);
-                    }
+            // 解析chain节点
+            List<Element> chainList = rootElement.elements("chain");
+            for (Element e : chainList) {
+                String chainName = e.attributeValue("name");
+                if (!FlowBus.containChain(chainName)) {
+                    parseOneChain(e, documentList);
                 }
             }
-        } catch (Exception e) {
-            String errorMsg = "FlowParser parser exception";
-            LOG.error(errorMsg, e);
-            throw new ParseException(errorMsg);
         }
     }
 
@@ -195,18 +190,23 @@ public abstract class XmlFlowParser extends FlowParser {
     //因为chain和node都是可执行器,在一个规则文件上,有可能是node,有可能是chain
     @SuppressWarnings("unchecked")
     private boolean hasChain(List<Document> documentList, String chainName) throws Exception {
-        for (Document document : documentList) {
-            List<Element> chainList = document.getRootElement().elements("chain");
-            for (Element ce : chainList) {
-                String ceName = ce.attributeValue("name");
-                if (ceName.equals(chainName)) {
-                    if (!FlowBus.containChain(chainName)) {
-                        parseOneChain(ce, documentList);
+        try{
+            for (Document document : documentList) {
+                List<Element> chainList = document.getRootElement().elements("chain");
+                for (Element ce : chainList) {
+                    String ceName = ce.attributeValue("name");
+                    if (ceName.equals(chainName)) {
+                        if (!FlowBus.containChain(chainName)) {
+                            parseOneChain(ce, documentList);
+                        }
+                        return true;
                     }
-                    return true;
                 }
             }
+            return false;
+        }catch (StackOverflowError e){
+            LOG.error("a cyclic dependency occurs in chain", e);
+            throw new CyclicDependencyException("a cyclic dependency occurs in chain");
         }
-        return false;
     }
 }

+ 41 - 0
liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/deadLoopChain/DeadLoopChainSpringbootTest.java

@@ -0,0 +1,41 @@
+package com.yomahub.liteflow.test.deadLoopChain;
+
+import com.yomahub.liteflow.core.FlowExecutor;
+import com.yomahub.liteflow.entity.data.DefaultSlot;
+import com.yomahub.liteflow.entity.data.LiteflowResponse;
+import com.yomahub.liteflow.exception.CyclicDependencyException;
+import com.yomahub.liteflow.test.BaseTest;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import javax.annotation.Resource;
+
+
+/**
+ * 测试springboot下循环chain死循环问题
+ * @author Bryan.Zhang
+ * @since 2.5.10
+ */
+@RunWith(SpringRunner.class)
+@TestPropertySource(value = "classpath:/deadLoopChain/application.properties")
+@SpringBootTest(classes = DeadLoopChainSpringbootTest.class)
+@EnableAutoConfiguration
+@ComponentScan({"com.yomahub.liteflow.test.deadLoopChain.cmp"})
+public class DeadLoopChainSpringbootTest extends BaseTest {
+
+    @Resource
+    private FlowExecutor flowExecutor;
+
+    //死循环问题解析时自动发现,抛错
+    //为了写测试用例,才配置了liteflow.parse-on-start=false参数,实际上应用不用配置延迟加载参数
+    @Test(expected = CyclicDependencyException.class)
+    public void testDeadLoopChain() {
+        LiteflowResponse<DefaultSlot> response = flowExecutor.execute2Resp("chain1", "arg");
+    }
+}

+ 20 - 0
liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/deadLoopChain/cmp/ACmp.java

@@ -0,0 +1,20 @@
+/**
+ * <p>Title: liteflow</p>
+ * <p>Description: 轻量级的组件式流程框架</p>
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.test.deadLoopChain.cmp;
+
+import com.yomahub.liteflow.core.NodeComponent;
+import org.springframework.stereotype.Component;
+
+@Component("a")
+public class ACmp extends NodeComponent {
+
+	@Override
+	public void process() {
+		System.out.println("ACmp executed!");
+	}
+}

+ 21 - 0
liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/deadLoopChain/cmp/BCmp.java

@@ -0,0 +1,21 @@
+/**
+ * <p>Title: liteflow</p>
+ * <p>Description: 轻量级的组件式流程框架</p>
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.test.deadLoopChain.cmp;
+
+import com.yomahub.liteflow.core.NodeComponent;
+import org.springframework.stereotype.Component;
+
+@Component("b")
+public class BCmp extends NodeComponent {
+
+	@Override
+	public void process() {
+		System.out.println("BCmp executed!");
+	}
+
+}

+ 21 - 0
liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/deadLoopChain/cmp/CCmp.java

@@ -0,0 +1,21 @@
+/**
+ * <p>Title: liteflow</p>
+ * <p>Description: 轻量级的组件式流程框架</p>
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.test.deadLoopChain.cmp;
+
+import com.yomahub.liteflow.core.NodeComponent;
+import org.springframework.stereotype.Component;
+
+@Component("c")
+public class CCmp extends NodeComponent {
+
+	@Override
+	public void process() {
+		System.out.println("CCmp executed!");
+	}
+
+}

+ 2 - 0
liteflow-testcase-springboot/src/test/resources/deadLoopChain/application.properties

@@ -0,0 +1,2 @@
+liteflow.rule-source=deadLoopChain/flow.xml
+liteflow.parse-on-start=false

+ 11 - 0
liteflow-testcase-springboot/src/test/resources/deadLoopChain/flow.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<flow>
+    <chain name="chain1">
+        <then value="a,b,chain2"/>
+    </chain>
+
+    <chain name="chain2">
+        <then value="c,chain1"/>
+    </chain>
+
+</flow>