Jelajahi Sumber

!237 [ISSUE #I7YYLF] 增加组件降级特性
Merge pull request !237 from DaleLee/issue/#I7YYLF

铂赛东 1 tahun lalu
induk
melakukan
2b481fb983
100 mengubah file dengan 2745 tambahan dan 414 penghapusan
  1. 23 0
      liteflow-core/src/main/java/com/yomahub/liteflow/annotation/FallbackCmp.java
  2. 1 1
      liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/operator/ForOperator.java
  3. 1 1
      liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/operator/IteratorOperator.java
  4. 21 38
      liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/operator/NodeOperator.java
  5. 1 1
      liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/operator/SwitchOperator.java
  6. 2 1
      liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/operator/base/OperatorHelper.java
  7. 2 0
      liteflow-core/src/main/java/com/yomahub/liteflow/enums/NodeTypeEnum.java
  8. 31 0
      liteflow-core/src/main/java/com/yomahub/liteflow/exception/FallbackCmpNotFoundException.java
  9. 31 3
      liteflow-core/src/main/java/com/yomahub/liteflow/flow/FlowBus.java
  10. 8 3
      liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/Condition.java
  11. 188 0
      liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/FallbackNodeProxy.java
  12. 15 11
      liteflow-core/src/main/java/com/yomahub/liteflow/property/LiteflowConfig.java
  13. 18 1
      liteflow-core/src/main/java/com/yomahub/liteflow/slot/Slot.java
  14. 1 1
      liteflow-solon-plugin/src/main/java/com/yomahub/liteflow/solon/config/LiteflowAutoConfiguration.java
  15. 11 11
      liteflow-solon-plugin/src/main/java/com/yomahub/liteflow/solon/config/LiteflowProperty.java
  16. 1 1
      liteflow-solon-plugin/src/main/resources/META-INF/liteflow-default.properties
  17. 11 11
      liteflow-spring-boot-starter/src/main/java/com/yomahub/liteflow/springboot/LiteflowProperty.java
  18. 1 1
      liteflow-spring-boot-starter/src/main/java/com/yomahub/liteflow/springboot/config/LiteflowPropertyAutoConfiguration.java
  19. 7 7
      liteflow-spring-boot-starter/src/main/resources/META-INF/additional-spring-configuration-metadata.json
  20. 225 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/fallback/FallbackELDeclSpringbootTest.java
  21. 16 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/ACmp.java
  22. 16 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/BCmp.java
  23. 20 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/BreakCmp.java
  24. 18 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/CCmp.java
  25. 15 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/DCmp.java
  26. 20 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/ForCmp.java
  27. 18 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/IfCmp1.java
  28. 20 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/IfCmp2.java
  29. 21 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/IteratorCmp1.java
  30. 23 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/IteratorCmp2.java
  31. 18 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/SwitchCmp1.java
  32. 20 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/SwitchCmp2.java
  33. 31 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/WhileCmp1.java
  34. 20 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/WhileCmp2.java
  35. 2 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/resources/fallback/application.properties
  36. 136 0
      liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/resources/fallback/flow.el.xml
  37. 225 0
      liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/fallback/FallbackTest.java
  38. 12 0
      liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/fallback/cmp/ACmp.java
  39. 12 0
      liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/fallback/cmp/BCmp.java
  40. 13 0
      liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/fallback/cmp/BreakCmp.java
  41. 14 0
      liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/fallback/cmp/CCmp.java
  42. 11 0
      liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/fallback/cmp/DCmp.java
  43. 13 0
      liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/fallback/cmp/ForCmp.java
  44. 11 0
      liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/fallback/cmp/IfCmp1.java
  45. 13 0
      liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/fallback/cmp/IfCmp2.java
  46. 14 0
      liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/fallback/cmp/IteratorCmp1.java
  47. 16 0
      liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/fallback/cmp/IteratorCmp2.java
  48. 11 0
      liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/fallback/cmp/SwitchCmp1.java
  49. 13 0
      liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/fallback/cmp/SwitchCmp2.java
  50. 24 0
      liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/fallback/cmp/WhileCmp1.java
  51. 13 0
      liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/fallback/cmp/WhileCmp2.java
  52. 153 0
      liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/resources/fallback/flow.el.xml
  53. 222 0
      liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/fallback/FallbackELSolonTest.java
  54. 14 0
      liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/fallback/cmp/ACmp.java
  55. 14 0
      liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/fallback/cmp/BCmp.java
  56. 15 0
      liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/fallback/cmp/BreakCmp.java
  57. 16 0
      liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/fallback/cmp/CCmp.java
  58. 13 0
      liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/fallback/cmp/DCmp.java
  59. 15 0
      liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/fallback/cmp/ForCmp.java
  60. 13 0
      liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/fallback/cmp/IfCmp1.java
  61. 15 0
      liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/fallback/cmp/IfCmp2.java
  62. 16 0
      liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/fallback/cmp/IteratorCmp1.java
  63. 18 0
      liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/fallback/cmp/IteratorCmp2.java
  64. 13 0
      liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/fallback/cmp/SwitchCmp1.java
  65. 15 0
      liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/fallback/cmp/SwitchCmp2.java
  66. 26 0
      liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/fallback/cmp/WhileCmp1.java
  67. 15 0
      liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/fallback/cmp/WhileCmp2.java
  68. 0 46
      liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/substituteNode/SubstituteSpringbootTest.java
  69. 0 21
      liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/substituteNode/cmp/ACmp.java
  70. 0 21
      liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/substituteNode/cmp/BCmp.java
  71. 0 21
      liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/substituteNode/cmp/CCmp.java
  72. 0 21
      liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/substituteNode/cmp/DCmp.java
  73. 0 21
      liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/substituteNode/cmp/SubCmp.java
  74. 2 0
      liteflow-testcase-el/liteflow-testcase-el-solon/src/test/resources/fallback/application.properties
  75. 136 0
      liteflow-testcase-el/liteflow-testcase-el-solon/src/test/resources/fallback/flow.el.xml
  76. 0 2
      liteflow-testcase-el/liteflow-testcase-el-solon/src/test/resources/substituteNode/application.properties
  77. 0 14
      liteflow-testcase-el/liteflow-testcase-el-solon/src/test/resources/substituteNode/flow.el.xml
  78. 225 0
      liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/FallbackELSpringbootTest.java
  79. 14 0
      liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/ACmp.java
  80. 17 0
      liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/BCmp.java
  81. 15 0
      liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/BreakCmp.java
  82. 18 0
      liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/CCmp.java
  83. 13 0
      liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/DCmp.java
  84. 15 0
      liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/ForCmp.java
  85. 13 0
      liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/IfCmp1.java
  86. 15 0
      liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/IfCmp2.java
  87. 16 0
      liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/IteratorCmp1.java
  88. 18 0
      liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/IteratorCmp2.java
  89. 13 0
      liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/SwitchCmp1.java
  90. 15 0
      liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/SwitchCmp2.java
  91. 26 0
      liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/WhileCmp1.java
  92. 15 0
      liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/WhileCmp2.java
  93. 0 50
      liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/substituteNode/SubstituteSpringbootTest.java
  94. 0 21
      liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/substituteNode/cmp/ACmp.java
  95. 0 21
      liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/substituteNode/cmp/BCmp.java
  96. 0 21
      liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/substituteNode/cmp/CCmp.java
  97. 0 21
      liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/substituteNode/cmp/DCmp.java
  98. 0 21
      liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/substituteNode/cmp/SubCmp.java
  99. 2 0
      liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/fallback/application.properties
  100. 136 0
      liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/fallback/flow.el.xml

+ 23 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/annotation/FallbackCmp.java

@@ -0,0 +1,23 @@
+package com.yomahub.liteflow.annotation;
+
+import com.yomahub.liteflow.enums.NodeTypeEnum;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 降级组件
+ *
+ * @author DaleLee
+ * @since 2.11.1
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+public @interface FallbackCmp {
+}

+ 1 - 1
liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/operator/ForOperator.java

@@ -27,7 +27,7 @@ public class ForOperator extends BaseOperator<ForCondition> {
 		Node node;
 		if (objects[0] instanceof Node) {
 			node = OperatorHelper.convert(objects[0], Node.class);
-			if (!ListUtil.toList(NodeTypeEnum.FOR, NodeTypeEnum.FOR_SCRIPT).contains(node.getType())) {
+			if (!ListUtil.toList(NodeTypeEnum.FOR, NodeTypeEnum.FOR_SCRIPT, NodeTypeEnum.FALLBACK).contains(node.getType())) {
 				throw new QLException("The parameter must be for-node item");
 			}
 		}

+ 1 - 1
liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/operator/IteratorOperator.java

@@ -15,7 +15,7 @@ public class IteratorOperator extends BaseOperator<IteratorCondition> {
 		OperatorHelper.checkObjectSizeEq(objects, 1);
 
 		Node node = OperatorHelper.convert(objects[0], Node.class);
-		if (!ListUtil.toList(NodeTypeEnum.ITERATOR).contains(node.getType())) {
+		if (!ListUtil.toList(NodeTypeEnum.ITERATOR, NodeTypeEnum.FALLBACK).contains(node.getType())) {
 			throw new QLException("The parameter must be iterator-node item");
 		}
 

+ 21 - 38
liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/operator/NodeOperator.java

@@ -1,11 +1,10 @@
 package com.yomahub.liteflow.builder.el.operator;
 
-import cn.hutool.core.util.ObjectUtil;
-import cn.hutool.core.util.StrUtil;
-import com.ql.util.express.exception.QLException;
 import com.yomahub.liteflow.builder.el.operator.base.BaseOperator;
 import com.yomahub.liteflow.builder.el.operator.base.OperatorHelper;
+import com.yomahub.liteflow.exception.ELParseException;
 import com.yomahub.liteflow.flow.FlowBus;
+import com.yomahub.liteflow.flow.element.FallbackNodeProxy;
 import com.yomahub.liteflow.flow.element.Node;
 import com.yomahub.liteflow.property.LiteflowConfig;
 import com.yomahub.liteflow.property.LiteflowConfigGetter;
@@ -14,45 +13,29 @@ import com.yomahub.liteflow.property.LiteflowConfigGetter;
  * EL规则中的node的操作符
  *
  * @author Bryan.Zhang
+ * @author DaleLee
  * @since 2.8.3
  */
 public class NodeOperator extends BaseOperator<Node> {
 
-	@Override
-	public Node build(Object[] objects) throws Exception {
-		OperatorHelper.checkObjectSizeEqOne(objects);
+    @Override
+    public Node build(Object[] objects) throws Exception {
+        OperatorHelper.checkObjectSizeEqOne(objects);
+        String nodeId = OperatorHelper.convert(objects[0], String.class);
 
-		String nodeId = OperatorHelper.convert(objects[0], String.class);
-
-		if (FlowBus.containNode(nodeId)) {
-			return FlowBus.getNode(nodeId);
-		}
-		else {
-			LiteflowConfig liteflowConfig = LiteflowConfigGetter.get();
-			if (StrUtil.isNotBlank(liteflowConfig.getSubstituteCmpClass())) {
-				Node substituteNode = FlowBus.getNodeMap()
-					.values()
-					.stream()
-					.filter(node -> node.getInstance()
-						.getClass()
-						.getName()
-						.equals(liteflowConfig.getSubstituteCmpClass()))
-					.findFirst()
-					.orElse(null);
-				if (ObjectUtil.isNotNull(substituteNode)) {
-					return substituteNode;
-				}
-				else {
-					String error = StrUtil.format("This node[{}] cannot be found", nodeId);
-					throw new QLException(error);
-				}
-			}
-			else {
-				String error = StrUtil.format("This node[{}] cannot be found, or you can configure an substitute node",
-						nodeId);
-				throw new QLException(error);
-			}
-		}
-	}
+        if (FlowBus.containNode(nodeId)) {
+            // 找到对应节点
+            return FlowBus.getNode(nodeId);
+        } else {
+            // 检查是否开启了组件降级功能
+            LiteflowConfig liteflowConfig = LiteflowConfigGetter.get();
+            Boolean enable = liteflowConfig.getFallbackCmpEnable();
+            if (!enable) {
+                throw new ELParseException("The fallback component is disabled");
+            }
+            // 生成代理节点
+            return new FallbackNodeProxy(nodeId);
+        }
+    }
 
 }

+ 1 - 1
liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/operator/SwitchOperator.java

@@ -21,7 +21,7 @@ public class SwitchOperator extends BaseOperator<SwitchCondition> {
 		OperatorHelper.checkObjectSizeEqOne(objects);
 
 		Node switchNode = OperatorHelper.convert(objects[0], Node.class);
-		if (!ListUtil.toList(NodeTypeEnum.SWITCH, NodeTypeEnum.SWITCH_SCRIPT).contains(switchNode.getType())) {
+		if (!ListUtil.toList(NodeTypeEnum.SWITCH, NodeTypeEnum.SWITCH_SCRIPT, NodeTypeEnum.FALLBACK).contains(switchNode.getType())) {
 			throw new QLException("The caller must be Switch item");
 		}
 

+ 2 - 1
liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/operator/base/OperatorHelper.java

@@ -145,7 +145,8 @@ public class OperatorHelper {
 		if (!(object instanceof Node && ListUtil.toList(
 				NodeTypeEnum.IF, NodeTypeEnum.IF_SCRIPT,
 				NodeTypeEnum.WHILE, NodeTypeEnum.WHILE_SCRIPT,
-				NodeTypeEnum.BREAK, NodeTypeEnum.BREAK_SCRIPT).contains(((Node) object).getType())
+				NodeTypeEnum.BREAK, NodeTypeEnum.BREAK_SCRIPT, NodeTypeEnum.FALLBACK)
+				.contains(((Node) object).getType())
 				|| object instanceof AndOrCondition || object instanceof NotCondition)) {
 			throw new QLException("The first parameter must be boolean type Node or boolean type condition");
 		}

+ 2 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/enums/NodeTypeEnum.java

@@ -34,6 +34,8 @@ public enum NodeTypeEnum {
 	BREAK("break", "循环跳出", false, NodeBreakComponent.class),
 
 	ITERATOR("iterator", "循环迭代", false, NodeIteratorComponent.class),
+	
+	FALLBACK("fallback", "降级", false, null),
 
 	SCRIPT("script", "脚本", true, ScriptCommonComponent.class),
 

+ 31 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/exception/FallbackCmpNotFoundException.java

@@ -0,0 +1,31 @@
+package com.yomahub.liteflow.exception;
+
+/**
+ * 没有找到降级组件异常
+ *
+ * @author DaleLee
+ * @since 2.11.1
+ */
+public class FallbackCmpNotFoundException extends RuntimeException {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 异常信息
+     */
+    private String message;
+
+    public FallbackCmpNotFoundException(String message) {
+        this.message = message;
+    }
+
+    @Override
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+}

+ 31 - 3
liteflow-core/src/main/java/com/yomahub/liteflow/flow/FlowBus.java

@@ -11,7 +11,11 @@ package com.yomahub.liteflow.flow;
 import cn.hutool.core.collection.ListUtil;
 import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.StrUtil;
-import com.yomahub.liteflow.core.*;
+import com.yomahub.liteflow.annotation.FallbackCmp;
+import com.yomahub.liteflow.annotation.util.AnnoUtil;
+import com.yomahub.liteflow.core.ComponentInitializer;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.core.ScriptComponent;
 import com.yomahub.liteflow.enums.FlowParserTypeEnum;
 import com.yomahub.liteflow.enums.NodeTypeEnum;
 import com.yomahub.liteflow.exception.ComponentCannotRegisterException;
@@ -31,6 +35,7 @@ import com.yomahub.liteflow.spi.holder.ContextAwareHolder;
 import com.yomahub.liteflow.spi.local.LocalContextAware;
 import com.yomahub.liteflow.util.CopyOnWriteHashMap;
 import com.yomahub.liteflow.util.LiteFlowProxyUtil;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -41,6 +46,7 @@ import java.util.stream.Collectors;
  * 流程元数据类
  *
  * @author Bryan.Zhang
+ * @author DaleLee
  */
 public class FlowBus {
 
@@ -50,6 +56,8 @@ public class FlowBus {
 
 	private static final Map<String, Node> nodeMap = new CopyOnWriteHashMap<>();
 
+	private static final Map<NodeTypeEnum, Node> fallbackNodeMap = new CopyOnWriteHashMap<>();
+
 	private FlowBus() {
 	}
 
@@ -92,8 +100,10 @@ public class FlowBus {
 			throw new NullNodeTypeException(StrUtil.format("node type is null for node[{}]", nodeId));
 		}
 
-		nodeMap.put(nodeId,
-				new Node(ComponentInitializer.loadInstance().initComponent(nodeComponent, type, nodeComponent.getName(), nodeId)));
+		Node node = new Node(ComponentInitializer.loadInstance()
+				.initComponent(nodeComponent, type, nodeComponent.getName(), nodeId));
+		nodeMap.put(nodeId, node);
+		addFallbackNode(node);
 	}
 
 	/**
@@ -203,6 +213,7 @@ public class FlowBus {
 
 				String activeNodeId = StrUtil.isEmpty(cmpInstance.getNodeId()) ? nodeId : cmpInstance.getNodeId();
 				nodeMap.put(activeNodeId, node);
+				addFallbackNode(node);
 			}
 
 		}
@@ -226,9 +237,14 @@ public class FlowBus {
 		return chainMap;
 	}
 
+	public static Node getFallBackNode(NodeTypeEnum nodeType) {
+		return fallbackNodeMap.get(nodeType);
+	}
+
 	public static void cleanCache() {
 		chainMap.clear();
 		nodeMap.clear();
+		fallbackNodeMap.clear();
 		cleanScriptCache();
 	}
 
@@ -269,4 +285,16 @@ public class FlowBus {
 		Arrays.stream(chainIds).forEach(FlowBus::removeChain);
 	}
 
+	// 判断是否是降级组件,如果是则添加到 fallbackNodeMap
+	private static void addFallbackNode(Node node) {
+		NodeComponent nodeComponent = node.getInstance();
+		FallbackCmp fallbackCmp = AnnoUtil.getAnnotation(nodeComponent.getClass(), FallbackCmp.class);
+		if (fallbackCmp == null) {
+			return;
+		}
+
+		NodeTypeEnum nodeType = node.getType();
+		fallbackNodeMap.put(nodeType, node);
+	}
+
 }

+ 8 - 3
liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/Condition.java

@@ -10,10 +10,9 @@ package com.yomahub.liteflow.flow.element;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.ListUtil;
 import cn.hutool.core.util.ObjectUtil;
+import com.yomahub.liteflow.enums.ConditionTypeEnum;
 import com.yomahub.liteflow.enums.ExecuteTypeEnum;
 import com.yomahub.liteflow.exception.ChainEndException;
-import com.yomahub.liteflow.flow.element.Executable;
-import com.yomahub.liteflow.enums.ConditionTypeEnum;
 import com.yomahub.liteflow.flow.element.condition.ConditionKey;
 import com.yomahub.liteflow.slot.DataBus;
 import com.yomahub.liteflow.slot.Slot;
@@ -27,6 +26,7 @@ import java.util.Map;
  * Condition的抽象类
  *
  * @author Bryan.Zhang
+ * @author DaleLee
  */
 public abstract class Condition implements Executable{
 
@@ -46,7 +46,10 @@ public abstract class Condition implements Executable{
 
 	@Override
 	public void execute(Integer slotIndex) throws Exception {
+		Slot slot = DataBus.getSlot(slotIndex);
 		try {
+			// 当前 Condition 入栈
+			slot.pushCondition(this);
 			executeCondition(slotIndex);
 		}
 		catch (ChainEndException e) {
@@ -55,7 +58,6 @@ public abstract class Condition implements Executable{
 			throw e;
 		}
 		catch (Exception e) {
-			Slot slot = DataBus.getSlot(slotIndex);
 			String chainId = this.getCurrChainId();
 			// 这里事先取到exception set到slot里,为了方便finally取到exception
 			if (slot.isSubChain(chainId)) {
@@ -65,6 +67,9 @@ public abstract class Condition implements Executable{
 				slot.setException(e);
 			}
 			throw e;
+		} finally {
+			// 当前 Condition 出栈
+			slot.popCondition();
 		}
 	}
 

+ 188 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/FallbackNodeProxy.java

@@ -0,0 +1,188 @@
+package com.yomahub.liteflow.flow.element;
+
+import cn.hutool.core.text.StrFormatter;
+import cn.hutool.core.util.ObjectUtil;
+import com.yomahub.liteflow.enums.ConditionTypeEnum;
+import com.yomahub.liteflow.enums.NodeTypeEnum;
+import com.yomahub.liteflow.exception.FallbackCmpNotFoundException;
+import com.yomahub.liteflow.exception.FlowSystemException;
+import com.yomahub.liteflow.flow.FlowBus;
+import com.yomahub.liteflow.flow.element.condition.ConditionKey;
+import com.yomahub.liteflow.flow.element.condition.ForCondition;
+import com.yomahub.liteflow.flow.element.condition.IfCondition;
+import com.yomahub.liteflow.flow.element.condition.IteratorCondition;
+import com.yomahub.liteflow.flow.element.condition.LoopCondition;
+import com.yomahub.liteflow.flow.element.condition.SwitchCondition;
+import com.yomahub.liteflow.flow.element.condition.WhileCondition;
+import com.yomahub.liteflow.slot.DataBus;
+import com.yomahub.liteflow.slot.Slot;
+
+/**
+ * 降级组件代理
+ *
+ * @author DaleLee
+ * @since 2.11.1
+ */
+public class FallbackNodeProxy extends Node {
+
+    // 原节点 id
+    private String expectedNodeId;
+
+    // 降级节点
+    private Node fallbackNode;
+
+    public FallbackNodeProxy() {
+        this.setType(NodeTypeEnum.FALLBACK);
+    }
+
+    public FallbackNodeProxy(String expectedNodeId) {
+        this();
+        this.expectedNodeId = expectedNodeId;
+    }
+
+    @Override
+    public void execute(Integer slotIndex) throws Exception {
+        loadFallBackNode(slotIndex);
+        this.fallbackNode.setCurrChainId(this.getCurrChainId());
+        this.fallbackNode.execute(slotIndex);
+    }
+
+    private void loadFallBackNode(Integer slotIndex) throws Exception {
+        if (ObjectUtil.isNotNull(this.fallbackNode)) {
+            // 已经加载过了
+            return;
+        }
+        Slot slot = DataBus.getSlot(slotIndex);
+        Condition curCondition = slot.getCurrentCondition();
+        if (ObjectUtil.isNull(curCondition)) {
+            throw new FlowSystemException("The current executing condition could not be found.");
+        }
+        Node node = findFallbackNode(curCondition);
+        if (ObjectUtil.isNull(node)) {
+            throw new FallbackCmpNotFoundException(
+                    StrFormatter.format("No fallback component found for [{}] in chain[{}].", this.expectedNodeId,
+                            this.getCurrChainId()));
+        }
+        // 使用 node 的副本
+        this.fallbackNode = node.copy();
+    }
+
+    private Node findFallbackNode(Condition condition) {
+        ConditionTypeEnum conditionType = condition.getConditionType();
+        switch (conditionType) {
+            case TYPE_THEN:
+            case TYPE_WHEN:
+            case TYPE_PRE:
+            case TYPE_FINALLY:
+            case TYPE_CATCH:
+                return FlowBus.getFallBackNode(NodeTypeEnum.COMMON);
+            case TYPE_IF:
+                return findNodeInIf((IfCondition) condition);
+            case TYPE_SWITCH:
+                return findNodeInSwitch((SwitchCondition) condition);
+            case TYPE_FOR:
+                return findNodeInFor((ForCondition) condition);
+            case TYPE_WHILE:
+                return findNodeInWhile((WhileCondition) condition);
+            case TYPE_ITERATOR:
+                return findNodeInIterator((IteratorCondition) condition);
+            case TYPE_NOT_OPT:
+            case TYPE_AND_OR_OPT:
+                return FlowBus.getFallBackNode(NodeTypeEnum.IF);
+            default:
+                return null;
+        }
+    }
+
+    private Node findNodeInIf(IfCondition ifCondition) {
+        Executable ifItem = ifCondition.getIfItem();
+        if (ifItem == this) {
+            // 需要条件组件
+            return FlowBus.getFallBackNode(NodeTypeEnum.IF);
+        }
+
+        // 需要普通组件
+        return FlowBus.getFallBackNode(NodeTypeEnum.COMMON);
+    }
+
+    private Node findNodeInSwitch(SwitchCondition switchCondition) {
+        Node switchNode = switchCondition.getSwitchNode();
+        if (switchNode == this) {
+            return FlowBus.getFallBackNode(NodeTypeEnum.SWITCH);
+        }
+
+        return FlowBus.getFallBackNode(NodeTypeEnum.COMMON);
+    }
+
+    private Node findNodeInFor(ForCondition forCondition) {
+        Node forNode = forCondition.getForNode();
+        if (forNode == this) {
+            return FlowBus.getFallBackNode(NodeTypeEnum.FOR);
+        }
+
+        return findNodeInLoop(forCondition);
+    }
+
+    private Node findNodeInWhile(WhileCondition whileCondition) {
+        Executable whileItem = whileCondition.getWhileItem();
+        if (whileItem == this) {
+            return FlowBus.getFallBackNode(NodeTypeEnum.WHILE);
+        }
+
+        return findNodeInLoop(whileCondition);
+    }
+
+    private Node findNodeInIterator(IteratorCondition iteratorCondition) {
+        Node iteratorNode = iteratorCondition.getIteratorNode();
+        if (iteratorNode == this) {
+            return FlowBus.getFallBackNode(NodeTypeEnum.ITERATOR);
+        }
+
+        return findNodeInLoop(iteratorCondition);
+    }
+
+    private Node findNodeInLoop(LoopCondition loopCondition) {
+        Executable breakItem = loopCondition.getExecutableOne(ConditionKey.BREAK_KEY);
+        if (breakItem == this) {
+            return FlowBus.getFallBackNode(NodeTypeEnum.BREAK);
+        }
+
+        return FlowBus.getFallBackNode(NodeTypeEnum.COMMON);
+    }
+
+    @Override
+    public <T> T getItemResultMetaValue(Integer slotIndex) {
+        return this.fallbackNode.getItemResultMetaValue(slotIndex);
+    }
+
+    @Override
+    public boolean isAccess(Integer slotIndex) throws Exception {
+        // 可能会先访问这个方法,所以在这里就要加载降级节点
+        loadFallBackNode(slotIndex);
+        return this.fallbackNode.isAccess(slotIndex);
+    }
+
+    @Override
+    public String getId() {
+        return this.fallbackNode == null ? null : this.fallbackNode.getId();
+    }
+
+    @Override
+    public Node copy() {
+        // 代理节点不复制
+        return this;
+    }
+
+    @Override
+    public NodeTypeEnum getType() {
+        return NodeTypeEnum.FALLBACK;
+    }
+
+    public String getExpectedNodeId() {
+        return expectedNodeId;
+    }
+
+    public void setExpectedNodeId(String expectedNodeId) {
+        this.expectedNodeId = expectedNodeId;
+    }
+}

+ 15 - 11
liteflow-core/src/main/java/com/yomahub/liteflow/property/LiteflowConfig.java

@@ -97,9 +97,6 @@ public class LiteflowConfig {
 	// 是否打印执行中的日志
 	private Boolean printExecutionLog;
 
-	// 替补组件class路径
-	private String substituteCmpClass;
-
 	// 规则文件/脚本文件变更监听
 	private Boolean enableMonitorFile = Boolean.FALSE;
 
@@ -111,6 +108,9 @@ public class LiteflowConfig {
 
 	//使用默认并行循环线程池时,最大队列数
 	private Integer parallelQueueLimit;
+	
+	// 是否启用组件降级
+	private Boolean fallbackCmpEnable;
 
 	public Boolean getEnableMonitorFile() {
 		return enableMonitorFile;
@@ -373,14 +373,6 @@ public class LiteflowConfig {
 		this.printExecutionLog = printExecutionLog;
 	}
 
-	public String getSubstituteCmpClass() {
-		return substituteCmpClass;
-	}
-
-	public void setSubstituteCmpClass(String substituteCmpClass) {
-		this.substituteCmpClass = substituteCmpClass;
-	}
-
 	public String getRuleSourceExtData() {
 		return ruleSourceExtData;
 	}
@@ -454,4 +446,16 @@ public class LiteflowConfig {
 	public void setParallelLoopExecutorClass(String parallelLoopExecutorClass) {
 		this.parallelLoopExecutorClass = parallelLoopExecutorClass;
 	}
+	
+	public Boolean getFallbackCmpEnable() {
+		if (ObjectUtil.isNull(this.fallbackCmpEnable)) {
+			return false;
+		} else {
+			return fallbackCmpEnable;
+		}
+	}
+	
+	public void setFallbackCmpEnable(Boolean fallbackCmpEnable) {
+		this.fallbackCmpEnable = fallbackCmpEnable;
+	}
 }

+ 18 - 1
liteflow-core/src/main/java/com/yomahub/liteflow/slot/Slot.java

@@ -13,26 +13,29 @@ import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import com.yomahub.liteflow.exception.NoSuchContextBeanException;
 import com.yomahub.liteflow.exception.NullParamException;
+import com.yomahub.liteflow.flow.element.Condition;
 import com.yomahub.liteflow.flow.entity.CmpStep;
 import com.yomahub.liteflow.flow.id.IdGeneratorHolder;
 import com.yomahub.liteflow.log.LFLog;
 import com.yomahub.liteflow.log.LFLoggerManager;
 import com.yomahub.liteflow.property.LiteflowConfigGetter;
+
 import java.util.Deque;
 import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Queue;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentLinkedDeque;
 import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.function.Consumer;
 
 /**
  * Slot的抽象类实现
  *
  * @author Bryan.Zhang
  * @author LeoLee
+ * @author DaleLee
  */
 @SuppressWarnings("unchecked")
 public class Slot {
@@ -88,6 +91,8 @@ public class Slot {
 	protected ConcurrentHashMap<String, Object> metaDataMap = new ConcurrentHashMap<>();
 
 	private List<Object> contextBeanList;
+	
+	private static final ThreadLocal<Deque<Condition>> conditionStack = ThreadLocal.withInitial(LinkedList::new);
 
 	public Slot() {
 	}
@@ -287,6 +292,18 @@ public class Slot {
 	public Iterator<?> getIteratorResult(String key) {
 		return getThreadMetaData(ITERATOR_PREFIX + key);
 	}
+	
+	public Condition getCurrentCondition() {
+		return conditionStack.get().peek();
+	}
+	
+	public void pushCondition(Condition condition) {
+		conditionStack.get().push(condition);
+	}
+	
+	public void popCondition() {
+		conditionStack.get().pop();
+	}
 
 	/**
 	 * @deprecated 请使用 {@link #setChainId(String)}

+ 1 - 1
liteflow-solon-plugin/src/main/java/com/yomahub/liteflow/solon/config/LiteflowAutoConfiguration.java

@@ -46,10 +46,10 @@ public class LiteflowAutoConfiguration {
 		liteflowConfig.setMainExecutorWorks(property.getMainExecutorWorks());
 		liteflowConfig.setMainExecutorClass(property.getMainExecutorClass());
 		liteflowConfig.setPrintExecutionLog(property.isPrintExecutionLog());
-		liteflowConfig.setSubstituteCmpClass(property.getSubstituteCmpClass());
 		liteflowConfig.setParallelMaxWorkers(property.getParallelMaxWorkers());
 		liteflowConfig.setParallelQueueLimit(property.getParallelQueueLimit());
 		liteflowConfig.setParallelLoopExecutorClass(property.getParallelLoopExecutorClass());
+		liteflowConfig.setFallbackCmpEnable(property.isFallbackCmpEnable());
 		return liteflowConfig;
 	}
 

+ 11 - 11
liteflow-solon-plugin/src/main/java/com/yomahub/liteflow/solon/config/LiteflowProperty.java

@@ -67,9 +67,6 @@ public class LiteflowProperty {
 	// 是否打印执行过程中的日志
 	private boolean printExecutionLog;
 
-	// 替补组件的class路径
-	private String substituteCmpClass;
-
 	//并行循环线程池类路径
 	private String parallelLoopExecutorClass;
 
@@ -78,6 +75,9 @@ public class LiteflowProperty {
 
 	//使用默认并行循环线程池时,最大队列数
 	private Integer parallelQueueLimit;
+	
+	// 是否启用组件降级
+	private Boolean fallbackCmpEnable;
 
 	public boolean isEnable() {
 		return enable;
@@ -212,14 +212,6 @@ public class LiteflowProperty {
 		this.requestIdGeneratorClass = requestIdGeneratorClass;
 	}
 
-	public String getSubstituteCmpClass() {
-		return substituteCmpClass;
-	}
-
-	public void setSubstituteCmpClass(String substituteCmpClass) {
-		this.substituteCmpClass = substituteCmpClass;
-	}
-
 	public String getRuleSourceExtData() {
 		return ruleSourceExtData;
 	}
@@ -251,4 +243,12 @@ public class LiteflowProperty {
 	public void setParallelQueueLimit(Integer parallelQueueLimit) {
 		this.parallelQueueLimit = parallelQueueLimit;
 	}
+	
+	public Boolean isFallbackCmpEnable() {
+		return fallbackCmpEnable;
+	}
+	
+	public void setFallbackCmpEnable(Boolean fallbackCmpEnable) {
+		this.fallbackCmpEnable = fallbackCmpEnable;
+	}
 }

+ 1 - 1
liteflow-solon-plugin/src/main/resources/META-INF/liteflow-default.properties

@@ -13,8 +13,8 @@ liteflow.retry-count=0
 liteflow.support-multiple-type=false
 liteflow.node-executor-class=com.yomahub.liteflow.flow.executor.DefaultNodeExecutor
 liteflow.print-execution-log=true
-liteflow.substitute-cmp-class=
 liteflow.monitor.enable-log=false
 liteflow.monitor.queue-limit=200
 liteflow.monitor.delay=300000
 liteflow.monitor.period=300000
+liteflow.fallback-cmp-enable=false

+ 11 - 11
liteflow-spring-boot-starter/src/main/java/com/yomahub/liteflow/springboot/LiteflowProperty.java

@@ -74,9 +74,6 @@ public class LiteflowProperty {
 	// 是否打印执行过程中的日志
 	private boolean printExecutionLog;
 
-	// 替补组件的class路径
-	private String substituteCmpClass;
-
 	// 规则文件/脚本文件变更监听
 	private Boolean enableMonitorFile;
 
@@ -87,6 +84,9 @@ public class LiteflowProperty {
 
 	//使用默认并行循环线程池时,最大队列数
 	private Integer parallelQueueLimit;
+	
+	// 是否启用组件降级
+	private Boolean fallbackCmpEnable;
 
 	public Boolean getEnableMonitorFile() {
 		return enableMonitorFile;
@@ -226,14 +226,6 @@ public class LiteflowProperty {
 		this.requestIdGeneratorClass = requestIdGeneratorClass;
 	}
 
-	public String getSubstituteCmpClass() {
-		return substituteCmpClass;
-	}
-
-	public void setSubstituteCmpClass(String substituteCmpClass) {
-		this.substituteCmpClass = substituteCmpClass;
-	}
-
 	public String getRuleSourceExtData() {
 		return ruleSourceExtData;
 	}
@@ -289,4 +281,12 @@ public class LiteflowProperty {
 	public void setParallelQueueLimit(Integer parallelQueueLimit) {
 		this.parallelQueueLimit = parallelQueueLimit;
 	}
+	
+	public Boolean isFallbackCmpEnable() {
+		return fallbackCmpEnable;
+	}
+	
+	public void setFallbackCmpEnable(Boolean fallbackCmpEnable) {
+		this.fallbackCmpEnable = fallbackCmpEnable;
+	}
 }

+ 1 - 1
liteflow-spring-boot-starter/src/main/java/com/yomahub/liteflow/springboot/config/LiteflowPropertyAutoConfiguration.java

@@ -46,11 +46,11 @@ public class LiteflowPropertyAutoConfiguration {
 		liteflowConfig.setMainExecutorWorks(property.getMainExecutorWorks());
 		liteflowConfig.setMainExecutorClass(property.getMainExecutorClass());
 		liteflowConfig.setPrintExecutionLog(property.isPrintExecutionLog());
-		liteflowConfig.setSubstituteCmpClass(property.getSubstituteCmpClass());
 		liteflowConfig.setEnableMonitorFile(property.getEnableMonitorFile());
 		liteflowConfig.setParallelMaxWorkers(property.getParallelMaxWorkers());
 		liteflowConfig.setParallelQueueLimit(property.getParallelQueueLimit());
 		liteflowConfig.setParallelLoopExecutorClass(property.getParallelLoopExecutorClass());
+		liteflowConfig.setFallbackCmpEnable(property.isFallbackCmpEnable());
 		return liteflowConfig;
 	}
 

+ 7 - 7
liteflow-spring-boot-starter/src/main/resources/META-INF/additional-spring-configuration-metadata.json

@@ -138,13 +138,6 @@
       "sourceType": "com.yomahub.liteflow.springboot.LiteflowProperty",
       "defaultValue": true
     },
-    {
-      "name": "liteflow.substitute-cmp-class",
-      "type": "java.lang.String",
-      "description": "substitute component class.",
-      "sourceType": "com.yomahub.liteflow.springboot.LiteflowProperty",
-      "defaultValue": ""
-    },
     {
       "name": "liteflow.monitor.enable-log",
       "type": "java.lang.Boolean",
@@ -200,6 +193,13 @@
       "description": "Custom thread pool implement for parallel-loop executor.",
       "sourceType": "com.yomahub.liteflow.springboot.LiteflowProperty",
       "defaultValue": "com.yomahub.liteflow.thread.LiteFlowDefaultParallelLoopExecutorBuilder"
+    },
+    {
+      "name": "liteflow.fallback-cmp-enable",
+      "type": "java.lang.Boolean",
+      "description": "Enable fallback component.",
+      "sourceType": "com.yomahub.liteflow.springboot.LiteflowProperty",
+      "defaultValue": false
     }
   ]
 }

+ 225 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/fallback/FallbackELDeclSpringbootTest.java

@@ -0,0 +1,225 @@
+package com.yomahub.liteflow.test.fallback;
+
+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;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+/**
+ * SpringBoot 降级组件测试
+ *
+ * @author DaleLee
+ * @since 2.11.1
+ */
+@TestPropertySource(value = "classpath:/fallback/application.properties")
+@SpringBootTest(classes = FallbackELDeclSpringbootTest.class)
+@EnableAutoConfiguration
+@ComponentScan({"com.yomahub.liteflow.test.fallback.cmp"})
+public class FallbackELDeclSpringbootTest extends BaseTest {
+
+    @Resource
+    private FlowExecutor flowExecutor;
+
+    @Test
+    public void testThen1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("then1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("a==>c", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testThen2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("then2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("c==>c==>c", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testWhen1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("when1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        String executeStepStr = response.getExecuteStepStrWithoutTime();
+        Assertions.assertTrue("b==>c".equals(executeStepStr) || "c==>b".equals(executeStepStr));
+    }
+
+    @Test
+    public void testIf1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("if1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("ifn2", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testIf2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("if2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("ifn1==>c", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testFor1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("for1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("for1==>a==>a==>a", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testFor2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("for2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("LOOP_3==>c==>c==>c", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testWhile1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("while1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("wn2", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testWhile2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("while2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("wn1==>c==>wn1==>c==>wn1==>c==>wn1", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testIterator1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("iterator1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("itn2", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testIterator2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("iterator2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("itn1==>c==>c==>c", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testBreak1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("break1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("LOOP_3==>a==>bn1", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testBreak2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("break2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("wn1==>a==>bn1", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testBreak3() {
+        LiteflowResponse response = flowExecutor.execute2Resp("break3", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("itn1==>a==>bn1", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testSwitch1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("switch1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("swn2==>b", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testSwitch2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("switch2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("swn1==>a", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testAnd1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("and1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("ifn2", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testOr1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("or1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("ifn2==>ifn1==>a", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testNot1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("not1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("ifn2==>a", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testCatch1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("catch1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("a==>d==>c", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testMulti1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("multi1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("a==>c==>ifn2", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testMulti2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("multi2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("ifn2==>ifn1==>a==>c", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testMulti3() {
+        LiteflowResponse response = flowExecutor.execute2Resp("multi3", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("for1==>b==>c==>b==>c==>b==>c", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testConcurrent1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("concurrent1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        String stepStr = response.getExecuteStepStrWithoutTime();
+        Assertions.assertTrue("c==>ifn2".equals(stepStr) || "ifn2==>c".equals(stepStr));
+    }
+
+    @Test
+    public void testConcurrent2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("concurrent2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        String stepStr = response.getExecuteStepStrWithoutTime();
+        Assertions.assertTrue("c==>ifn2".equals(stepStr) || "ifn2==>c".equals(stepStr));
+    }
+
+    @Test
+    public void testConcurrent3() throws ExecutionException, InterruptedException {
+        // 执行多条 chain
+        Future<LiteflowResponse> future1 = flowExecutor.execute2Future("concurrent1", "arg", new Object());
+        Future<LiteflowResponse> future2 = flowExecutor.execute2Future("concurrent2", "arg", new Object());
+        Thread.sleep(1000);
+        LiteflowResponse response1 = future1.get();
+        LiteflowResponse response2 = future2.get();
+        Assertions.assertTrue(response1.isSuccess());
+        String stepStr1 = response1.getExecuteStepStrWithoutTime();
+        Assertions.assertTrue("c==>ifn2".equals(stepStr1) || "ifn2==>c".equals(stepStr1));
+        Assertions.assertTrue(response2.isSuccess());
+        String stepStr2 = response2.getExecuteStepStrWithoutTime();
+        Assertions.assertTrue("c==>ifn2".equals(stepStr2) || "ifn2==>c".equals(stepStr2));
+    }
+}

+ 16 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/ACmp.java

@@ -0,0 +1,16 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+
+@LiteflowComponent("a")
+public class ACmp {
+
+    @LiteflowMethod(LiteFlowMethodEnum.PROCESS)
+    public void process(NodeComponent bindCmp) {
+        System.out.println("ACmp executed!");
+    }
+
+}

+ 16 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/BCmp.java

@@ -0,0 +1,16 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+
+@LiteflowComponent("b")
+public class BCmp {
+
+    @LiteflowMethod(LiteFlowMethodEnum.PROCESS)
+    public void process(NodeComponent bindCmp) {
+        System.out.println("BCmp executed!");
+    }
+
+}

+ 20 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/BreakCmp.java

@@ -0,0 +1,20 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.FallbackCmp;
+import com.yomahub.liteflow.annotation.LiteflowCmpDefine;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+import com.yomahub.liteflow.enums.NodeTypeEnum;
+
+@LiteflowComponent("bn1")
+@LiteflowCmpDefine(NodeTypeEnum.BREAK)
+@FallbackCmp
+public class BreakCmp {
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_BREAK)
+    public boolean processBreak(NodeComponent bindCmp) throws Exception {
+        return true;
+    }
+}

+ 18 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/CCmp.java

@@ -0,0 +1,18 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.FallbackCmp;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+
+@LiteflowComponent("c")
+@FallbackCmp
+public class CCmp {
+
+    @LiteflowMethod(LiteFlowMethodEnum.PROCESS)
+    public void process(NodeComponent bindCmp) {
+        System.out.println("CCmp executed!");
+    }
+
+}

+ 15 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/DCmp.java

@@ -0,0 +1,15 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+
+@LiteflowComponent("d")
+public class DCmp {
+
+    @LiteflowMethod(LiteFlowMethodEnum.PROCESS)
+    public void process(NodeComponent bindCmp) throws Exception {
+        throw new RuntimeException("component[d]");
+    }
+}

+ 20 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/ForCmp.java

@@ -0,0 +1,20 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.FallbackCmp;
+import com.yomahub.liteflow.annotation.LiteflowCmpDefine;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+import com.yomahub.liteflow.enums.NodeTypeEnum;
+
+@LiteflowComponent("for1")
+@LiteflowCmpDefine(NodeTypeEnum.FOR)
+@FallbackCmp
+public class ForCmp {
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_FOR)
+    public int processFor(NodeComponent bindCmp) throws Exception {
+        return 3;
+    }
+}

+ 18 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/IfCmp1.java

@@ -0,0 +1,18 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowCmpDefine;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+import com.yomahub.liteflow.enums.NodeTypeEnum;
+
+@LiteflowComponent("ifn1")
+@LiteflowCmpDefine(NodeTypeEnum.IF)
+public class IfCmp1 {
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_IF)
+    public boolean processIf(NodeComponent bindCmp) throws Exception {
+        return true;
+    }
+}

+ 20 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/IfCmp2.java

@@ -0,0 +1,20 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.FallbackCmp;
+import com.yomahub.liteflow.annotation.LiteflowCmpDefine;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+import com.yomahub.liteflow.enums.NodeTypeEnum;
+
+@LiteflowComponent("ifn2")
+@LiteflowCmpDefine(NodeTypeEnum.IF)
+@FallbackCmp
+public class IfCmp2 {
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_IF)
+    public boolean processIf(NodeComponent bindCmp) throws Exception {
+        return false;
+    }
+}

+ 21 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/IteratorCmp1.java

@@ -0,0 +1,21 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowCmpDefine;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+import com.yomahub.liteflow.enums.NodeTypeEnum;
+
+import java.util.Arrays;
+import java.util.Iterator;
+
+@LiteflowComponent("itn1")
+@LiteflowCmpDefine(NodeTypeEnum.ITERATOR)
+public class IteratorCmp1 {
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_ITERATOR)
+    public Iterator<?> processIterator(NodeComponent bindCmp) throws Exception {
+        return Arrays.asList("a", "b", "c").iterator();
+    }
+}

+ 23 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/IteratorCmp2.java

@@ -0,0 +1,23 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.FallbackCmp;
+import com.yomahub.liteflow.annotation.LiteflowCmpDefine;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+import com.yomahub.liteflow.enums.NodeTypeEnum;
+
+import java.util.Collections;
+import java.util.Iterator;
+
+@LiteflowComponent("itn2")
+@LiteflowCmpDefine(NodeTypeEnum.ITERATOR)
+@FallbackCmp
+public class IteratorCmp2 {
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_ITERATOR)
+    public Iterator<?> processIterator(NodeComponent bindCmp) throws Exception {
+        return Collections.emptyIterator();
+    }
+}

+ 18 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/SwitchCmp1.java

@@ -0,0 +1,18 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowCmpDefine;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+import com.yomahub.liteflow.enums.NodeTypeEnum;
+
+@LiteflowComponent("swn1")
+@LiteflowCmpDefine(NodeTypeEnum.SWITCH)
+public class SwitchCmp1 {
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_SWITCH)
+    public String processSwitch(NodeComponent bindCmp) throws Exception {
+        return "a";
+    }
+}

+ 20 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/SwitchCmp2.java

@@ -0,0 +1,20 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.FallbackCmp;
+import com.yomahub.liteflow.annotation.LiteflowCmpDefine;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+import com.yomahub.liteflow.enums.NodeTypeEnum;
+
+@LiteflowComponent("swn2")
+@LiteflowCmpDefine(NodeTypeEnum.SWITCH)
+@FallbackCmp
+public class SwitchCmp2 {
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_SWITCH)
+    public String processSwitch(NodeComponent bindCmp) throws Exception {
+        return "b";
+    }
+}

+ 31 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/WhileCmp1.java

@@ -0,0 +1,31 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowCmpDefine;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+import com.yomahub.liteflow.enums.NodeTypeEnum;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@LiteflowComponent("wn1")
+@LiteflowCmpDefine(NodeTypeEnum.WHILE)
+public class WhileCmp1 {
+    private int count = 0;
+
+    // 执行过的 chain
+    Set<String> executedChain = new HashSet<>();
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_WHILE)
+    public boolean processWhile(NodeComponent bindCmp) throws Exception {
+        // 判断是否切换了 chain
+        if (!executedChain.contains(bindCmp.getCurrChainId())) {
+            count = 0;
+            executedChain.add(bindCmp.getCurrChainId());
+        }
+        count++;
+        return count <= 3;
+    }
+}

+ 20 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/WhileCmp2.java

@@ -0,0 +1,20 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.FallbackCmp;
+import com.yomahub.liteflow.annotation.LiteflowCmpDefine;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.annotation.LiteflowMethod;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
+import com.yomahub.liteflow.enums.NodeTypeEnum;
+
+@LiteflowComponent("wn2")
+@LiteflowCmpDefine(NodeTypeEnum.WHILE)
+@FallbackCmp
+public class WhileCmp2 {
+
+    @LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_WHILE)
+    public boolean processWhile(NodeComponent bindCmp) throws Exception {
+        return false;
+    }
+}

+ 2 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/resources/fallback/application.properties

@@ -0,0 +1,2 @@
+liteflow.rule-source=fallback/flow.el.xml
+liteflow.fallback-cmp-enable=true

+ 136 - 0
liteflow-testcase-el/liteflow-testcase-el-declare-springboot/src/test/resources/fallback/flow.el.xml

@@ -0,0 +1,136 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<flow>
+    <!-- THEN 普通组件降级 -->
+    <chain name="then1">
+        THEN(a, node("x"));
+    </chain>
+    
+    <chain name="then2">
+        THEN(PRE(node("x1")), node("x2"), FINALLY(node("x3")));
+    </chain>
+
+    <!-- WHEN 普通组件降级 -->
+    <chain name="when1">
+        WHEN(b, node("x"));
+    </chain>
+
+    <!-- IF 条件组件降级 -->
+    <chain name="if1">
+        IF(node("x"), a)
+    </chain>
+    
+    <!-- IF 普通组件降级 -->
+    <chain name="if2">
+        IF(ifn1, node("x"))
+    </chain>
+    
+    <!-- FOR 次数循环组件降级 -->
+    <chain name="for1">
+        FOR(node("x")).DO(a);
+    </chain>
+    
+    <!-- FOR 普通组件降级 -->
+    <chain name="for2">
+        FOR(3).DO(node("x"));
+    </chain>
+    
+    <!-- WHILE 条件循环组件降级 -->
+    <chain name="while1">
+        WHILE(node("x")).DO(a)
+    </chain>
+    
+    <!-- WHILE 普通组件降级 -->
+    <chain name="while2">
+        WHILE(wn1).DO(node("x"))
+    </chain>
+    
+    <!-- ITERATOR 迭代组件降级 -->
+    <chain name="iterator1">
+        ITERATOR(node("x")).DO(a)
+    </chain>
+    
+    <!-- ITERATOR 普通组件降级 -->
+    <chain name="iterator2">
+        ITERATOR(itn1).DO(node("x"))
+    </chain>
+    
+    <!-- BREAK 退出循环组件降级 -->
+    <chain name="break1">
+        FOR(3).DO(a).BREAK(node("x"));
+    </chain>
+    
+    <chain name="break2">
+        WHILE(wn1).DO(a).BREAK(node("x"));
+    </chain>
+    
+    <chain name="break3">
+        ITERATOR(itn1).DO(a).BREAK(node("x"));
+    </chain>
+    
+    <!-- SWITCH 选择组件降级 -->
+    <chain name="switch1">
+        SWITCH(node("x")).to(a,b);
+    </chain>
+    
+    <!-- SWITCH 普通组件降级 -->
+    <chain name="switch2">
+        SWITCH(swn1).to(node("x"),a);
+    </chain>
+    
+    <!-- AND 条件组件降级 -->
+    <chain name="and1">
+        IF(AND(node("x"),ifn1), a);
+    </chain>
+    
+    <!-- OR 条件组件降级 -->
+    <chain name="or1">
+        IF(OR(node("x"),ifn1), a);
+    </chain>
+    
+    <!-- NOT 条件组件降级 -->
+    <chain name="not1">
+        IF(NOT(node("x")), a);
+    </chain>
+    
+    <!-- CATCH 普通组件降级 -->
+    <chain name="catch1">
+            CATCH(THEN(a, d)).DO(node("x"))
+    </chain>
+    
+    <!-- 多个组件降级 -->
+    <chain name="multi1">
+        THEN(
+            a,
+            node("x1"),
+            IF(node("x2"), b)
+        );
+    </chain>
+    
+    <chain name="multi2">
+        IF(
+            OR(node("x1"), ifn1),
+            THEN(a, node("x2"))
+        );
+    </chain>
+    
+    <chain name="multi3">
+        FOR(node("x1")).DO(
+            THEN(b, node("x2"))
+        );
+    </chain>
+
+    <!-- 并发降级测试 -->
+    <chain name="concurrent1">
+        WHEN(
+            THEN(node("x1")),
+            IF(node("x2"), b)
+        ).maxWaitSeconds(10000);
+    </chain>
+
+    <chain name="concurrent2">
+        WHEN(
+            node("x1"),
+            IF(node("x2"), b)
+        ).maxWaitSeconds(10000);
+    </chain>
+</flow>

+ 225 - 0
liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/fallback/FallbackTest.java

@@ -0,0 +1,225 @@
+package com.yomahub.liteflow.test.fallback;
+
+import com.yomahub.liteflow.core.FlowExecutor;
+import com.yomahub.liteflow.core.FlowExecutorHolder;
+import com.yomahub.liteflow.flow.LiteflowResponse;
+import com.yomahub.liteflow.property.LiteflowConfig;
+import com.yomahub.liteflow.test.BaseTest;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+/**
+ * 非 Spring 环境下组件降级测试
+ *
+ * @author DaleLee
+ * @since 2.11.1
+ */
+public class FallbackTest extends BaseTest {
+    private static FlowExecutor flowExecutor;
+
+    @BeforeAll
+    public static void init() {
+        LiteflowConfig config = new LiteflowConfig();
+        config.setRuleSource("fallback/flow.el.xml");
+        config.setFallbackCmpEnable(true);
+        flowExecutor = FlowExecutorHolder.loadInstance(config);
+    }
+
+    @Test
+    public void testThen1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("then1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("a==>c", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testThen2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("then2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("c==>c==>c", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testWhen1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("when1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        String executeStepStr = response.getExecuteStepStrWithoutTime();
+        Assertions.assertTrue("b==>c".equals(executeStepStr) || "c==>b".equals(executeStepStr));
+    }
+
+    @Test
+    public void testIf1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("if1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("ifn2", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testIf2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("if2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("ifn1==>c", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testFor1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("for1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("for1==>a==>a==>a", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testFor2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("for2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("LOOP_3==>c==>c==>c", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testWhile1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("while1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("wn2", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testWhile2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("while2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("wn1==>c==>wn1==>c==>wn1==>c==>wn1", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testIterator1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("iterator1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("itn2", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testIterator2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("iterator2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("itn1==>c==>c==>c", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testBreak1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("break1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("LOOP_3==>a==>bn1", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testBreak2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("break2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("wn1==>a==>bn1", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testBreak3() {
+        LiteflowResponse response = flowExecutor.execute2Resp("break3", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("itn1==>a==>bn1", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testSwitch1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("switch1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("swn2==>b", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testSwitch2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("switch2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("swn1==>a", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testAnd1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("and1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("ifn2", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testOr1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("or1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("ifn2==>ifn1==>a", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testNot1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("not1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("ifn2==>a", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testCatch1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("catch1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("a==>d==>c", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testMulti1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("multi1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("a==>c==>ifn2", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testMulti2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("multi2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("ifn2==>ifn1==>a==>c", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testMulti3() {
+        LiteflowResponse response = flowExecutor.execute2Resp("multi3", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("for1==>b==>c==>b==>c==>b==>c", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testConcurrent1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("concurrent1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        String stepStr = response.getExecuteStepStrWithoutTime();
+        Assertions.assertTrue("c==>ifn2".equals(stepStr) || "ifn2==>c".equals(stepStr));
+    }
+
+    @Test
+    public void testConcurrent2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("concurrent2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        String stepStr = response.getExecuteStepStrWithoutTime();
+        Assertions.assertTrue("c==>ifn2".equals(stepStr) || "ifn2==>c".equals(stepStr));
+    }
+
+    @Test
+    public void testConcurrent3() throws ExecutionException, InterruptedException {
+        // 执行多条 chain
+        Future<LiteflowResponse> future1 = flowExecutor.execute2Future("concurrent1", "arg", new Object());
+        Future<LiteflowResponse> future2 = flowExecutor.execute2Future("concurrent2", "arg", new Object());
+        Thread.sleep(1000);
+        LiteflowResponse response1 = future1.get();
+        LiteflowResponse response2 = future2.get();
+        Assertions.assertTrue(response1.isSuccess());
+        String stepStr1 = response1.getExecuteStepStrWithoutTime();
+        Assertions.assertTrue("c==>ifn2".equals(stepStr1) || "ifn2==>c".equals(stepStr1));
+        Assertions.assertTrue(response2.isSuccess());
+        String stepStr2 = response2.getExecuteStepStrWithoutTime();
+        Assertions.assertTrue("c==>ifn2".equals(stepStr2) || "ifn2==>c".equals(stepStr2));
+    }
+}

+ 12 - 0
liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/fallback/cmp/ACmp.java

@@ -0,0 +1,12 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.core.NodeComponent;
+
+public class ACmp extends NodeComponent {
+
+    @Override
+    public void process() {
+        System.out.println("ACmp executed!");
+    }
+
+}

+ 12 - 0
liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/fallback/cmp/BCmp.java

@@ -0,0 +1,12 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.core.NodeComponent;
+
+public class BCmp extends NodeComponent {
+
+    @Override
+    public void process() {
+        System.out.println("BCmp executed!");
+    }
+
+}

+ 13 - 0
liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/fallback/cmp/BreakCmp.java

@@ -0,0 +1,13 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.FallbackCmp;
+import com.yomahub.liteflow.core.NodeBreakComponent;
+
+@FallbackCmp
+public class BreakCmp extends NodeBreakComponent {
+
+    @Override
+    public boolean processBreak() throws Exception {
+        return true;
+    }
+}

+ 14 - 0
liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/fallback/cmp/CCmp.java

@@ -0,0 +1,14 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.FallbackCmp;
+import com.yomahub.liteflow.core.NodeComponent;
+
+@FallbackCmp
+public class CCmp extends NodeComponent {
+
+    @Override
+    public void process() {
+        System.out.println("CCmp executed!");
+    }
+
+}

+ 11 - 0
liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/fallback/cmp/DCmp.java

@@ -0,0 +1,11 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.core.NodeComponent;
+
+public class DCmp extends NodeComponent {
+
+    @Override
+    public void process() throws Exception {
+        throw new RuntimeException("component[d]");
+    }
+}

+ 13 - 0
liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/fallback/cmp/ForCmp.java

@@ -0,0 +1,13 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.FallbackCmp;
+import com.yomahub.liteflow.core.NodeForComponent;
+
+@FallbackCmp
+public class ForCmp extends NodeForComponent {
+
+    @Override
+    public int processFor() throws Exception {
+        return 3;
+    }
+}

+ 11 - 0
liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/fallback/cmp/IfCmp1.java

@@ -0,0 +1,11 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.core.NodeIfComponent;
+
+public class IfCmp1 extends NodeIfComponent {
+
+    @Override
+    public boolean processIf() throws Exception {
+        return true;
+    }
+}

+ 13 - 0
liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/fallback/cmp/IfCmp2.java

@@ -0,0 +1,13 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.FallbackCmp;
+import com.yomahub.liteflow.core.NodeIfComponent;
+
+@FallbackCmp
+public class IfCmp2 extends NodeIfComponent {
+
+    @Override
+    public boolean processIf() throws Exception {
+        return false;
+    }
+}

+ 14 - 0
liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/fallback/cmp/IteratorCmp1.java

@@ -0,0 +1,14 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.core.NodeIteratorComponent;
+
+import java.util.Arrays;
+import java.util.Iterator;
+
+public class IteratorCmp1 extends NodeIteratorComponent {
+
+    @Override
+    public Iterator<?> processIterator() throws Exception {
+        return Arrays.asList("a", "b", "c").iterator();
+    }
+}

+ 16 - 0
liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/fallback/cmp/IteratorCmp2.java

@@ -0,0 +1,16 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.FallbackCmp;
+import com.yomahub.liteflow.core.NodeIteratorComponent;
+
+import java.util.Collections;
+import java.util.Iterator;
+
+@FallbackCmp
+public class IteratorCmp2 extends NodeIteratorComponent {
+
+    @Override
+    public Iterator<?> processIterator() throws Exception {
+        return Collections.emptyIterator();
+    }
+}

+ 11 - 0
liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/fallback/cmp/SwitchCmp1.java

@@ -0,0 +1,11 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.core.NodeSwitchComponent;
+
+public class SwitchCmp1 extends NodeSwitchComponent {
+
+    @Override
+    public String processSwitch() throws Exception {
+        return "a";
+    }
+}

+ 13 - 0
liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/fallback/cmp/SwitchCmp2.java

@@ -0,0 +1,13 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.FallbackCmp;
+import com.yomahub.liteflow.core.NodeSwitchComponent;
+
+@FallbackCmp
+public class SwitchCmp2 extends NodeSwitchComponent {
+
+    @Override
+    public String processSwitch() throws Exception {
+        return "b";
+    }
+}

+ 24 - 0
liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/fallback/cmp/WhileCmp1.java

@@ -0,0 +1,24 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.core.NodeWhileComponent;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class WhileCmp1 extends NodeWhileComponent {
+    private int count = 0;
+
+    // 执行过的 chain
+    Set<String> executedChain = new HashSet<>();
+
+    @Override
+    public boolean processWhile() throws Exception {
+        // 判断是否切换了 chain
+        if (!executedChain.contains(this.getCurrChainId())) {
+            count = 0;
+            executedChain.add(this.getCurrChainId());
+        }
+        count++;
+        return count <= 3;
+    }
+}

+ 13 - 0
liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/java/com/yomahub/liteflow/test/fallback/cmp/WhileCmp2.java

@@ -0,0 +1,13 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.FallbackCmp;
+import com.yomahub.liteflow.core.NodeWhileComponent;
+
+@FallbackCmp
+public class WhileCmp2 extends NodeWhileComponent {
+    
+    @Override
+    public boolean processWhile() throws Exception {
+        return false;
+    }
+}

+ 153 - 0
liteflow-testcase-el/liteflow-testcase-el-nospring/src/test/resources/fallback/flow.el.xml

@@ -0,0 +1,153 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<flow>
+    <nodes>
+        <node id = "a" class="com.yomahub.liteflow.test.fallback.cmp.ACmp"/>
+        <node id = "b" class="com.yomahub.liteflow.test.fallback.cmp.BCmp"/>
+        <node id = "c" class="com.yomahub.liteflow.test.fallback.cmp.CCmp"/>
+        <node id = "d" class="com.yomahub.liteflow.test.fallback.cmp.DCmp"/>
+        <node id = "ifn1" class="com.yomahub.liteflow.test.fallback.cmp.IfCmp1"/>
+        <node id = "ifn2" class="com.yomahub.liteflow.test.fallback.cmp.IfCmp2"/>
+        <node id = "swn1" class="com.yomahub.liteflow.test.fallback.cmp.SwitchCmp1"/>
+        <node id = "swn2" class="com.yomahub.liteflow.test.fallback.cmp.SwitchCmp2"/>
+        <node id = "for1" class="com.yomahub.liteflow.test.fallback.cmp.ForCmp"/>
+        <node id = "wn1" class="com.yomahub.liteflow.test.fallback.cmp.WhileCmp1"/>
+        <node id = "wn2" class="com.yomahub.liteflow.test.fallback.cmp.WhileCmp2"/>
+        <node id = "itn1" class="com.yomahub.liteflow.test.fallback.cmp.IteratorCmp1"/>
+        <node id = "itn2" class="com.yomahub.liteflow.test.fallback.cmp.IteratorCmp2"/>
+        <node id = "bn1" class="com.yomahub.liteflow.test.fallback.cmp.BreakCmp"/>
+    </nodes>
+
+    <!-- THEN 普通组件降级 -->
+    <chain name="then1">
+        THEN(a, node("x"));
+    </chain>
+    
+    <chain name="then2">
+        THEN(PRE(node("x1")), node("x2"), FINALLY(node("x3")));
+    </chain>
+
+    <!-- WHEN 普通组件降级 -->
+    <chain name="when1">
+        WHEN(b, node("x"));
+    </chain>
+
+    <!-- IF 条件组件降级 -->
+    <chain name="if1">
+        IF(node("x"), a)
+    </chain>
+    
+    <!-- IF 普通组件降级 -->
+    <chain name="if2">
+        IF(ifn1, node("x"))
+    </chain>
+    
+    <!-- FOR 次数循环组件降级 -->
+    <chain name="for1">
+        FOR(node("x")).DO(a);
+    </chain>
+    
+    <!-- FOR 普通组件降级 -->
+    <chain name="for2">
+        FOR(3).DO(node("x"));
+    </chain>
+    
+    <!-- WHILE 条件循环组件降级 -->
+    <chain name="while1">
+        WHILE(node("x")).DO(a)
+    </chain>
+    
+    <!-- WHILE 普通组件降级 -->
+    <chain name="while2">
+        WHILE(wn1).DO(node("x"))
+    </chain>
+    
+    <!-- ITERATOR 迭代组件降级 -->
+    <chain name="iterator1">
+        ITERATOR(node("x")).DO(a)
+    </chain>
+    
+    <!-- ITERATOR 普通组件降级 -->
+    <chain name="iterator2">
+        ITERATOR(itn1).DO(node("x"))
+    </chain>
+    
+    <!-- BREAK 退出循环组件降级 -->
+    <chain name="break1">
+        FOR(3).DO(a).BREAK(node("x"));
+    </chain>
+    
+    <chain name="break2">
+        WHILE(wn1).DO(a).BREAK(node("x"));
+    </chain>
+    
+    <chain name="break3">
+        ITERATOR(itn1).DO(a).BREAK(node("x"));
+    </chain>
+    
+    <!-- SWITCH 选择组件降级 -->
+    <chain name="switch1">
+        SWITCH(node("x")).to(a,b);
+    </chain>
+    
+    <!-- SWITCH 普通组件降级 -->
+    <chain name="switch2">
+        SWITCH(swn1).to(node("x"),a);
+    </chain>
+    
+    <!-- AND 条件组件降级 -->
+    <chain name="and1">
+        IF(AND(node("x"),ifn1), a);
+    </chain>
+    
+    <!-- OR 条件组件降级 -->
+    <chain name="or1">
+        IF(OR(node("x"),ifn1), a);
+    </chain>
+    
+    <!-- NOT 条件组件降级 -->
+    <chain name="not1">
+        IF(NOT(node("x")), a);
+    </chain>
+    
+    <!-- CATCH 普通组件降级 -->
+    <chain name="catch1">
+            CATCH(THEN(a, d)).DO(node("x"))
+    </chain>
+    
+    <!-- 多个组件降级 -->
+    <chain name="multi1">
+        THEN(
+            a,
+            node("x1"),
+            IF(node("x2"), b)
+        );
+    </chain>
+    
+    <chain name="multi2">
+        IF(
+            OR(node("x1"), ifn1),
+            THEN(a, node("x2"))
+        );
+    </chain>
+    
+    <chain name="multi3">
+        FOR(node("x1")).DO(
+            THEN(b, node("x2"))
+        );
+    </chain>
+
+    <!-- 并发降级测试 -->
+    <chain name="concurrent1">
+        WHEN(
+            THEN(node("x1")),
+            IF(node("x2"), b)
+        ).maxWaitSeconds(10000);
+    </chain>
+
+    <chain name="concurrent2">
+        WHEN(
+            node("x1"),
+            IF(node("x2"), b)
+        ).maxWaitSeconds(10000);
+    </chain>
+</flow>

+ 222 - 0
liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/fallback/FallbackELSolonTest.java

@@ -0,0 +1,222 @@
+package com.yomahub.liteflow.test.fallback;
+
+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.junit.jupiter.api.extension.ExtendWith;
+import org.noear.solon.annotation.Inject;
+import org.noear.solon.test.SolonJUnit5Extension;
+import org.noear.solon.test.annotation.TestPropertySource;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+/**
+ * Solon 降级组件测试
+ *
+ * @author DaleLee
+ * @since 2.11.1
+ */
+@ExtendWith(SolonJUnit5Extension.class)
+@TestPropertySource("classpath:/fallback/application.properties")
+public class FallbackELSolonTest extends BaseTest {
+
+    @Inject
+    private FlowExecutor flowExecutor;
+
+    @Test
+    public void testThen1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("then1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("a==>c", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testThen2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("then2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("c==>c==>c", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testWhen1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("when1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        String executeStepStr = response.getExecuteStepStrWithoutTime();
+        Assertions.assertTrue("b==>c".equals(executeStepStr) || "c==>b".equals(executeStepStr));
+    }
+
+    @Test
+    public void testIf1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("if1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("ifn2", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testIf2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("if2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("ifn1==>c", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testFor1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("for1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("for1==>a==>a==>a", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testFor2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("for2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("LOOP_3==>c==>c==>c", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testWhile1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("while1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("wn2", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testWhile2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("while2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("wn1==>c==>wn1==>c==>wn1==>c==>wn1", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testIterator1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("iterator1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("itn2", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testIterator2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("iterator2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("itn1==>c==>c==>c", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testBreak1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("break1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("LOOP_3==>a==>bn1", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testBreak2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("break2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("wn1==>a==>bn1", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testBreak3() {
+        LiteflowResponse response = flowExecutor.execute2Resp("break3", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("itn1==>a==>bn1", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testSwitch1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("switch1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("swn2==>b", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testSwitch2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("switch2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("swn1==>a", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testAnd1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("and1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("ifn2", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testOr1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("or1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("ifn2==>ifn1==>a", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testNot1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("not1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("ifn2==>a", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testCatch1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("catch1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("a==>d==>c", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testMulti1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("multi1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("a==>c==>ifn2", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testMulti2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("multi2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("ifn2==>ifn1==>a==>c", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testMulti3() {
+        LiteflowResponse response = flowExecutor.execute2Resp("multi3", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("for1==>b==>c==>b==>c==>b==>c", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testConcurrent1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("concurrent1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        String stepStr = response.getExecuteStepStrWithoutTime();
+        Assertions.assertTrue("c==>ifn2".equals(stepStr) || "ifn2==>c".equals(stepStr));
+    }
+
+    @Test
+    public void testConcurrent2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("concurrent2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        String stepStr = response.getExecuteStepStrWithoutTime();
+        Assertions.assertTrue("c==>ifn2".equals(stepStr) || "ifn2==>c".equals(stepStr));
+    }
+
+    @Test
+    public void testConcurrent3() throws ExecutionException, InterruptedException {
+        // 执行多条 chain
+        Future<LiteflowResponse> future1 = flowExecutor.execute2Future("concurrent1", "arg", new Object());
+        Future<LiteflowResponse> future2 = flowExecutor.execute2Future("concurrent2", "arg", new Object());
+        Thread.sleep(1000);
+        LiteflowResponse response1 = future1.get();
+        LiteflowResponse response2 = future2.get();
+        Assertions.assertTrue(response1.isSuccess());
+        String stepStr1 = response1.getExecuteStepStrWithoutTime();
+        Assertions.assertTrue("c==>ifn2".equals(stepStr1) || "ifn2==>c".equals(stepStr1));
+        Assertions.assertTrue(response2.isSuccess());
+        String stepStr2 = response2.getExecuteStepStrWithoutTime();
+        Assertions.assertTrue("c==>ifn2".equals(stepStr2) || "ifn2==>c".equals(stepStr2));
+    }
+}

+ 14 - 0
liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/fallback/cmp/ACmp.java

@@ -0,0 +1,14 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeComponent;
+
+@LiteflowComponent("a")
+public class ACmp extends NodeComponent {
+
+    @Override
+    public void process() {
+        System.out.println("ACmp executed!");
+    }
+
+}

+ 14 - 0
liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/fallback/cmp/BCmp.java

@@ -0,0 +1,14 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeComponent;
+
+@LiteflowComponent("b")
+public class BCmp extends NodeComponent {
+
+    @Override
+    public void process() {
+        System.out.println("BCmp executed!");
+    }
+
+}

+ 15 - 0
liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/fallback/cmp/BreakCmp.java

@@ -0,0 +1,15 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.FallbackCmp;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeBreakComponent;
+
+@LiteflowComponent("bn1")
+@FallbackCmp
+public class BreakCmp extends NodeBreakComponent {
+
+    @Override
+    public boolean processBreak() throws Exception {
+        return true;
+    }
+}

+ 16 - 0
liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/fallback/cmp/CCmp.java

@@ -0,0 +1,16 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.FallbackCmp;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeComponent;
+
+@LiteflowComponent("c")
+@FallbackCmp
+public class CCmp extends NodeComponent {
+
+    @Override
+    public void process() {
+        System.out.println("CCmp executed!");
+    }
+
+}

+ 13 - 0
liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/fallback/cmp/DCmp.java

@@ -0,0 +1,13 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeComponent;
+
+@LiteflowComponent("d")
+public class DCmp extends NodeComponent {
+
+    @Override
+    public void process() throws Exception {
+        throw new RuntimeException("component[d]");
+    }
+}

+ 15 - 0
liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/fallback/cmp/ForCmp.java

@@ -0,0 +1,15 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.FallbackCmp;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeForComponent;
+
+@LiteflowComponent("for1")
+@FallbackCmp
+public class ForCmp extends NodeForComponent {
+
+    @Override
+    public int processFor() throws Exception {
+        return 3;
+    }
+}

+ 13 - 0
liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/fallback/cmp/IfCmp1.java

@@ -0,0 +1,13 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeIfComponent;
+
+@LiteflowComponent("ifn1")
+public class IfCmp1 extends NodeIfComponent {
+
+    @Override
+    public boolean processIf() throws Exception {
+        return true;
+    }
+}

+ 15 - 0
liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/fallback/cmp/IfCmp2.java

@@ -0,0 +1,15 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.FallbackCmp;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeIfComponent;
+
+@LiteflowComponent("ifn2")
+@FallbackCmp
+public class IfCmp2 extends NodeIfComponent {
+
+    @Override
+    public boolean processIf() throws Exception {
+        return false;
+    }
+}

+ 16 - 0
liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/fallback/cmp/IteratorCmp1.java

@@ -0,0 +1,16 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeIteratorComponent;
+
+import java.util.Arrays;
+import java.util.Iterator;
+
+@LiteflowComponent("itn1")
+public class IteratorCmp1 extends NodeIteratorComponent {
+
+    @Override
+    public Iterator<?> processIterator() throws Exception {
+        return Arrays.asList("a", "b", "c").iterator();
+    }
+}

+ 18 - 0
liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/fallback/cmp/IteratorCmp2.java

@@ -0,0 +1,18 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.FallbackCmp;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeIteratorComponent;
+
+import java.util.Collections;
+import java.util.Iterator;
+
+@LiteflowComponent("itn2")
+@FallbackCmp
+public class IteratorCmp2 extends NodeIteratorComponent {
+
+    @Override
+    public Iterator<?> processIterator() throws Exception {
+        return Collections.emptyIterator();
+    }
+}

+ 13 - 0
liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/fallback/cmp/SwitchCmp1.java

@@ -0,0 +1,13 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeSwitchComponent;
+
+@LiteflowComponent("swn1")
+public class SwitchCmp1 extends NodeSwitchComponent {
+
+    @Override
+    public String processSwitch() throws Exception {
+        return "a";
+    }
+}

+ 15 - 0
liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/fallback/cmp/SwitchCmp2.java

@@ -0,0 +1,15 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.FallbackCmp;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeSwitchComponent;
+
+@LiteflowComponent("swn2")
+@FallbackCmp
+public class SwitchCmp2 extends NodeSwitchComponent {
+
+    @Override
+    public String processSwitch() throws Exception {
+        return "b";
+    }
+}

+ 26 - 0
liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/fallback/cmp/WhileCmp1.java

@@ -0,0 +1,26 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeWhileComponent;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@LiteflowComponent("wn1")
+public class WhileCmp1 extends NodeWhileComponent {
+    private int count = 0;
+
+    // 执行过的 chain
+    Set<String> executedChain = new HashSet<>();
+
+    @Override
+    public boolean processWhile() throws Exception {
+        // 判断是否切换了 chain
+        if (!executedChain.contains(this.getCurrChainId())) {
+            count = 0;
+            executedChain.add(this.getCurrChainId());
+        }
+        count++;
+        return count <= 3;
+    }
+}

+ 15 - 0
liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/fallback/cmp/WhileCmp2.java

@@ -0,0 +1,15 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.FallbackCmp;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeWhileComponent;
+
+@LiteflowComponent("wn2")
+@FallbackCmp
+public class WhileCmp2 extends NodeWhileComponent {
+    
+    @Override
+    public boolean processWhile() throws Exception {
+        return false;
+    }
+}

+ 0 - 46
liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/substituteNode/SubstituteSpringbootTest.java

@@ -1,46 +0,0 @@
-package com.yomahub.liteflow.test.substituteNode;
-
-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.junit.jupiter.api.extension.ExtendWith;
-import org.noear.solon.annotation.Inject;
-import org.noear.solon.test.SolonJUnit5Extension;
-import org.noear.solon.test.annotation.TestPropertySource;
-
-/**
- * springboot环境EL替补节点的测试
- *
- * @author Bryan.Zhang
- */
-@ExtendWith(SolonJUnit5Extension.class)
-@TestPropertySource("classpath:/substituteNode/application.properties")
-public class SubstituteSpringbootTest extends BaseTest {
-
-	@Inject
-	private FlowExecutor flowExecutor;
-
-	// 最简单的情况
-	@Test
-	public void testSub1() throws Exception {
-		LiteflowResponse response = flowExecutor.execute2Resp("chain1", "arg");
-		Assertions.assertTrue(response.isSuccess());
-	}
-
-	// 有替补节点
-	@Test
-	public void testSub2() throws Exception {
-		LiteflowResponse response = flowExecutor.execute2Resp("chain2", "arg");
-		Assertions.assertTrue(response.isSuccess());
-	}
-
-	// 测试特殊命名的节点
-	@Test
-	public void testSub3() throws Exception {
-		LiteflowResponse response = flowExecutor.execute2Resp("chain3", "arg");
-		Assertions.assertTrue(response.isSuccess());
-	}
-
-}

+ 0 - 21
liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/substituteNode/cmp/ACmp.java

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

+ 0 - 21
liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/substituteNode/cmp/BCmp.java

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

+ 0 - 21
liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/substituteNode/cmp/CCmp.java

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

+ 0 - 21
liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/substituteNode/cmp/DCmp.java

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

+ 0 - 21
liteflow-testcase-el/liteflow-testcase-el-solon/src/test/java/com/yomahub/liteflow/test/substituteNode/cmp/SubCmp.java

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

+ 2 - 0
liteflow-testcase-el/liteflow-testcase-el-solon/src/test/resources/fallback/application.properties

@@ -0,0 +1,2 @@
+liteflow.rule-source=fallback/flow.el.xml
+liteflow.fallback-cmp-enable=true

+ 136 - 0
liteflow-testcase-el/liteflow-testcase-el-solon/src/test/resources/fallback/flow.el.xml

@@ -0,0 +1,136 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<flow>
+    <!-- THEN 普通组件降级 -->
+    <chain name="then1">
+        THEN(a, node("x"));
+    </chain>
+    
+    <chain name="then2">
+        THEN(PRE(node("x1")), node("x2"), FINALLY(node("x3")));
+    </chain>
+
+    <!-- WHEN 普通组件降级 -->
+    <chain name="when1">
+        WHEN(b, node("x"));
+    </chain>
+
+    <!-- IF 条件组件降级 -->
+    <chain name="if1">
+        IF(node("x"), a)
+    </chain>
+    
+    <!-- IF 普通组件降级 -->
+    <chain name="if2">
+        IF(ifn1, node("x"))
+    </chain>
+    
+    <!-- FOR 次数循环组件降级 -->
+    <chain name="for1">
+        FOR(node("x")).DO(a);
+    </chain>
+    
+    <!-- FOR 普通组件降级 -->
+    <chain name="for2">
+        FOR(3).DO(node("x"));
+    </chain>
+    
+    <!-- WHILE 条件循环组件降级 -->
+    <chain name="while1">
+        WHILE(node("x")).DO(a)
+    </chain>
+    
+    <!-- WHILE 普通组件降级 -->
+    <chain name="while2">
+        WHILE(wn1).DO(node("x"))
+    </chain>
+    
+    <!-- ITERATOR 迭代组件降级 -->
+    <chain name="iterator1">
+        ITERATOR(node("x")).DO(a)
+    </chain>
+    
+    <!-- ITERATOR 普通组件降级 -->
+    <chain name="iterator2">
+        ITERATOR(itn1).DO(node("x"))
+    </chain>
+    
+    <!-- BREAK 退出循环组件降级 -->
+    <chain name="break1">
+        FOR(3).DO(a).BREAK(node("x"));
+    </chain>
+    
+    <chain name="break2">
+        WHILE(wn1).DO(a).BREAK(node("x"));
+    </chain>
+    
+    <chain name="break3">
+        ITERATOR(itn1).DO(a).BREAK(node("x"));
+    </chain>
+    
+    <!-- SWITCH 选择组件降级 -->
+    <chain name="switch1">
+        SWITCH(node("x")).to(a,b);
+    </chain>
+    
+    <!-- SWITCH 普通组件降级 -->
+    <chain name="switch2">
+        SWITCH(swn1).to(node("x"),a);
+    </chain>
+    
+    <!-- AND 条件组件降级 -->
+    <chain name="and1">
+        IF(AND(node("x"),ifn1), a);
+    </chain>
+    
+    <!-- OR 条件组件降级 -->
+    <chain name="or1">
+        IF(OR(node("x"),ifn1), a);
+    </chain>
+    
+    <!-- NOT 条件组件降级 -->
+    <chain name="not1">
+        IF(NOT(node("x")), a);
+    </chain>
+    
+    <!-- CATCH 普通组件降级 -->
+    <chain name="catch1">
+            CATCH(THEN(a, d)).DO(node("x"))
+    </chain>
+    
+    <!-- 多个组件降级 -->
+    <chain name="multi1">
+        THEN(
+            a,
+            node("x1"),
+            IF(node("x2"), b)
+        );
+    </chain>
+    
+    <chain name="multi2">
+        IF(
+            OR(node("x1"), ifn1),
+            THEN(a, node("x2"))
+        );
+    </chain>
+    
+    <chain name="multi3">
+        FOR(node("x1")).DO(
+            THEN(b, node("x2"))
+        );
+    </chain>
+
+    <!-- 并发降级测试 -->
+    <chain name="concurrent1">
+        WHEN(
+            THEN(node("x1")),
+            IF(node("x2"), b)
+        ).maxWaitSeconds(10000);
+    </chain>
+
+    <chain name="concurrent2">
+        WHEN(
+            node("x1"),
+            IF(node("x2"), b)
+        ).maxWaitSeconds(10000);
+    </chain>
+</flow>

+ 0 - 2
liteflow-testcase-el/liteflow-testcase-el-solon/src/test/resources/substituteNode/application.properties

@@ -1,2 +0,0 @@
-liteflow.rule-source=substituteNode/flow.el.xml
-liteflow.substitute-cmp-class=com.yomahub.liteflow.test.substituteNode.cmp.SubCmp

+ 0 - 14
liteflow-testcase-el/liteflow-testcase-el-solon/src/test/resources/substituteNode/flow.el.xml

@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<flow>
-    <chain name="chain1">
-        THEN(node("a"), node("b"), node("c"));
-    </chain>
-
-    <chain name="chain2">
-        THEN(node("a"), node("b"), node("93-nodeTEST"));
-    </chain>
-
-    <chain name="chain3">
-        THEN(a, b, node("88-ffc"));
-    </chain>
-</flow>

+ 225 - 0
liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/FallbackELSpringbootTest.java

@@ -0,0 +1,225 @@
+package com.yomahub.liteflow.test.fallback;
+
+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;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+/**
+ * SpringBoot 降级组件测试
+ *
+ * @author DaleLee
+ * @since 2.11.1
+ */
+@TestPropertySource(value = "classpath:/fallback/application.properties")
+@SpringBootTest(classes = FallbackELSpringbootTest.class)
+@EnableAutoConfiguration
+@ComponentScan({"com.yomahub.liteflow.test.fallback.cmp"})
+public class FallbackELSpringbootTest extends BaseTest {
+
+    @Resource
+    private FlowExecutor flowExecutor;
+
+    @Test
+    public void testThen1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("then1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("a==>c", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testThen2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("then2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("c==>c==>c", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testWhen1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("when1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        String executeStepStr = response.getExecuteStepStrWithoutTime();
+        Assertions.assertTrue("b==>c".equals(executeStepStr) || "c==>b".equals(executeStepStr));
+    }
+
+    @Test
+    public void testIf1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("if1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("ifn2", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testIf2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("if2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("ifn1==>c", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testFor1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("for1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("for1==>a==>a==>a", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testFor2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("for2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("LOOP_3==>c==>c==>c", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testWhile1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("while1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("wn2", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testWhile2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("while2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("wn1==>c==>wn1==>c==>wn1==>c==>wn1", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testIterator1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("iterator1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("itn2", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testIterator2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("iterator2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("itn1==>c==>c==>c", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testBreak1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("break1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("LOOP_3==>a==>bn1", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testBreak2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("break2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("wn1==>a==>bn1", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testBreak3() {
+        LiteflowResponse response = flowExecutor.execute2Resp("break3", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("itn1==>a==>bn1", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testSwitch1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("switch1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("swn2==>b", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testSwitch2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("switch2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("swn1==>a", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testAnd1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("and1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("ifn2", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testOr1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("or1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("ifn2==>ifn1==>a", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testNot1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("not1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("ifn2==>a", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testCatch1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("catch1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("a==>d==>c", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testMulti1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("multi1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("a==>c==>ifn2", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testMulti2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("multi2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("ifn2==>ifn1==>a==>c", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testMulti3() {
+        LiteflowResponse response = flowExecutor.execute2Resp("multi3", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        Assertions.assertEquals("for1==>b==>c==>b==>c==>b==>c", response.getExecuteStepStrWithoutTime());
+    }
+
+    @Test
+    public void testConcurrent1() {
+        LiteflowResponse response = flowExecutor.execute2Resp("concurrent1", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        String stepStr = response.getExecuteStepStrWithoutTime();
+        Assertions.assertTrue("c==>ifn2".equals(stepStr) || "ifn2==>c".equals(stepStr));
+    }
+
+    @Test
+    public void testConcurrent2() {
+        LiteflowResponse response = flowExecutor.execute2Resp("concurrent2", "arg");
+        Assertions.assertTrue(response.isSuccess());
+        String stepStr = response.getExecuteStepStrWithoutTime();
+        Assertions.assertTrue("c==>ifn2".equals(stepStr) || "ifn2==>c".equals(stepStr));
+    }
+
+    @Test
+    public void testConcurrent3() throws ExecutionException, InterruptedException {
+        // 执行多条 chain
+        Future<LiteflowResponse> future1 = flowExecutor.execute2Future("concurrent1", "arg", new Object());
+        Future<LiteflowResponse> future2 = flowExecutor.execute2Future("concurrent2", "arg", new Object());
+        Thread.sleep(1000);
+        LiteflowResponse response1 = future1.get();
+        LiteflowResponse response2 = future2.get();
+        Assertions.assertTrue(response1.isSuccess());
+        String stepStr1 = response1.getExecuteStepStrWithoutTime();
+        Assertions.assertTrue("c==>ifn2".equals(stepStr1) || "ifn2==>c".equals(stepStr1));
+        Assertions.assertTrue(response2.isSuccess());
+        String stepStr2 = response2.getExecuteStepStrWithoutTime();
+        Assertions.assertTrue("c==>ifn2".equals(stepStr2) || "ifn2==>c".equals(stepStr2));
+    }
+}

+ 14 - 0
liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/ACmp.java

@@ -0,0 +1,14 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeComponent;
+
+@LiteflowComponent("a")
+public class ACmp extends NodeComponent {
+
+    @Override
+    public void process() {
+        System.out.println("ACmp executed!");
+    }
+
+}

+ 17 - 0
liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/BCmp.java

@@ -0,0 +1,17 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.test.customNodes.domain.DemoDomain;
+
+import javax.annotation.Resource;
+
+@LiteflowComponent("b")
+public class BCmp extends NodeComponent {
+
+    @Override
+    public void process() {
+        System.out.println("BCmp executed!");
+    }
+
+}

+ 15 - 0
liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/BreakCmp.java

@@ -0,0 +1,15 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.FallbackCmp;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeBreakComponent;
+
+@LiteflowComponent("bn1")
+@FallbackCmp
+public class BreakCmp extends NodeBreakComponent {
+
+    @Override
+    public boolean processBreak() throws Exception {
+        return true;
+    }
+}

+ 18 - 0
liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/CCmp.java

@@ -0,0 +1,18 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.FallbackCmp;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.slot.DefaultContext;
+import org.springframework.stereotype.Component;
+
+@LiteflowComponent("c")
+@FallbackCmp
+public class CCmp extends NodeComponent {
+
+    @Override
+    public void process() {
+        System.out.println("CCmp executed!");
+    }
+
+}

+ 13 - 0
liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/DCmp.java

@@ -0,0 +1,13 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeComponent;
+
+@LiteflowComponent("d")
+public class DCmp extends NodeComponent {
+
+    @Override
+    public void process() throws Exception {
+        throw new RuntimeException("component[d]");
+    }
+}

+ 15 - 0
liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/ForCmp.java

@@ -0,0 +1,15 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.FallbackCmp;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeForComponent;
+
+@LiteflowComponent("for1")
+@FallbackCmp
+public class ForCmp extends NodeForComponent {
+
+    @Override
+    public int processFor() throws Exception {
+        return 3;
+    }
+}

+ 13 - 0
liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/IfCmp1.java

@@ -0,0 +1,13 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeIfComponent;
+
+@LiteflowComponent("ifn1")
+public class IfCmp1 extends NodeIfComponent {
+
+    @Override
+    public boolean processIf() throws Exception {
+        return true;
+    }
+}

+ 15 - 0
liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/IfCmp2.java

@@ -0,0 +1,15 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.FallbackCmp;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeIfComponent;
+
+@LiteflowComponent("ifn2")
+@FallbackCmp
+public class IfCmp2 extends NodeIfComponent {
+
+    @Override
+    public boolean processIf() throws Exception {
+        return false;
+    }
+}

+ 16 - 0
liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/IteratorCmp1.java

@@ -0,0 +1,16 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeIteratorComponent;
+
+import java.util.Arrays;
+import java.util.Iterator;
+
+@LiteflowComponent("itn1")
+public class IteratorCmp1 extends NodeIteratorComponent {
+
+    @Override
+    public Iterator<?> processIterator() throws Exception {
+        return Arrays.asList("a", "b", "c").iterator();
+    }
+}

+ 18 - 0
liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/IteratorCmp2.java

@@ -0,0 +1,18 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.FallbackCmp;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeIteratorComponent;
+
+import java.util.Collections;
+import java.util.Iterator;
+
+@LiteflowComponent("itn2")
+@FallbackCmp
+public class IteratorCmp2 extends NodeIteratorComponent {
+
+    @Override
+    public Iterator<?> processIterator() throws Exception {
+        return Collections.emptyIterator();
+    }
+}

+ 13 - 0
liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/SwitchCmp1.java

@@ -0,0 +1,13 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeSwitchComponent;
+
+@LiteflowComponent("swn1")
+public class SwitchCmp1 extends NodeSwitchComponent {
+
+    @Override
+    public String processSwitch() throws Exception {
+        return "a";
+    }
+}

+ 15 - 0
liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/SwitchCmp2.java

@@ -0,0 +1,15 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.FallbackCmp;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeSwitchComponent;
+
+@LiteflowComponent("swn2")
+@FallbackCmp
+public class SwitchCmp2 extends NodeSwitchComponent {
+
+    @Override
+    public String processSwitch() throws Exception {
+        return "b";
+    }
+}

+ 26 - 0
liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/WhileCmp1.java

@@ -0,0 +1,26 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeWhileComponent;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@LiteflowComponent("wn1")
+public class WhileCmp1 extends NodeWhileComponent {
+    private int count = 0;
+
+    // 执行过的 chain
+    Set<String> executedChain = new HashSet<>();
+
+    @Override
+    public boolean processWhile() throws Exception {
+        // 判断是否切换了 chain
+        if (!executedChain.contains(this.getCurrChainId())) {
+            count = 0;
+            executedChain.add(this.getCurrChainId());
+        }
+        count++;
+        return count <= 3;
+    }
+}

+ 15 - 0
liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/WhileCmp2.java

@@ -0,0 +1,15 @@
+package com.yomahub.liteflow.test.fallback.cmp;
+
+import com.yomahub.liteflow.annotation.FallbackCmp;
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeWhileComponent;
+
+@LiteflowComponent("wn2")
+@FallbackCmp
+public class WhileCmp2 extends NodeWhileComponent {
+    
+    @Override
+    public boolean processWhile() throws Exception {
+        return false;
+    }
+}

+ 0 - 50
liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/substituteNode/SubstituteSpringbootTest.java

@@ -1,50 +0,0 @@
-package com.yomahub.liteflow.test.substituteNode;
-
-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;
-
-/**
- * springboot环境EL替补节点的测试
- *
- * @author Bryan.Zhang
- */
-@TestPropertySource(value = "classpath:/substituteNode/application.properties")
-@SpringBootTest(classes = SubstituteSpringbootTest.class)
-@EnableAutoConfiguration
-@ComponentScan({ "com.yomahub.liteflow.test.substituteNode.cmp" })
-public class SubstituteSpringbootTest extends BaseTest {
-
-	@Resource
-	private FlowExecutor flowExecutor;
-
-	// 最简单的情况
-	@Test
-	public void testSub1() throws Exception {
-		LiteflowResponse response = flowExecutor.execute2Resp("chain1", "arg");
-		Assertions.assertTrue(response.isSuccess());
-	}
-
-	// 有替补节点
-	@Test
-	public void testSub2() throws Exception {
-		LiteflowResponse response = flowExecutor.execute2Resp("chain2", "arg");
-		Assertions.assertTrue(response.isSuccess());
-	}
-
-	// 测试特殊命名的节点
-	@Test
-	public void testSub3() throws Exception {
-		LiteflowResponse response = flowExecutor.execute2Resp("chain3", "arg");
-		Assertions.assertTrue(response.isSuccess());
-	}
-
-}

+ 0 - 21
liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/substituteNode/cmp/ACmp.java

@@ -1,21 +0,0 @@
-/**
- * <p>Title: liteflow</p>
- * <p>Description: 轻量级的组件式流程框架</p>
- * @author Bryan.Zhang
- * @email weenyc31@163.com
- * @Date 2020/4/1
- */
-package com.yomahub.liteflow.test.substituteNode.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!");
-	}
-
-}

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

@@ -1,21 +0,0 @@
-/**
- * <p>Title: liteflow</p>
- * <p>Description: 轻量级的组件式流程框架</p>
- * @author Bryan.Zhang
- * @email weenyc31@163.com
- * @Date 2020/4/1
- */
-package com.yomahub.liteflow.test.substituteNode.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!");
-	}
-
-}

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

@@ -1,21 +0,0 @@
-/**
- * <p>Title: liteflow</p>
- * <p>Description: 轻量级的组件式流程框架</p>
- * @author Bryan.Zhang
- * @email weenyc31@163.com
- * @Date 2020/4/1
- */
-package com.yomahub.liteflow.test.substituteNode.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!");
-	}
-
-}

+ 0 - 21
liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/substituteNode/cmp/DCmp.java

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

+ 0 - 21
liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/substituteNode/cmp/SubCmp.java

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

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

@@ -0,0 +1,2 @@
+liteflow.rule-source=fallback/flow.el.xml
+liteflow.fallback-cmp-enable=true

+ 136 - 0
liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/fallback/flow.el.xml

@@ -0,0 +1,136 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<flow>
+    <!-- THEN 普通组件降级 -->
+    <chain name="then1">
+        THEN(a, node("x"));
+    </chain>
+    
+    <chain name="then2">
+        THEN(PRE(node("x1")), node("x2"), FINALLY(node("x3")));
+    </chain>
+
+    <!-- WHEN 普通组件降级 -->
+    <chain name="when1">
+        WHEN(b, node("x"));
+    </chain>
+
+    <!-- IF 条件组件降级 -->
+    <chain name="if1">
+        IF(node("x"), a)
+    </chain>
+    
+    <!-- IF 普通组件降级 -->
+    <chain name="if2">
+        IF(ifn1, node("x"))
+    </chain>
+    
+    <!-- FOR 次数循环组件降级 -->
+    <chain name="for1">
+        FOR(node("x")).DO(a);
+    </chain>
+    
+    <!-- FOR 普通组件降级 -->
+    <chain name="for2">
+        FOR(3).DO(node("x"));
+    </chain>
+    
+    <!-- WHILE 条件循环组件降级 -->
+    <chain name="while1">
+        WHILE(node("x")).DO(a)
+    </chain>
+    
+    <!-- WHILE 普通组件降级 -->
+    <chain name="while2">
+        WHILE(wn1).DO(node("x"))
+    </chain>
+    
+    <!-- ITERATOR 迭代组件降级 -->
+    <chain name="iterator1">
+        ITERATOR(node("x")).DO(a)
+    </chain>
+    
+    <!-- ITERATOR 普通组件降级 -->
+    <chain name="iterator2">
+        ITERATOR(itn1).DO(node("x"))
+    </chain>
+    
+    <!-- BREAK 退出循环组件降级 -->
+    <chain name="break1">
+        FOR(3).DO(a).BREAK(node("x"));
+    </chain>
+    
+    <chain name="break2">
+        WHILE(wn1).DO(a).BREAK(node("x"));
+    </chain>
+    
+    <chain name="break3">
+        ITERATOR(itn1).DO(a).BREAK(node("x"));
+    </chain>
+    
+    <!-- SWITCH 选择组件降级 -->
+    <chain name="switch1">
+        SWITCH(node("x")).to(a,b);
+    </chain>
+    
+    <!-- SWITCH 普通组件降级 -->
+    <chain name="switch2">
+        SWITCH(swn1).to(node("x"),a);
+    </chain>
+    
+    <!-- AND 条件组件降级 -->
+    <chain name="and1">
+        IF(AND(node("x"),ifn1), a);
+    </chain>
+    
+    <!-- OR 条件组件降级 -->
+    <chain name="or1">
+        IF(OR(node("x"),ifn1), a);
+    </chain>
+    
+    <!-- NOT 条件组件降级 -->
+    <chain name="not1">
+        IF(NOT(node("x")), a);
+    </chain>
+    
+    <!-- CATCH 普通组件降级 -->
+    <chain name="catch1">
+            CATCH(THEN(a, d)).DO(node("x"))
+    </chain>
+    
+    <!-- 多个组件降级 -->
+    <chain name="multi1">
+        THEN(
+            a,
+            node("x1"),
+            IF(node("x2"), b)
+        );
+    </chain>
+    
+    <chain name="multi2">
+        IF(
+            OR(node("x1"), ifn1),
+            THEN(a, node("x2"))
+        );
+    </chain>
+    
+    <chain name="multi3">
+        FOR(node("x1")).DO(
+            THEN(b, node("x2"))
+        );
+    </chain>
+
+    <!-- 并发降级测试 -->
+    <chain name="concurrent1">
+        WHEN(
+            THEN(node("x1")),
+            IF(node("x2"), b)
+        ).maxWaitSeconds(10000);
+    </chain>
+
+    <chain name="concurrent2">
+        WHEN(
+            node("x1"),
+            IF(node("x2"), b)
+        ).maxWaitSeconds(10000);
+    </chain>
+</flow>

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini