Преглед изворни кода

feature #I96A33 为LF增加决策表特性

everywhere.z пре 1 година
родитељ
комит
9b8902c01c

+ 46 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/LiteFlowChainELBuilder.java

@@ -21,6 +21,7 @@ import com.yomahub.liteflow.exception.ParseException;
 import com.yomahub.liteflow.flow.FlowBus;
 import com.yomahub.liteflow.flow.element.Chain;
 import com.yomahub.liteflow.flow.element.Condition;
+import com.yomahub.liteflow.flow.element.Executable;
 import com.yomahub.liteflow.flow.element.Node;
 import com.yomahub.liteflow.log.LFLog;
 import com.yomahub.liteflow.log.LFLoggerManager;
@@ -44,6 +45,11 @@ public class LiteFlowChainELBuilder {
 
 	private Chain chain;
 
+	/**
+	 * 这是route EL的文本
+	 */
+	private Executable route;
+
 	/**
 	 * 这是主体的Condition //声明这个变量,而不是用chain.getConditionList的目的,是为了辅助平滑加载
 	 * 虽然FlowBus里面的map都是CopyOnWrite类型的,但是在buildCondition的时候,为了平滑加载,所以不能事先把chain.getConditionList给设为空List
@@ -130,6 +136,45 @@ public class LiteFlowChainELBuilder {
 		return this;
 	}
 
+	public LiteFlowChainELBuilder setRoute(String routeEl){
+		if (StrUtil.isBlank(routeEl)) {
+			String errMsg = StrUtil.format("You have defined the label <route> but there is no content in the chain[{}].", chain.getChainId());
+			throw new FlowSystemException(errMsg);
+		}
+		List<String> errorList = new ArrayList<>();
+		try {
+			DefaultContext<String, Object> context = new DefaultContext<>();
+
+			// 往上下文里放入所有的node,使得el表达式可以直接引用到nodeId
+			FlowBus.getNodeMap().keySet().forEach(nodeId -> context.put(nodeId, FlowBus.getNode(nodeId)));
+
+			// 解析route el成为一个executable
+			Executable routeExecutable = (Executable) EXPRESS_RUNNER.execute(routeEl, context, errorList, true, true);
+
+			if (Objects.isNull(routeExecutable)){
+				throw new QLException(StrUtil.format("parse route el fail,el:[{}]", routeEl));
+			}
+
+			// 把主要的condition加入
+			this.route = routeExecutable;
+			return this;
+		} catch (QLException e) {
+			// EL 底层会包装异常,这里是曲线处理
+			if (ObjectUtil.isNotNull(e.getCause()) && Objects.equals(e.getCause().getMessage(), DataNotFoundException.MSG)) {
+				// 构建错误信息
+				String msg = buildDataNotFoundExceptionMsg(routeEl);
+				throw new ELParseException(msg);
+			}else if (ObjectUtil.isNotNull(e.getCause())){
+				throw new ELParseException(e.getCause().getMessage());
+			}else{
+				throw new ELParseException(e.getMessage());
+			}
+		} catch (Exception e) {
+			String errMsg = StrUtil.format("parse el fail in this chain[{}];\r\n", chain.getChainId());
+			throw new ELParseException(errMsg + e.getMessage());
+		}
+	}
+
 	public LiteFlowChainELBuilder setEL(String elStr) {
 		if (StrUtil.isBlank(elStr)) {
 			String errMsg = StrUtil.format("no content in this chain[{}]", chain.getChainId());
@@ -197,6 +242,7 @@ public class LiteFlowChainELBuilder {
 	}
 
 	public void build() {
+		this.chain.setRouteItem(this.route);
 		this.chain.setConditionList(this.conditionList);
 
 		//暂且去掉循环依赖检测,因为有发现循环依赖检测在对大的EL进行检测的时候,会导致CPU飙升,也或许是jackson低版本的问题

+ 4 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/common/ChainConstant.java

@@ -10,6 +10,10 @@ public interface ChainConstant {
 
 	String CHAIN = "chain";
 
+	String ROUTE = "route";
+
+	String BODY = "body";
+
 	String FLOW = "flow";
 
 	String NODES = "nodes";

+ 10 - 0
liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/Chain.java

@@ -30,6 +30,8 @@ public class Chain implements Executable{
 
 	private String chainId;
 
+	private Executable routeItem;
+
 	private List<Condition> conditionList = new ArrayList<>();
 
 	public Chain(String chainName) {
@@ -133,4 +135,12 @@ public class Chain implements Executable{
 	public String getTag() {
 		return null;
 	}
+
+	public Executable getRouteItem() {
+		return routeItem;
+	}
+
+	public void setRouteItem(Executable routeItem) {
+		this.routeItem = routeItem;
+	}
 }

+ 44 - 29
liteflow-core/src/main/java/com/yomahub/liteflow/parser/helper/ParserHelper.java

@@ -8,12 +8,7 @@ import com.yomahub.liteflow.builder.LiteFlowNodeBuilder;
 import com.yomahub.liteflow.builder.el.LiteFlowChainELBuilder;
 import com.yomahub.liteflow.builder.prop.NodePropBean;
 import com.yomahub.liteflow.enums.NodeTypeEnum;
-import com.yomahub.liteflow.exception.ChainDuplicateException;
-import com.yomahub.liteflow.exception.ChainNotFoundException;
-import com.yomahub.liteflow.exception.NodeClassNotFoundException;
-import com.yomahub.liteflow.exception.NodeTypeCanNotGuessException;
-import com.yomahub.liteflow.exception.NodeTypeNotSupportException;
-import com.yomahub.liteflow.exception.ParseException;
+import com.yomahub.liteflow.exception.*;
 import com.yomahub.liteflow.flow.FlowBus;
 import com.yomahub.liteflow.flow.element.Chain;
 import com.yomahub.liteflow.flow.element.condition.AbstractCondition;
@@ -30,18 +25,7 @@ import java.util.Optional;
 import java.util.Set;
 import java.util.function.Consumer;
 
-import static com.yomahub.liteflow.common.ChainConstant.CHAIN;
-import static com.yomahub.liteflow.common.ChainConstant.EXTENDS;
-import static com.yomahub.liteflow.common.ChainConstant.FILE;
-import static com.yomahub.liteflow.common.ChainConstant.FLOW;
-import static com.yomahub.liteflow.common.ChainConstant.ID;
-import static com.yomahub.liteflow.common.ChainConstant.LANGUAGE;
-import static com.yomahub.liteflow.common.ChainConstant.NAME;
-import static com.yomahub.liteflow.common.ChainConstant.NODE;
-import static com.yomahub.liteflow.common.ChainConstant.NODES;
-import static com.yomahub.liteflow.common.ChainConstant.TYPE;
-import static com.yomahub.liteflow.common.ChainConstant.VALUE;
-import static com.yomahub.liteflow.common.ChainConstant._CLASS;
+import static com.yomahub.liteflow.common.ChainConstant.*;
 
 /**
  * Parser 通用 Helper
@@ -296,11 +280,27 @@ public class ParserHelper {
 	public static void parseOneChainEl(JsonNode chainNode) {
 		// 构建chainBuilder
 		String chainId = Optional.ofNullable(chainNode.get(ID)).orElse(chainNode.get(NAME)).textValue();
-		String el = chainNode.get(VALUE).textValue();
-		LiteFlowChainELBuilder.createChain()
-				.setChainId(chainId)
-				.setEL(el)
-				.build();
+
+		JsonNode routeJsonNode = chainNode.get(ROUTE);
+
+		LiteFlowChainELBuilder builder = LiteFlowChainELBuilder.createChain().setChainId(chainId);
+
+		// 如果有route这个标签,说明是决策表chain
+		// 决策表链路必须有route和body这两个标签
+		if (routeJsonNode != null){
+			builder.setRoute(routeJsonNode.textValue());
+
+			JsonNode bodyJsonNode = chainNode.get(BODY);
+			if (bodyJsonNode == null){
+				String errMsg = StrUtil.format("If you have defined the field route, then you must define the field body in chain[{}]", chainId);
+				throw new FlowSystemException(errMsg);
+			}
+			builder.setEL(bodyJsonNode.textValue());
+		}else{
+			builder.setEL(chainNode.get(VALUE).textValue());
+		}
+
+		builder.build();
 	}
 
 	/**
@@ -310,12 +310,27 @@ public class ParserHelper {
 	public static void parseOneChainEl(Element e) {
 		// 构建chainBuilder
 		String chainId = Optional.ofNullable(e.attributeValue(ID)).orElse(e.attributeValue(NAME));
-		String text = e.getText();
-		String el = ElRegexUtil.removeComments(text);
-		LiteFlowChainELBuilder.createChain()
-				.setChainId(chainId)
-				.setEL(el)
-				.build();
+
+		Element routeElement = e.element(ROUTE);
+
+		LiteFlowChainELBuilder builder = LiteFlowChainELBuilder.createChain().setChainId(chainId);
+
+		// 如果有route这个标签,说明是决策表chain
+		// 决策表链路必须有route和body这两个标签
+		if (routeElement != null){
+			builder.setRoute(ElRegexUtil.removeComments(routeElement.getText()));
+
+			Element bodyElement = e.element(BODY);
+			if (bodyElement == null){
+				String errMsg = StrUtil.format("If you have defined the tag <route>, then you must define the tag <body> in chain[{}]", chainId);
+				throw new FlowSystemException(errMsg);
+			}
+			builder.setEL(ElRegexUtil.removeComments(bodyElement.getText()));
+		}else{
+			builder.setEL(ElRegexUtil.removeComments(e.getText()));
+		}
+
+		builder.build();
 	}
 
 	/**

+ 3 - 1
liteflow-core/src/main/resources/dtd/liteflow.dtd

@@ -3,7 +3,9 @@
 <!ELEMENT flow  ((nodes)? , (chain)+)>
 <!ELEMENT nodes  (node)+>
 <!ELEMENT node  (#PCDATA | EMPTY)*>
-<!ELEMENT chain (#PCDATA)>
+<!ELEMENT chain ((route)? | (body)? | #PCDATA)>
+<!ELEMENT route (#PCDATA)>
+<!ELEMENT body (#PCDATA)>
 
 <!ATTLIST node
         id CDATA #REQUIRED

+ 42 - 0
liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/route/RouteSpringbootTest.java

@@ -0,0 +1,42 @@
+package com.yomahub.liteflow.test.route;
+
+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:/route/application.properties")
+@SpringBootTest(classes = RouteSpringbootTest.class)
+@EnableAutoConfiguration
+@ComponentScan({ "com.yomahub.liteflow.test.route.cmp" })
+public class RouteSpringbootTest extends BaseTest {
+
+	@Resource
+	private FlowExecutor flowExecutor;
+
+	// 最简单的情况
+	@Test
+	public void testRoute1() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("r_chain1", "arg");
+		Assertions.assertTrue(response.isSuccess());
+	}
+
+	@Test
+	public void testRoute2() throws Exception {
+		LiteflowResponse response = flowExecutor.execute2Resp("r_chain2", "arg");
+		Assertions.assertTrue(response.isSuccess());
+	}
+
+}

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

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

+ 21 - 0
liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/route/cmp/BCmp.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.route.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!");
+	}
+
+}

+ 12 - 0
liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/route/cmp/R1.java

@@ -0,0 +1,12 @@
+package com.yomahub.liteflow.test.route.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeBooleanComponent;
+
+@LiteflowComponent("r1")
+public class R1 extends NodeBooleanComponent {
+    @Override
+    public boolean processBoolean() throws Exception {
+        return true;
+    }
+}

+ 12 - 0
liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/route/cmp/R2.java

@@ -0,0 +1,12 @@
+package com.yomahub.liteflow.test.route.cmp;
+
+import com.yomahub.liteflow.annotation.LiteflowComponent;
+import com.yomahub.liteflow.core.NodeBooleanComponent;
+
+@LiteflowComponent("r2")
+public class R2 extends NodeBooleanComponent {
+    @Override
+    public boolean processBoolean() throws Exception {
+        return false;
+    }
+}

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

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

+ 21 - 0
liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/route/flow.el.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE flow PUBLIC  "liteflow" "liteflow.dtd">
+<flow>
+    <chain name="r_chain1">
+        <route>
+            r1
+        </route>
+        <body>
+            THEN(a,b);
+        </body>
+    </chain>
+
+    <chain name="r_chain2">
+        <route>
+            OR(r1,r2)
+        </route>
+        <body>
+            THEN(a,b);
+        </body>
+    </chain>
+</flow>