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