diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/core/FlowExecutor.java b/liteflow-core/src/main/java/com/yomahub/liteflow/core/FlowExecutor.java index cfd82cb56..8222cae45 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/core/FlowExecutor.java +++ b/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); diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/exception/CyclicDependencyException.java b/liteflow-core/src/main/java/com/yomahub/liteflow/exception/CyclicDependencyException.java new file mode 100644 index 000000000..95a6e676d --- /dev/null +++ b/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; + } +} diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/parser/XmlFlowParser.java b/liteflow-core/src/main/java/com/yomahub/liteflow/parser/XmlFlowParser.java index e7f039863..8a6089c24 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/parser/XmlFlowParser.java +++ b/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 documentList) throws Exception { - try { - //先进行Spring上下文中的节点的判断 - for (Entry componentEntry : ComponentScanner.nodeComponentMap.entrySet()) { - if (!FlowBus.containNode(componentEntry.getKey())) { - FlowBus.addSpringScanNode(componentEntry.getKey(), componentEntry.getValue()); - } + //先进行Spring上下文中的节点的判断 + for (Entry 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"); - // 当存在节点定义时,解析node节点 - if (ObjectUtil.isNotNull(nodesElement)){ - List 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"); + // 当存在节点定义时,解析node节点 + if (ObjectUtil.isNotNull(nodesElement)){ + List 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(); + //初始化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); } - 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); - } + }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 chainList = rootElement.elements("chain"); - for (Element e : chainList) { - String chainName = e.attributeValue("name"); - if (!FlowBus.containChain(chainName)) { - parseOneChain(e, documentList); - } + // 解析chain节点 + List 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 documentList, String chainName) throws Exception { - for (Document document : documentList) { - List 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 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; } } diff --git a/liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/deadLoopChain/DeadLoopChainSpringbootTest.java b/liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/deadLoopChain/DeadLoopChainSpringbootTest.java new file mode 100644 index 000000000..3049d1bc3 --- /dev/null +++ b/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 response = flowExecutor.execute2Resp("chain1", "arg"); + } +} diff --git a/liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/deadLoopChain/cmp/ACmp.java b/liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/deadLoopChain/cmp/ACmp.java new file mode 100644 index 000000000..3f5e940df --- /dev/null +++ b/liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/deadLoopChain/cmp/ACmp.java @@ -0,0 +1,20 @@ +/** + *

Title: liteflow

+ *

Description: 轻量级的组件式流程框架

+ * @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!"); + } +} diff --git a/liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/deadLoopChain/cmp/BCmp.java b/liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/deadLoopChain/cmp/BCmp.java new file mode 100644 index 000000000..7ae816926 --- /dev/null +++ b/liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/deadLoopChain/cmp/BCmp.java @@ -0,0 +1,21 @@ +/** + *

Title: liteflow

+ *

Description: 轻量级的组件式流程框架

+ * @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!"); + } + +} diff --git a/liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/deadLoopChain/cmp/CCmp.java b/liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/deadLoopChain/cmp/CCmp.java new file mode 100644 index 000000000..78edd7e34 --- /dev/null +++ b/liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/deadLoopChain/cmp/CCmp.java @@ -0,0 +1,21 @@ +/** + *

Title: liteflow

+ *

Description: 轻量级的组件式流程框架

+ * @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!"); + } + +} diff --git a/liteflow-testcase-springboot/src/test/resources/deadLoopChain/application.properties b/liteflow-testcase-springboot/src/test/resources/deadLoopChain/application.properties new file mode 100644 index 000000000..8e35187c2 --- /dev/null +++ b/liteflow-testcase-springboot/src/test/resources/deadLoopChain/application.properties @@ -0,0 +1,2 @@ +liteflow.rule-source=deadLoopChain/flow.xml +liteflow.parse-on-start=false \ No newline at end of file diff --git a/liteflow-testcase-springboot/src/test/resources/deadLoopChain/flow.xml b/liteflow-testcase-springboot/src/test/resources/deadLoopChain/flow.xml new file mode 100644 index 000000000..ca6e1c3f7 --- /dev/null +++ b/liteflow-testcase-springboot/src/test/resources/deadLoopChain/flow.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file