diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/LiteFlowChainELBuilder.java b/liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/LiteFlowChainELBuilder.java index 97e3de3e5..bb61ce5a4 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/LiteFlowChainELBuilder.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/LiteFlowChainELBuilder.java @@ -15,8 +15,8 @@ import com.yomahub.liteflow.exception.ELParseException; import com.yomahub.liteflow.exception.FlowSystemException; import com.yomahub.liteflow.flow.FlowBus; import com.yomahub.liteflow.flow.element.Chain; -import com.yomahub.liteflow.flow.element.Node; import com.yomahub.liteflow.flow.element.Condition; +import com.yomahub.liteflow.flow.element.Node; import com.yomahub.liteflow.log.LFLog; import com.yomahub.liteflow.log.LFLoggerManager; @@ -108,12 +108,15 @@ public class LiteFlowChainELBuilder { return this; } + /** + *

原来逻辑从 FlowBus 中获取相应的 chain,如果 EL 表达式中出现嵌套引用 chain,那么在构建 Condition 的时候可能会出现 chain 死循环引用情况

+ *

故删掉从 FlowBus 中获取的逻辑,直接使用新的 {@link LiteFlowChainELBuilder} 对象。

+ * + * @param chainId + * @return LiteFlowChainELBuilder + */ public LiteFlowChainELBuilder setChainId(String chainId) { - if (FlowBus.containChain(chainId)) { - this.chain = FlowBus.getChain(chainId); - } else { - this.chain.setChainId(chainId); - } + this.chain.setChainId(chainId); return this; } 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 830ad1025..f333a384e 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 @@ -15,9 +15,7 @@ import com.yomahub.liteflow.enums.InnerChainTypeEnum; import com.yomahub.liteflow.exception.*; import com.yomahub.liteflow.flow.FlowBus; import com.yomahub.liteflow.flow.LiteflowResponse; -import com.yomahub.liteflow.flow.element.Chain; -import com.yomahub.liteflow.flow.element.Node; -import com.yomahub.liteflow.flow.element.Rollbackable; +import com.yomahub.liteflow.flow.element.*; import com.yomahub.liteflow.flow.entity.CmpStep; import com.yomahub.liteflow.flow.id.IdGeneratorHolder; import com.yomahub.liteflow.log.LFLog; @@ -195,6 +193,9 @@ public class FlowExecutor { } } + // 检查构建生成的 chain 的有效性 + checkValidOfChain(); + // 执行钩子 if (isStart) { FlowInitHook.executeHook(); @@ -214,6 +215,56 @@ public class FlowExecutor { } } + /** + * 检查 chain 的有效性,同时重新构建 FlowBus 的 chain,将调用的子 chain 连起来 + * @throws CyclicDependencyException + */ + private void checkValidOfChain() { + // 存储已经构建完的有效的 chain 对应 Id + Set validChainIdSet = new HashSet<>(); + // 遍历所有解析的 chain + for (Chain rootChain : FlowBus.getChainMap().values()) { + // 不存在 validChainIdSet 中的 chain,说明还未检查 + if (!validChainIdSet.contains(rootChain.getChainId())) { + // 与 rootChain 相关联的 chain 的 ID + Set associatedChainIdSet = new HashSet<>(); + // 检查 chain 的有效性,是否存在死循环情况 + checkValidOfChain(rootChain, associatedChainIdSet); + // 检查完当前 chain 后,能走到这里说明当前相关的 chain 是有效的 + validChainIdSet.addAll(associatedChainIdSet); + } + } + } + + /** + * 检查 chain 的有效性 + * @param currentChain 当前遍历到的 chain 节点 + * @throws CyclicDependencyException + */ + private void checkValidOfChain(Chain currentChain, Set associatedChainIdSet) { + // 判断 completedChainIdSet 中是否已经存在对应的 chain + if (associatedChainIdSet.add(currentChain.getChainId())) { + // Set 中不存在则说明可能是父 chain 或者子 chain 未引用自身,又或者子 chain 未引用其父 chain,继续判断其子 chain + for (Condition condition : currentChain.getConditionList()) { + // 遍历所有 executable 列表 + for (Executable executable : condition.getExecutableList()) { + // 只需判断 chain,因为只有 chain 才会存在死循环依赖情况 + if (executable instanceof Chain) { + // 能执行到此处,必能从 FlowBus 中获取到对应的 chain,故无需做非空判断 + Chain childrenChain = FlowBus.getChainMap().get(executable.getId()); + // 递归检查 chain 有效性 + checkValidOfChain(childrenChain, associatedChainIdSet); + // 重新构建 chain 的 condition 列表 + ((Chain) executable).setConditionList(childrenChain.getConditionList()); + } + } + } + } else { + // chain 重复,说明子 chain 中引用了自身或其父 chain,存在死循环情况 + throw new CyclicDependencyException(StrUtil.format("There is a circular dependency in the chain[{}], please check carefully.", currentChain.getChainId())); + } + } + // 此方法就是从原有的配置源主动拉取新的进行刷新 // 和FlowBus.refreshFlowMetaData的区别就是一个为主动拉取,一个为被动监听到新的内容进行刷新 public void reloadRule() { diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/parser/helper/ParserHelper.java b/liteflow-core/src/main/java/com/yomahub/liteflow/parser/helper/ParserHelper.java index eb239f76f..89eb4177b 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/parser/helper/ParserHelper.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/parser/helper/ParserHelper.java @@ -13,7 +13,10 @@ import com.yomahub.liteflow.flow.FlowBus; import org.dom4j.Document; import org.dom4j.Element; -import java.util.*; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.Set; import java.util.function.Consumer; import java.util.regex.Pattern; @@ -237,8 +240,10 @@ public class ParserHelper { // 构建chainBuilder String chainId = Optional.ofNullable(chainNode.get(ID)).orElse(chainNode.get(NAME)).textValue(); String el = chainNode.get(VALUE).textValue(); - LiteFlowChainELBuilder chainELBuilder = LiteFlowChainELBuilder.createChain().setChainId(chainId); - chainELBuilder.setEL(el).build(); + LiteFlowChainELBuilder.createChain() + .setChainId(chainId) + .setEL(el) + .build(); } /** @@ -250,8 +255,10 @@ public class ParserHelper { String chainId = Optional.ofNullable(e.attributeValue(ID)).orElse(e.attributeValue(NAME)); String text = e.getText(); String el = RegexUtil.removeComments(text); - LiteFlowChainELBuilder chainELBuilder = LiteFlowChainELBuilder.createChain().setChainId(chainId); - chainELBuilder.setEL(el).build(); + LiteFlowChainELBuilder.createChain() + .setChainId(chainId) + .setEL(el) + .build(); } /** diff --git a/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/subflow/endlessLoop/FlowInDifferentConfigELSpringbootTest.java b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/subflow/endlessLoop/FlowInDifferentConfigELSpringbootTest.java new file mode 100644 index 000000000..331297ab3 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/subflow/endlessLoop/FlowInDifferentConfigELSpringbootTest.java @@ -0,0 +1,37 @@ +package com.yomahub.liteflow.test.subflow.endlessLoop; + +import com.yomahub.liteflow.core.FlowExecutor; +import com.yomahub.liteflow.flow.LiteflowResponse; +import com.yomahub.liteflow.test.BaseTest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +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 javax.annotation.Resource; + +/** + * 测试多文件情况下 chain 死循环逻辑 + * + * @author luo yi + * @since 2.11.0 + */ +@TestPropertySource(value = "classpath:/subflow/endlessLoop/application-subInDifferentConfig1.properties") +@SpringBootTest(classes = FlowInDifferentConfigELSpringbootTest.class) +@EnableAutoConfiguration +@ComponentScan({ "com.yomahub.liteflow.test.subflow.cmp1", "com.yomahub.liteflow.test.subflow.cmp2" }) +public class FlowInDifferentConfigELSpringbootTest extends BaseTest { + + @Resource + private FlowExecutor flowExecutor; + + // 测试 chain 死循环 + @Test + public void testChainEndlessLoop() { + LiteflowResponse response = flowExecutor.execute2Resp("chain1", "it's a request"); + Assertions.assertFalse(response.isSuccess()); + } + +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/subflow/endlessLoop/FlowJsonELSpringBootTest.java b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/subflow/endlessLoop/FlowJsonELSpringBootTest.java new file mode 100644 index 000000000..3a9a01a53 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/subflow/endlessLoop/FlowJsonELSpringBootTest.java @@ -0,0 +1,37 @@ +package com.yomahub.liteflow.test.subflow.endlessLoop; + +import com.yomahub.liteflow.core.FlowExecutor; +import com.yomahub.liteflow.flow.LiteflowResponse; +import com.yomahub.liteflow.test.BaseTest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +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 javax.annotation.Resource; + +/** + * 测试 json 文件情况下 chain 死循环逻辑 + * + * @author luo yi + * @since 2.11.0 + */ +@TestPropertySource(value = "classpath:/subflow/endlessLoop/application-json.properties") +@SpringBootTest(classes = FlowJsonELSpringBootTest.class) +@EnableAutoConfiguration +@ComponentScan({ "com.yomahub.liteflow.test.subflow.cmp1" }) +public class FlowJsonELSpringBootTest extends BaseTest { + + @Resource + private FlowExecutor flowExecutor; + + // 测试 chain 死循环 + @Test + public void testChainEndlessLoop() { + LiteflowResponse response = flowExecutor.execute2Resp("chain7", "it's a request"); + Assertions.assertFalse(response.isSuccess()); + } + +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/subflow/endlessLoop/FlowXMLELSpringBootTest.java b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/subflow/endlessLoop/FlowXMLELSpringBootTest.java new file mode 100644 index 000000000..f686e4150 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/subflow/endlessLoop/FlowXMLELSpringBootTest.java @@ -0,0 +1,37 @@ +package com.yomahub.liteflow.test.subflow.endlessLoop; + +import com.yomahub.liteflow.core.FlowExecutor; +import com.yomahub.liteflow.flow.LiteflowResponse; +import com.yomahub.liteflow.test.BaseTest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +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 javax.annotation.Resource; + +/** + * 测试 xml 文件情况下 chain 死循环逻辑 + * + * @author luo yi + * @since 2.11.0 + */ +@TestPropertySource(value = "classpath:/subflow/endlessLoop/application-xml.properties") +@SpringBootTest(classes = FlowXMLELSpringBootTest.class) +@EnableAutoConfiguration +@ComponentScan({ "com.yomahub.liteflow.test.subflow.cmp1" }) +public class FlowXMLELSpringBootTest extends BaseTest { + + @Resource + private FlowExecutor flowExecutor; + + // 测试 chain 死循环 + @Test + public void testChainEndlessLoop() { + LiteflowResponse response = flowExecutor.execute2Resp("chain1", "it's a request"); + Assertions.assertFalse(response.isSuccess()); + } + +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/subflow/endlessLoop/FlowYmlELSpringBootTest.java b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/subflow/endlessLoop/FlowYmlELSpringBootTest.java new file mode 100644 index 000000000..b0829cb26 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/subflow/endlessLoop/FlowYmlELSpringBootTest.java @@ -0,0 +1,37 @@ +package com.yomahub.liteflow.test.subflow.endlessLoop; + +import com.yomahub.liteflow.core.FlowExecutor; +import com.yomahub.liteflow.flow.LiteflowResponse; +import com.yomahub.liteflow.test.BaseTest; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +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 javax.annotation.Resource; + +/** + * 测试 yml 文件情况下 chain 死循环逻辑 + * + * @author luo yi + * @since 2.11.0 + */ +@TestPropertySource(value = "classpath:/subflow/endlessLoop/application-yml.properties") +@SpringBootTest(classes = FlowYmlELSpringBootTest.class) +@EnableAutoConfiguration +@ComponentScan({ "com.yomahub.liteflow.test.subflow.cmp1" }) +public class FlowYmlELSpringBootTest extends BaseTest { + + @Resource + private FlowExecutor flowExecutor; + + // 测试 chain 死循环 + @Test + public void testChainEndlessLoop() { + LiteflowResponse response = flowExecutor.execute2Resp("chain5", "it's a request"); + Assertions.assertFalse(response.isSuccess()); + } + +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/subflow/endlessLoop/application-json.properties b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/subflow/endlessLoop/application-json.properties new file mode 100644 index 000000000..f19fd93f5 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/subflow/endlessLoop/application-json.properties @@ -0,0 +1 @@ +liteflow.rule-source=subflow/endlessLoop/flow.el.json \ No newline at end of file diff --git a/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/subflow/endlessLoop/application-subInDifferentConfig1.properties b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/subflow/endlessLoop/application-subInDifferentConfig1.properties new file mode 100644 index 000000000..418d38274 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/subflow/endlessLoop/application-subInDifferentConfig1.properties @@ -0,0 +1,2 @@ +liteflow.rule-source=subflow/endlessLoop/flow-sub1.el.xml,subflow/endlessLoop/flow-sub2.el.yml +liteflow.support-multiple-type=true \ No newline at end of file diff --git a/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/subflow/endlessLoop/application-xml.properties b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/subflow/endlessLoop/application-xml.properties new file mode 100644 index 000000000..d2261bca5 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/subflow/endlessLoop/application-xml.properties @@ -0,0 +1 @@ +liteflow.rule-source=subflow/endlessLoop/flow.el.xml \ No newline at end of file diff --git a/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/subflow/endlessLoop/application-yml.properties b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/subflow/endlessLoop/application-yml.properties new file mode 100644 index 000000000..a9088ce2e --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/subflow/endlessLoop/application-yml.properties @@ -0,0 +1 @@ +liteflow.rule-source=subflow/endlessLoop/flow.el.yml \ No newline at end of file diff --git a/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/subflow/endlessLoop/flow-sub1.el.xml b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/subflow/endlessLoop/flow-sub1.el.xml new file mode 100644 index 000000000..3f1a585ab --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/subflow/endlessLoop/flow-sub1.el.xml @@ -0,0 +1,6 @@ + + + + THEN(a, b); + + \ No newline at end of file diff --git a/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/subflow/endlessLoop/flow-sub2.el.yml b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/subflow/endlessLoop/flow-sub2.el.yml new file mode 100644 index 000000000..6861be3cc --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/subflow/endlessLoop/flow-sub2.el.yml @@ -0,0 +1,6 @@ +flow: + chain: + - name: chain2 + value: "THEN(c, d, chain3);" + - name: chain3 + value: "THEN(a, chain2);" \ No newline at end of file diff --git a/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/subflow/endlessLoop/flow.el.json b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/subflow/endlessLoop/flow.el.json new file mode 100644 index 000000000..1a54d6c6c --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/subflow/endlessLoop/flow.el.json @@ -0,0 +1,18 @@ +{ + "flow": { + "chain": [ + { + "name": "chain7", + "value": "THEN(a, chain8);" + }, + { + "name": "chain8", + "value": "THEN(b, chain9);" + }, + { + "name": "chain9", + "value": "WHEN(c, chain7);" + } + ] + } +} \ No newline at end of file diff --git a/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/subflow/endlessLoop/flow.el.xml b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/subflow/endlessLoop/flow.el.xml new file mode 100644 index 000000000..d615bf81d --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/subflow/endlessLoop/flow.el.xml @@ -0,0 +1,16 @@ + + + + + THEN(a, chain2); + + + + THEN(b, chain3); + + + + THEN(c, chain1); + + + \ No newline at end of file diff --git a/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/subflow/endlessLoop/flow.el.yml b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/subflow/endlessLoop/flow.el.yml new file mode 100644 index 000000000..1699d501b --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/subflow/endlessLoop/flow.el.yml @@ -0,0 +1,8 @@ +flow: + chain: + - name: chain4 + value: "THEN(a, chain5);" + - name: chain5 + value: "THEN(b, chain6);" + - name: chain6 + value: "THEN(c, chain5);" \ No newline at end of file