Browse Source

feature #I4CRSY 在一个流程中的同名组件无法传递不同的参数

bryan31 3 years ago
parent
commit
0600100029
18 changed files with 356 additions and 59 deletions
  1. 24 2
      liteflow-core/src/main/java/com/yomahub/liteflow/core/NodeComponent.java
  2. 0 6
      liteflow-core/src/main/java/com/yomahub/liteflow/entity/flow/Chain.java
  3. 2 1
      liteflow-core/src/main/java/com/yomahub/liteflow/entity/flow/Executable.java
  4. 25 1
      liteflow-core/src/main/java/com/yomahub/liteflow/entity/flow/Node.java
  5. 14 1
      liteflow-core/src/main/java/com/yomahub/liteflow/flow/FlowBus.java
  6. 2 20
      liteflow-core/src/main/java/com/yomahub/liteflow/parser/FlowParser.java
  7. 13 11
      liteflow-core/src/main/java/com/yomahub/liteflow/parser/JsonFlowParser.java
  8. 33 6
      liteflow-core/src/main/java/com/yomahub/liteflow/parser/RegexEntity.java
  9. 51 0
      liteflow-core/src/main/java/com/yomahub/liteflow/parser/RegexNodeEntity.java
  10. 13 11
      liteflow-core/src/main/java/com/yomahub/liteflow/parser/XmlFlowParser.java
  11. 47 0
      liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/tag/NodeTagSpringbootTest.java
  12. 32 0
      liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/tag/cmp/ACmp.java
  13. 22 0
      liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/tag/cmp/BCmp.java
  14. 25 0
      liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/tag/cmp/CCmp.java
  15. 21 0
      liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/tag/cmp/DCmp.java
  16. 21 0
      liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/tag/cmp/ECmp.java
  17. 1 0
      liteflow-testcase-springboot/src/test/resources/tag/application.properties
  18. 10 0
      liteflow-testcase-springboot/src/test/resources/tag/flow.xml

+ 24 - 2
liteflow-core/src/main/java/com/yomahub/liteflow/core/NodeComponent.java

@@ -27,6 +27,9 @@ import com.yomahub.liteflow.flow.FlowBus;
 import com.yomahub.liteflow.monitor.MonitorBus;
 import com.yomahub.liteflow.spring.ComponentScanner;
 
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * 普通组件抽象类
  * @author Bryan.Zhang
@@ -44,6 +47,10 @@ public abstract class NodeComponent {
 
 	private String name;
 
+	private String tag;
+
+	private Map<String, Executable> condNodeMap;
+
 	private NodeTypeEnum type;
 
 	//这是自己的实例,取代this
@@ -90,8 +97,7 @@ public abstract class NodeComponent {
 		if (this instanceof NodeCondComponent) {
 			String condNodeId = slot.getCondResult(this.getClass().getName());
 			if (StrUtil.isNotBlank(condNodeId)) {
-				Node thisNode = FlowBus.getNode(nodeId);
-				Executable condExecutor = thisNode.getCondNode(condNodeId);
+				Executable condExecutor = condNodeMap.get(condNodeId);
 				if (ObjectUtil.isNotNull(condExecutor)) {
 					condExecutor.execute(slotIndexTL.get());
 				}
@@ -213,4 +219,20 @@ public abstract class NodeComponent {
 	public void setRetryForExceptions(Class<? extends Exception>[] retryForExceptions) {
 		this.retryForExceptions = retryForExceptions;
 	}
+
+	public String getTag() {
+		return tag;
+	}
+
+	public void setTag(String tag) {
+		this.tag = tag;
+	}
+
+	public Map<String, Executable> getCondNodeMap() {
+		return condNodeMap;
+	}
+
+	public void setCondNodeMap(Map<String, Executable> condNodeMap) {
+		this.condNodeMap = condNodeMap;
+	}
 }

+ 0 - 6
liteflow-core/src/main/java/com/yomahub/liteflow/entity/flow/Chain.java

@@ -9,28 +9,22 @@
 package com.yomahub.liteflow.entity.flow;
 
 import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import com.yomahub.liteflow.entity.data.DataBus;
 import com.yomahub.liteflow.entity.data.Slot;
 import com.yomahub.liteflow.enums.ExecuteTypeEnum;
 import com.yomahub.liteflow.exception.ChainEndException;
-import com.yomahub.liteflow.exception.ConfigErrorException;
 import com.yomahub.liteflow.exception.FlowSystemException;
 import com.yomahub.liteflow.exception.WhenExecuteException;
 import com.yomahub.liteflow.property.LiteflowConfig;
 import com.yomahub.liteflow.property.LiteflowConfigGetter;
 import com.yomahub.liteflow.util.ExecutorHelper;
-import com.yomahub.liteflow.util.SpringAware;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.*;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
 
 /**
  * chain对象,实现可执行器

+ 2 - 1
liteflow-core/src/main/java/com/yomahub/liteflow/entity/flow/Executable.java

@@ -5,9 +5,10 @@ import com.yomahub.liteflow.enums.ExecuteTypeEnum;
 /**
  * 可执行器接口
  * 目前实现这个接口的有2个,node和chain
+ *
  * @author Bryan.Zhang
  */
-public interface Executable {
+public interface Executable{
 
     void execute(Integer slotIndex) throws Exception;
 

+ 25 - 1
liteflow-core/src/main/java/com/yomahub/liteflow/entity/flow/Node.java

@@ -27,7 +27,7 @@ import org.slf4j.LoggerFactory;
  * Node节点,实现可执行器
  * @author Bryan.Zhang
  */
-public class Node implements Executable{
+public class Node implements Executable,Cloneable{
 
 	private static final Logger LOG = LoggerFactory.getLogger(Node.class);
 
@@ -35,6 +35,8 @@ public class Node implements Executable{
 
 	private String name;
 
+	private String tag;
+
 	private NodeTypeEnum type;
 
 	private NodeComponent instance;
@@ -107,6 +109,11 @@ public class Node implements Executable{
 			//判断是否可执行,所以isAccess经常作为一个组件进入的实际判断要素,用作检查slot里的参数的完备性
 			if (instance.isAccess()) {
 
+				//把tag和condNodeMap赋给NodeComponent
+				//这里为什么要这么做?因为tag和condNodeMap从某种意义上来说是属于某个chain本身范围,并非全局的
+				instance.setTag(tag);
+				instance.setCondNodeMap(condNodeMap);
+
 				//执行业务逻辑的主要入口
 				instance.execute();
 
@@ -135,6 +142,15 @@ public class Node implements Executable{
 		}
 	}
 
+	@Override
+	protected Object clone() throws CloneNotSupportedException {
+		return super.clone();
+	}
+
+	public Node copy() throws Exception{
+		return (Node) this.clone();
+	}
+
 	@Override
 	public ExecuteTypeEnum getExecuteType() {
 		return ExecuteTypeEnum.NODE;
@@ -144,4 +160,12 @@ public class Node implements Executable{
 	public String getExecuteName() {
 		return id;
 	}
+
+	public String getTag() {
+		return tag;
+	}
+
+	public void setTag(String tag) {
+		this.tag = tag;
+	}
 }

+ 14 - 1
liteflow-core/src/main/java/com/yomahub/liteflow/flow/FlowBus.java

@@ -30,6 +30,7 @@ import com.yomahub.liteflow.script.exception.ScriptSpiException;
 import com.yomahub.liteflow.util.SpringAware;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.util.SerializationUtils;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -132,6 +133,18 @@ public class FlowBus {
         return nodeMap.get(nodeId);
     }
 
+    //虽然实现了cloneable,但是还是浅copy,因为condNodeMap这个对象还是共用的。
+    //那condNodeMap共用有关系么,原则上没有关系。但是从设计理念上,以后应该要分开
+    //tag和condNodeMap这2个属性不属于全局概念,属于每个chain范围的属性
+    public static Node copyNode(String nodeId) {
+        try{
+            Node node = nodeMap.get(nodeId);
+            return node.copy();
+        }catch (Exception e){
+            return null;
+        }
+    }
+
     public static void cleanCache() {
         chainMap.clear();
         nodeMap.clear();
@@ -141,7 +154,7 @@ public class FlowBus {
             if (ObjectUtil.isNotNull(scriptExecutor)){
                 scriptExecutor.cleanCache();
             }
-        }catch (ScriptSpiException e){}
+        }catch (ScriptSpiException ignored){}
     }
 
     //目前这种方式刷新不完全平滑

+ 2 - 20
liteflow-core/src/main/java/com/yomahub/liteflow/parser/FlowParser.java

@@ -32,27 +32,9 @@ public abstract class FlowParser {
 
     public abstract void parse(List<String> contentList) throws Exception ;
 
-    private static final Pattern p = Pattern.compile("[^\\)\\(]+");
+    private static final Pattern p1 = Pattern.compile("[^\\)\\(]+");
 
-    //条件节点的正则解析
-    public RegexEntity parseNodeStr(String str) {
-        List<String> list = new ArrayList<String>();
-
-        Matcher m = p.matcher(str);
-        while(m.find()){
-            list.add(m.group());
-        }
-        RegexEntity regexEntity = new RegexEntity();
-        regexEntity.setItem(list.get(0).trim());
-        if(list.size() > 1){
-            String[] realNodeArray = list.get(1).split("\\|");
-            for (int i = 0; i < realNodeArray.length; i++) {
-                realNodeArray[i] = realNodeArray[i].trim();
-            }
-            regexEntity.setRealItemArray(realNodeArray);
-        }
-        return regexEntity;
-    }
+    private static final Pattern p2 = Pattern.compile("[^\\[\\]]+");
 
     protected void buildBaseFlowConditions(List<Condition> conditionList,Condition condition){
         if (condition.getConditionType().equals(ConditionTypeEnum.TYPE_THEN.getType())) {

+ 13 - 11
liteflow-core/src/main/java/com/yomahub/liteflow/parser/JsonFlowParser.java

@@ -131,29 +131,31 @@ public abstract class JsonFlowParser extends FlowParser {
             condArray = condArrayStr.split(",");
             RegexEntity regexEntity;
             String itemExpression;
-            String item;
+            RegexNodeEntity item;
             //这里解析的规则,优先按照node去解析,再按照chain去解析
             for (int i = 0; i < condArray.length; i++) {
                 itemExpression = condArray[i].trim();
-                regexEntity = parseNodeStr(itemExpression);
+                regexEntity = RegexEntity.parse(itemExpression);
                 item = regexEntity.getItem();
-                if (FlowBus.containNode(item)) {
-                    Node node = FlowBus.getNode(item);
+                if (FlowBus.containNode(item.getId())) {
+                    Node node = FlowBus.copyNode(item.getId());
+                    node.setTag(regexEntity.getItem().getTag());
                     chainNodeList.add(node);
                     //这里判断是不是条件节点,条件节点会含有realItem,也就是括号里的node
                     if (regexEntity.getRealItemArray() != null) {
-                        for (String key : regexEntity.getRealItemArray()) {
-                            if (FlowBus.containNode(key)) {
-                                Node condNode = FlowBus.getNode(key);
+                        for (RegexNodeEntity realItem : regexEntity.getRealItemArray()) {
+                            if (FlowBus.containNode(realItem.getId())) {
+                                Node condNode = FlowBus.copyNode(realItem.getId());
+                                node.setTag(realItem.getTag());
                                 node.setCondNode(condNode.getId(), condNode);
-                            } else if (hasChain(flowJsonObjectList, key)) {
-                                Chain chain = FlowBus.getChain(key);
+                            } else if (hasChain(flowJsonObjectList, realItem.getId())) {
+                                Chain chain = FlowBus.getChain(realItem.getId());
                                 node.setCondNode(chain.getChainName(), chain);
                             }
                         }
                     }
-                } else if (hasChain(flowJsonObjectList, item)) {
-                    Chain chain = FlowBus.getChain(item);
+                } else if (hasChain(flowJsonObjectList, item.getId())) {
+                    Chain chain = FlowBus.getChain(item.getId());
                     chainNodeList.add(chain);
                 } else {
                     String errorMsg = StrUtil.format("executable node[{}] is not found!", regexEntity.getItem());

+ 33 - 6
liteflow-core/src/main/java/com/yomahub/liteflow/parser/RegexEntity.java

@@ -7,29 +7,56 @@
  */
 package com.yomahub.liteflow.parser;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 /**
  * 正则实体,主要用于条件节点
  * @author Bryan.Zhang
  */
 public class RegexEntity {
 
-	private String item;
+	private static final Pattern p = Pattern.compile("[^\\)\\(]+");
+
+	private RegexNodeEntity item;
+
+	private RegexNodeEntity[] realItemArray;
 
-	private String[] realItemArray;
+	public static RegexEntity parse(String nodeStr){
+		List<String> list = new ArrayList<String>();
+		Matcher m = p.matcher(nodeStr);
+		while(m.find()){
+			list.add(m.group());
+		}
+
+		RegexEntity regexEntity = new RegexEntity();
+		regexEntity.setItem(RegexNodeEntity.parse(list.get(0)));
+		try{
+			String[] array = list.get(1).split("\\|");
+			List<RegexNodeEntity> regexNodeEntityList = new ArrayList<>();
+			for (String itemStr : array){
+				regexNodeEntityList.add(RegexNodeEntity.parse(itemStr));
+			}
+			regexEntity.setRealItemArray(regexNodeEntityList.toArray(new RegexNodeEntity[]{}));
+		}catch (Exception ignored){}
+		return regexEntity;
+	}
 
-	public String getItem() {
+	public RegexNodeEntity getItem() {
 		return item;
 	}
 
-	public void setItem(String item) {
+	public void setItem(RegexNodeEntity item) {
 		this.item = item;
 	}
 
-	public String[] getRealItemArray() {
+	public RegexNodeEntity[] getRealItemArray() {
 		return realItemArray;
 	}
 
-	public void setRealItemArray(String[] realItemArray) {
+	public void setRealItemArray(RegexNodeEntity[] realItemArray) {
 		this.realItemArray = realItemArray;
 	}
 }

+ 51 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/parser/RegexNodeEntity.java

@@ -0,0 +1,51 @@
+package com.yomahub.liteflow.parser;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * 节点解析,主要用于解析节点name的重命名
+ * @author Bryan.Zhang
+ * @since 2.6.2
+ */
+public class RegexNodeEntity {
+
+    private static final Pattern p = Pattern.compile("[^\\[\\]]+");
+
+    private String id;
+
+    private String tag;
+
+    public static RegexNodeEntity parse(String itemStr){
+        List<String> list = new ArrayList<String>();
+        Matcher m = p.matcher(itemStr);
+        while(m.find()){
+            list.add(m.group());
+        }
+
+        RegexNodeEntity regexNodeEntity = new RegexNodeEntity();
+        regexNodeEntity.setId(list.get(0));
+        try{
+            regexNodeEntity.setTag(list.get(1));
+        }catch (Exception ignored){}
+        return regexNodeEntity;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getTag() {
+        return tag;
+    }
+
+    public void setTag(String tag) {
+        this.tag = tag;
+    }
+}

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

@@ -148,29 +148,31 @@ public abstract class XmlFlowParser extends FlowParser {
             condArray = condArrayStr.split(",");
             RegexEntity regexEntity;
             String itemExpression;
-            String item;
+            RegexNodeEntity item;
             //这里解析的规则,优先按照node去解析,再按照chain去解析
             for (int i = 0; i < condArray.length; i++) {
                 itemExpression = condArray[i].trim();
-                regexEntity = parseNodeStr(itemExpression);
+                regexEntity = RegexEntity.parse(itemExpression);
                 item = regexEntity.getItem();
-                if (FlowBus.containNode(item)) {
-                    Node node = FlowBus.getNode(item);
+                if (FlowBus.containNode(item.getId())) {
+                    Node node = FlowBus.copyNode(item.getId());
+                    node.setTag(regexEntity.getItem().getTag());
                     chainNodeList.add(node);
                     //这里判断是不是条件节点,条件节点会含有realItem,也就是括号里的node
                     if (regexEntity.getRealItemArray() != null) {
-                        for (String key : regexEntity.getRealItemArray()) {
-                            if (FlowBus.containNode(key)) {
-                                Node condNode = FlowBus.getNode(key);
+                        for (RegexNodeEntity realItem : regexEntity.getRealItemArray()) {
+                            if (FlowBus.containNode(realItem.getId())) {
+                                Node condNode = FlowBus.copyNode(realItem.getId());
+                                condNode.setTag(realItem.getTag());
                                 node.setCondNode(condNode.getId(), condNode);
-                            } else if (hasChain(documentList, key)) {
-                                Chain chain = FlowBus.getChain(key);
+                            } else if (hasChain(documentList, realItem.getId())) {
+                                Chain chain = FlowBus.getChain(realItem.getId());
                                 node.setCondNode(chain.getChainName(), chain);
                             }
                         }
                     }
-                } else if (hasChain(documentList, item)) {
-                    Chain chain = FlowBus.getChain(item);
+                } else if (hasChain(documentList, item.getId())) {
+                    Chain chain = FlowBus.getChain(item.getId());
                     chainNodeList.add(chain);
                 } else {
                     String errorMsg = StrUtil.format("executable node[{}] is not found!", regexEntity.getItem());

+ 47 - 0
liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/tag/NodeTagSpringbootTest.java

@@ -0,0 +1,47 @@
+package com.yomahub.liteflow.test.tag;
+
+import com.yomahub.liteflow.core.FlowExecutor;
+import com.yomahub.liteflow.entity.data.DefaultSlot;
+import com.yomahub.liteflow.entity.data.LiteflowResponse;
+import com.yomahub.liteflow.test.BaseTest;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import javax.annotation.Resource;
+import java.util.Set;
+
+/**
+ * springboot环境下隐私投递的测试
+ * @author Bryan.Zhang
+ * @since 2.5.0
+ */
+@RunWith(SpringRunner.class)
+@TestPropertySource(value = "classpath:/tag/application.properties")
+@SpringBootTest(classes = NodeTagSpringbootTest.class)
+@EnableAutoConfiguration
+@ComponentScan({"com.yomahub.liteflow.test.tag.cmp"})
+public class NodeTagSpringbootTest extends BaseTest {
+
+    @Resource
+    private FlowExecutor flowExecutor;
+
+    @Test
+    public void testTag1() throws Exception{
+        LiteflowResponse<DefaultSlot> response = flowExecutor.execute2Resp("chain1", "arg");
+        Assert.assertTrue(response.isSuccess());
+        Assert.assertEquals("123",response.getSlot().getData("test"));
+    }
+
+    @Test
+    public void testTag2() throws Exception{
+        LiteflowResponse<DefaultSlot> response = flowExecutor.execute2Resp("chain2", "arg");
+        Assert.assertTrue(response.isSuccess());
+        Assert.assertEquals("a==>a==>a==>c==>e", response.getSlot().printStep());
+    }
+}

+ 32 - 0
liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/tag/cmp/ACmp.java

@@ -0,0 +1,32 @@
+/**
+ * <p>Title: liteflow</p>
+ * <p>Description: 轻量级的组件式流程框架</p>
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.test.tag.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.entity.data.Slot;
+
+import java.util.HashSet;
+
+@LiteflowComponent("a")
+public class ACmp extends NodeComponent {
+
+	@Override
+	public void process() {
+		String testKey = "test";
+
+		Slot slot = this.getSlot();
+		if (slot.getData(testKey) == null){
+			slot.setData(testKey,this.getTag());
+		}else{
+			String s = slot.getData(testKey);
+			s += this.getTag();
+			slot.setData(testKey, s);
+		}
+	}
+}

+ 22 - 0
liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/tag/cmp/BCmp.java

@@ -0,0 +1,22 @@
+/**
+ * <p>Title: liteflow</p>
+ * <p>Description: 轻量级的组件式流程框架</p>
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.test.tag.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeComponent;
+
+import java.util.Set;
+
+@LiteflowComponent("b")
+public class BCmp extends NodeComponent {
+
+	@Override
+	public void process() {
+		System.out.println(this.getTag());
+	}
+}

+ 25 - 0
liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/tag/cmp/CCmp.java

@@ -0,0 +1,25 @@
+/**
+ * <p>Title: liteflow</p>
+ * <p>Description: 轻量级的组件式流程框架</p>
+ * @author Bryan.Zhang
+ * @email weenyc31@163.com
+ * @Date 2020/4/1
+ */
+package com.yomahub.liteflow.test.tag.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeComponent;
+import com.yomahub.liteflow.core.NodeCondComponent;
+
+@LiteflowComponent("c")
+public class CCmp extends NodeCondComponent {
+
+	@Override
+	public String processCond() throws Exception {
+		if(this.getTag().equals("2")){
+			return "e";
+		}else{
+			return "d";
+		}
+	}
+}

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

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

+ 21 - 0
liteflow-testcase-springboot/src/test/java/com/yomahub/liteflow/test/tag/cmp/ECmp.java

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

+ 1 - 0
liteflow-testcase-springboot/src/test/resources/tag/application.properties

@@ -0,0 +1 @@
+liteflow.rule-source=tag/flow.xml

+ 10 - 0
liteflow-testcase-springboot/src/test/resources/tag/flow.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<flow>
+    <chain name="chain1">
+        <then value="a[1],a[2],a[3]"/>
+    </chain>
+
+    <chain name="chain2">
+        <then value="a[1],a[2],a[3],c[2](d[5]|e[6])"/>
+    </chain>
+</flow>