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

This commit is contained in:
everywhere.z 2024-04-02 13:26:26 +08:00
parent 6a2dbd849b
commit 984f7e8e03
41 changed files with 673 additions and 88 deletions

View File

@ -177,5 +177,4 @@ public class LiteFlowNodeBuilder {
throw new NodeBuildException(CollUtil.join(errorList, ",", "[", "]"));
}
}
}

View File

@ -13,16 +13,14 @@ import com.ql.util.express.InstructionSet;
import com.ql.util.express.exception.QLException;
import com.yomahub.liteflow.builder.el.operator.*;
import com.yomahub.liteflow.common.ChainConstant;
import com.yomahub.liteflow.exception.CyclicDependencyException;
import com.yomahub.liteflow.exception.DataNotFoundException;
import com.yomahub.liteflow.exception.ELParseException;
import com.yomahub.liteflow.exception.FlowSystemException;
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;
import com.yomahub.liteflow.flow.element.Executable;
import com.yomahub.liteflow.flow.element.Node;
import com.yomahub.liteflow.flow.element.condition.AndOrCondition;
import com.yomahub.liteflow.flow.element.condition.NotCondition;
import com.yomahub.liteflow.log.LFLog;
import com.yomahub.liteflow.log.LFLoggerManager;
import com.yomahub.liteflow.util.ElRegexUtil;
@ -41,7 +39,7 @@ public class LiteFlowChainELBuilder {
private static final LFLog LOG = LFLoggerManager.getLogger(LiteFlowChainELBuilder.class);
private static ObjectMapper objectMapper =new ObjectMapper();
private static ObjectMapper objectMapper = new ObjectMapper();
private Chain chain;
@ -138,7 +136,7 @@ public class LiteFlowChainELBuilder {
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());
String errMsg = StrUtil.format("You have defined the label <route> but there is no el in the chain route[{}].", chain.getChainId());
throw new FlowSystemException(errMsg);
}
List<String> errorList = new ArrayList<>();
@ -151,6 +149,11 @@ public class LiteFlowChainELBuilder {
// 解析route el成为一个executable
Executable routeExecutable = (Executable) EXPRESS_RUNNER.execute(routeEl, context, errorList, true, true);
// 判断routeEL是不是符合规范
if (!(routeExecutable instanceof AndOrCondition || routeExecutable instanceof NotCondition || routeExecutable instanceof Node)){
throw new RouteELInvalidException("the route EL can only be a boolean node, or an AND or OR expression.");
}
if (Objects.isNull(routeExecutable)){
throw new QLException(StrUtil.format("parse route el fail,el:[{}]", routeEl));
}
@ -169,7 +172,9 @@ public class LiteFlowChainELBuilder {
}else{
throw new ELParseException(e.getMessage());
}
} catch (Exception e) {
}catch (RouteELInvalidException e){
throw e;
}catch (Exception e) {
String errMsg = StrUtil.format("parse el fail in this chain[{}];\r\n", chain.getChainId());
throw new ELParseException(errMsg + e.getMessage());
}
@ -177,7 +182,7 @@ public class LiteFlowChainELBuilder {
public LiteFlowChainELBuilder setEL(String elStr) {
if (StrUtil.isBlank(elStr)) {
String errMsg = StrUtil.format("no content in this chain[{}]", chain.getChainId());
String errMsg = StrUtil.format("no el in this chain[{}]", chain.getChainId());
throw new FlowSystemException(errMsg);
}

View File

@ -4,7 +4,7 @@ import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.StrUtil;
import com.ql.util.express.exception.QLException;
import com.yomahub.liteflow.enums.ConditionTypeEnum;
import com.yomahub.liteflow.enums.ExecuteTypeEnum;
import com.yomahub.liteflow.enums.ExecuteableTypeEnum;
import com.yomahub.liteflow.enums.NodeTypeEnum;
import com.yomahub.liteflow.exception.DataNotFoundException;
import com.yomahub.liteflow.flow.element.Condition;
@ -148,7 +148,7 @@ public class OperatorHelper {
throw new QLException("The parameter must be Executable item.");
}
Executable item = (Executable) object;
if (item.getExecuteType().equals(ExecuteTypeEnum.NODE)){
if (item.getExecuteType().equals(ExecuteableTypeEnum.NODE)){
Node node = (Node) item;
if (!ListUtil.toList(NodeTypeEnum.COMMON, NodeTypeEnum.SCRIPT, NodeTypeEnum.FALLBACK).contains(node.getType())){
throw new QLException(StrUtil.format("The node[{}] must be a common type component", node.getId()));
@ -165,14 +165,14 @@ public class OperatorHelper {
throw new QLException("The parameter must be Executable item.");
}
Executable item = (Executable) object;
if (item.getExecuteType().equals(ExecuteTypeEnum.NODE)){
if (item.getExecuteType().equals(ExecuteableTypeEnum.NODE)){
Node node = (Node) item;
if (!ListUtil.toList(NodeTypeEnum.BOOLEAN,
NodeTypeEnum.BOOLEAN_SCRIPT,
NodeTypeEnum.FALLBACK).contains(node.getType())){
throw new QLException(StrUtil.format("The node[{}] must be boolean type Node.", node.getId()));
}
}else if(item.getExecuteType().equals(ExecuteTypeEnum.CONDITION)){
}else if(item.getExecuteType().equals(ExecuteableTypeEnum.CONDITION)){
Condition condition = (Condition) item;
if (!ListUtil.toList(ConditionTypeEnum.TYPE_AND_OR_OPT, ConditionTypeEnum.TYPE_NOT_OPT).contains(condition.getConditionType())){
throw new QLException(StrUtil.format("The condition[{}] must be boolean type Condition.", condition.getId()));
@ -187,7 +187,7 @@ public class OperatorHelper {
throw new QLException("The parameter must be Executable item.");
}
Executable item = (Executable) object;
if (item.getExecuteType().equals(ExecuteTypeEnum.NODE)){
if (item.getExecuteType().equals(ExecuteableTypeEnum.NODE)){
Node node = (Node) item;
if (!ListUtil.toList(NodeTypeEnum.FOR,
NodeTypeEnum.FOR_SCRIPT,
@ -204,7 +204,7 @@ public class OperatorHelper {
throw new QLException("The parameter must be Executable item.");
}
Executable item = (Executable) object;
if (item.getExecuteType().equals(ExecuteTypeEnum.NODE)){
if (item.getExecuteType().equals(ExecuteableTypeEnum.NODE)){
Node node = (Node) item;
if (!ListUtil.toList(NodeTypeEnum.ITERATOR,
NodeTypeEnum.FALLBACK).contains(node.getType())){
@ -220,7 +220,7 @@ public class OperatorHelper {
throw new QLException("The parameter must be Executable item.");
}
Executable item = (Executable) object;
if (item.getExecuteType().equals(ExecuteTypeEnum.NODE)){
if (item.getExecuteType().equals(ExecuteableTypeEnum.NODE)){
Node node = (Node) item;
if (!ListUtil.toList(NodeTypeEnum.SWITCH,
NodeTypeEnum.SWITCH_SCRIPT,

View File

@ -8,9 +8,12 @@
*/
package com.yomahub.liteflow.core;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.lang.Tuple;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.*;
import com.yomahub.liteflow.enums.ChainExecuteModeEnum;
import com.yomahub.liteflow.enums.InnerChainTypeEnum;
import com.yomahub.liteflow.exception.*;
import com.yomahub.liteflow.flow.FlowBus;
@ -36,7 +39,12 @@ import com.yomahub.liteflow.spi.holder.PathContentParserHolder;
import com.yomahub.liteflow.thread.ExecutorHelper;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
* 流程规则主要执行器类
@ -177,6 +185,10 @@ public class FlowExecutor {
LOG.error(e.getMessage(), e);
throw e;
}
catch (RouteELInvalidException e) {
LOG.error(e.getMessage(), e);
throw e;
}
catch (Exception e) {
String errorMsg = StrUtil.format("init flow executor cause error for path {},reason: {}", rulePathList,
e.getMessage());
@ -269,18 +281,34 @@ public class FlowExecutor {
return this.execute2Resp(chainId, param, null, contextBeanClazzArray, null);
}
public List<LiteflowResponse> executeRouteChain(Object param, Class<?>... contextBeanClazzArray){
return this.executeWithRoute(param, null, contextBeanClazzArray, null);
}
public LiteflowResponse execute2Resp(String chainId, Object param, Object... contextBeanArray) {
return this.execute2Resp(chainId, param, null, null, contextBeanArray);
}
public List<LiteflowResponse> executeRouteChain(Object param, Object... contextBeanArray){
return this.executeWithRoute(param, null, null, contextBeanArray);
}
public LiteflowResponse execute2RespWithRid(String chainId, Object param, String requestId, Class<?>... contextBeanClazzArray) {
return this.execute2Resp(chainId, param, requestId, contextBeanClazzArray, null);
}
public List<LiteflowResponse> executeRouteChainWithRid(String chainId, Object param, String requestId, Class<?>... contextBeanClazzArray) {
return this.executeWithRoute(param, requestId, contextBeanClazzArray, null);
}
public LiteflowResponse execute2RespWithRid(String chainId, Object param, String requestId, Object... contextBeanArray) {
return this.execute2Resp(chainId, param, requestId, null, contextBeanArray);
}
public List<LiteflowResponse> executeRouteChainWithRid(String chainId, Object param, String requestId, Object... contextBeanArray) {
return this.executeWithRoute(param, requestId, null, contextBeanArray);
}
// 调用一个流程并返回Future<LiteflowResponse>允许多上下文的传入
public Future<LiteflowResponse> execute2Future(String chainId, Object param, Class<?>... contextBeanClazzArray) {
return ExecutorHelper.loadInstance()
@ -320,18 +348,23 @@ public class FlowExecutor {
private LiteflowResponse execute2Resp(String chainId, Object param, String requestId, Class<?>[] contextBeanClazzArray,
Object[] contextBeanArray) {
Slot slot = doExecute(chainId, param, requestId, contextBeanClazzArray, contextBeanArray, null, InnerChainTypeEnum.NONE);
Slot slot = doExecute(chainId, param, requestId, contextBeanClazzArray, contextBeanArray, null, InnerChainTypeEnum.NONE, ChainExecuteModeEnum.BODY);
return LiteflowResponse.newMainResponse(slot);
}
private List<LiteflowResponse> executeWithRoute(Object param, String requestId, Class<?>[] contextBeanClazzArray, Object[] contextBeanArray){
List<Slot> slotList = doExecuteWithRoute(param, requestId, contextBeanClazzArray, contextBeanArray);
return slotList.stream().map(LiteflowResponse::newMainResponse).collect(Collectors.toList());
}
private LiteflowResponse invoke2Resp(String chainId, Object param, Integer slotIndex,
InnerChainTypeEnum innerChainType) {
Slot slot = doExecute(chainId, param, null, null, null, slotIndex, innerChainType);
Slot slot = doExecute(chainId, param, null, null, null, slotIndex, innerChainType, ChainExecuteModeEnum.BODY);
return LiteflowResponse.newInnerResponse(chainId, slot);
}
private Slot doExecute(String chainId, Object param, String requestId, Class<?>[] contextBeanClazzArray, Object[] contextBeanArray,
Integer slotIndex, InnerChainTypeEnum innerChainType) {
Integer slotIndex, InnerChainTypeEnum innerChainType, ChainExecuteModeEnum chainExecuteModeEnum) {
if (FlowBus.needInit()) {
init(true);
}
@ -399,8 +432,14 @@ public class FlowExecutor {
String errorMsg = StrUtil.format("couldn't find chain with the id[{}]", chainId);
throw new ChainNotFoundException(errorMsg);
}
// 执行chain
chain.execute(slotIndex);
// 根据chain执行模式执行chain
if (chainExecuteModeEnum.equals(ChainExecuteModeEnum.BODY)){
chain.execute(slotIndex);
}else if(chainExecuteModeEnum.equals(ChainExecuteModeEnum.ROUTE)){
chain.executeRoute(slotIndex);
}else{
throw new LiteFlowException("chain execute mode error");
}
}
catch (ChainEndException e) {
if (ObjectUtil.isNotNull(chain)) {
@ -482,4 +521,80 @@ public class FlowExecutor {
MonitorFile.getInstance().addMonitorFilePaths(fileAbsolutePath);
}
private List<Slot> doExecuteWithRoute(Object param, String requestId, Class<?>[] contextBeanClazzArray, Object[] contextBeanArray){
if (FlowBus.needInit()) {
init(true);
}
List<Chain> routeChainList = FlowBus.getChainMap().values().stream().filter(chain -> chain.getRouteItem() != null).collect(Collectors.toList());
if (CollUtil.isEmpty(routeChainList)){
throw new RouteChainNotFoundException("cannot find any route chain");
}
// 异步执行route el
List<Tuple> routeTupleList = new ArrayList<>();
for (Chain routeChain : routeChainList){
CompletableFuture<Slot> f = CompletableFuture.supplyAsync(
() -> doExecute(routeChain.getChainId(), param, null, contextBeanClazzArray, contextBeanArray, null, InnerChainTypeEnum.NONE, ChainExecuteModeEnum.ROUTE)
);
routeTupleList.add(new Tuple(routeChain, f));
}
CompletableFuture<?> resultRouteCf = CompletableFuture.allOf(routeTupleList.stream().map(
(Function<Tuple, CompletableFuture<?>>) tuple -> tuple.get(1)
).collect(Collectors.toList()).toArray(new CompletableFuture[] {}));
try{
resultRouteCf.get();
}catch (Exception e){
throw new LiteFlowException("There is An error occurred while executing the route.", e);
}
// 把route执行为true都过滤出来
List<Chain> matchedRouteChainList = routeTupleList.stream().filter(tuple -> {
try{
CompletableFuture<Slot> f = tuple.get(1);
Slot slot = f.get();
return BooleanUtil.isTrue(slot.getRouteResult());
}catch (Exception e){
return false;
}
}).map(
(Function<Tuple, Chain>) tuple -> tuple.get(0)
).collect(Collectors.toList());
if (CollUtil.isEmpty(matchedRouteChainList)){
throw new NoMatchedRouteChainException("there is no matched route chain");
}
// 异步分别执行这些chain
List<CompletableFuture<Slot>> executeChainCfList = new ArrayList<>();
for (Chain chain : matchedRouteChainList){
CompletableFuture<Slot> cf = CompletableFuture.supplyAsync(
() -> doExecute(chain.getChainId(), param, requestId, contextBeanClazzArray, contextBeanArray, null, InnerChainTypeEnum.NONE, ChainExecuteModeEnum.BODY)
);
executeChainCfList.add(cf);
}
CompletableFuture<?> resultChainCf = CompletableFuture.allOf(executeChainCfList.toArray(new CompletableFuture[] {}));
try{
resultChainCf.get();
}catch (Exception e){
throw new LiteFlowException("There is An error occurred while executing the matched chain.", e);
}
List<Slot> resultSlotList = executeChainCfList.stream().map(slotCompletableFuture -> {
try{
return slotCompletableFuture.get();
}catch (Exception e){
return null;
}
}).filter(Objects::nonNull).collect(Collectors.toList());
LOG.info("There are {} chains that matched the route.", resultSlotList.size());
return resultSlotList;
}
}

View File

@ -0,0 +1,14 @@
package com.yomahub.liteflow.enums;
/**
* Chain执行模式分为两种
* 第一种是执行Chain的本体EL为BODY
* 第二种是执行Chain的决策路由为ROUTE
*
* @author Bryan.Zhang
* @since 2.12.0
*/
public enum ChainExecuteModeEnum {
BODY,ROUTE
}

View File

@ -12,7 +12,7 @@ package com.yomahub.liteflow.enums;
*
* @author Bryan.Zhang
*/
public enum ExecuteTypeEnum {
public enum ExecuteableTypeEnum {
CHAIN, CONDITION, NODE

View File

@ -0,0 +1,28 @@
package com.yomahub.liteflow.exception;
/**
* 没有匹配的决策路由
*
* @author Bryan.Zhang
*/
public class NoMatchedRouteChainException extends RuntimeException {
private static final long serialVersionUID = 1L;
/** 异常信息 */
private String message;
public NoMatchedRouteChainException(String message) {
this.message = message;
}
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}

View File

@ -0,0 +1,28 @@
package com.yomahub.liteflow.exception;
/**
* 决策路由没有找到异常
*
* @author Bryan.Zhang
*/
public class RouteChainNotFoundException extends RuntimeException {
private static final long serialVersionUID = 1L;
/** 异常信息 */
private String message;
public RouteChainNotFoundException(String message) {
this.message = message;
}
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}

View File

@ -0,0 +1,28 @@
package com.yomahub.liteflow.exception;
/**
* Route语句不符合规范异常
*
* @author Bryan.Zhang
*/
public class RouteELInvalidException extends RuntimeException {
private static final long serialVersionUID = 1L;
/** 异常信息 */
private String message;
public RouteELInvalidException(String message) {
this.message = message;
}
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}

View File

@ -16,6 +16,8 @@ import java.util.function.Consumer;
*/
public class LiteflowResponse {
private String chainId;
private boolean success;
private String code;
@ -39,7 +41,8 @@ public class LiteflowResponse {
private static LiteflowResponse newResponse(Slot slot, Exception e) {
LiteflowResponse response = new LiteflowResponse();
if (slot != null && e != null) {
response.setChainId(slot.getChainId());
if (e != null) {
response.setSuccess(false);
response.setCause(e);
response.setMessage(response.getCause().getMessage());
@ -165,4 +168,11 @@ public class LiteflowResponse {
return this.getSlot().getRequestId();
}
public String getChainId() {
return chainId;
}
public void setChainId(String chainId) {
this.chainId = chainId;
}
}

View File

@ -14,7 +14,7 @@ import com.yomahub.liteflow.log.LFLog;
import com.yomahub.liteflow.log.LFLoggerManager;
import com.yomahub.liteflow.slot.DataBus;
import com.yomahub.liteflow.slot.Slot;
import com.yomahub.liteflow.enums.ExecuteTypeEnum;
import com.yomahub.liteflow.enums.ExecuteableTypeEnum;
import com.yomahub.liteflow.exception.FlowSystemException;
import java.util.ArrayList;
import java.util.List;
@ -111,9 +111,35 @@ public class Chain implements Executable{
}
}
public void executeRoute(Integer slotIndex) throws Exception {
if (routeItem == null) {
throw new FlowSystemException("no route condition or node in this chain[" + chainId + "]");
}
Slot slot = DataBus.getSlot(slotIndex);
try {
// 设置主ChainName
slot.setChainId(chainId);
// 执行决策路由
routeItem.setCurrChainId(chainId);
routeItem.execute(slotIndex);
boolean routeResult = routeItem.getItemResultMetaValue(slotIndex);
slot.setRouteResult(routeResult);
}
catch (ChainEndException e) {
throw e;
}
catch (Exception e) {
slot.setException(e);
throw e;
}
}
@Override
public ExecuteTypeEnum getExecuteType() {
return ExecuteTypeEnum.CHAIN;
public ExecuteableTypeEnum getExecuteType() {
return ExecuteableTypeEnum.CHAIN;
}
@Override

View File

@ -12,7 +12,7 @@ import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.yomahub.liteflow.enums.ConditionTypeEnum;
import com.yomahub.liteflow.enums.ExecuteTypeEnum;
import com.yomahub.liteflow.enums.ExecuteableTypeEnum;
import com.yomahub.liteflow.exception.ChainEndException;
import com.yomahub.liteflow.flow.element.condition.ConditionKey;
import com.yomahub.liteflow.slot.DataBus;
@ -22,9 +22,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -82,8 +80,8 @@ public abstract class Condition implements Executable{
public abstract void executeCondition(Integer slotIndex) throws Exception;
@Override
public ExecuteTypeEnum getExecuteType() {
return ExecuteTypeEnum.CONDITION;
public ExecuteableTypeEnum getExecuteType() {
return ExecuteableTypeEnum.CONDITION;
}
public List<Executable> getExecutableList() {

View File

@ -1,6 +1,6 @@
package com.yomahub.liteflow.flow.element;
import com.yomahub.liteflow.enums.ExecuteTypeEnum;
import com.yomahub.liteflow.enums.ExecuteableTypeEnum;
/**
* 可执行器接口 目前实现这个接口的有3个ChainConditionNode
@ -15,7 +15,7 @@ public interface Executable{
return true;
}
ExecuteTypeEnum getExecuteType();
ExecuteableTypeEnum getExecuteType();
/**
* @return

View File

@ -13,7 +13,7 @@ import cn.hutool.core.util.StrUtil;
import com.alibaba.ttl.TransmittableThreadLocal;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.yomahub.liteflow.core.NodeComponent;
import com.yomahub.liteflow.enums.ExecuteTypeEnum;
import com.yomahub.liteflow.enums.ExecuteableTypeEnum;
import com.yomahub.liteflow.enums.NodeTypeEnum;
import com.yomahub.liteflow.exception.ChainEndException;
import com.yomahub.liteflow.exception.FlowSystemException;
@ -227,8 +227,8 @@ public class Node implements Executable, Cloneable, Rollbackable{
}
@Override
public ExecuteTypeEnum getExecuteType() {
return ExecuteTypeEnum.NODE;
public ExecuteableTypeEnum getExecuteType() {
return ExecuteableTypeEnum.NODE;
}
public String getScript() {

View File

@ -354,7 +354,13 @@ public class ParserHelper {
}
builder.setEL(ElRegexUtil.removeComments(bodyElement.getText()));
}else{
builder.setEL(ElRegexUtil.removeComments(e.getText()));
// 即使没有route这个标签body标签单独写也是被允许的
Element bodyElement = e.element(BODY);
if (bodyElement != null){
builder.setEL(ElRegexUtil.removeComments(bodyElement.getText()));
}else{
builder.setEL(ElRegexUtil.removeComments(e.getText()));
}
}
builder.build();

View File

@ -97,6 +97,8 @@ public class Slot {
private static final TransmittableThreadLocal<Deque<Condition>> conditionStack = TransmittableThreadLocal.withInitial(ConcurrentLinkedDeque::new);
private Boolean routeResult;
public Slot() {
}
@ -494,4 +496,11 @@ public class Slot {
}
}
public Boolean getRouteResult() {
return routeResult;
}
public void setRouteResult(Boolean routeResult) {
this.routeResult = routeResult;
}
}

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>liteflow-testcase-el</artifactId>
<groupId>com.yomahub</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>liteflow-testcase-el-routechain</artifactId>
<dependencies>
<dependency>
<groupId>com.yomahub</groupId>
<artifactId>liteflow-spring-boot-starter</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.yomahub</groupId>
<artifactId>liteflow-el-builder</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,24 @@
package com.yomahub.liteflow.test;
import com.yomahub.liteflow.core.FlowInitHook;
import com.yomahub.liteflow.flow.FlowBus;
import com.yomahub.liteflow.property.LiteflowConfigGetter;
import com.yomahub.liteflow.spi.holder.SpiFactoryCleaner;
import com.yomahub.liteflow.spring.ComponentScanner;
import com.yomahub.liteflow.thread.ExecutorHelper;
import org.junit.jupiter.api.AfterAll;
public class BaseTest {
@AfterAll
public static void cleanScanCache() {
ComponentScanner.cleanCache();
FlowBus.cleanCache();
ExecutorHelper.loadInstance().clearExecutorServiceMap();
SpiFactoryCleaner.clean();
LiteflowConfigGetter.clean();
FlowInitHook.cleanHook();
FlowBus.clearStat();
}
}

View File

@ -0,0 +1,52 @@
package com.yomahub.liteflow.test.base;
import com.yomahub.liteflow.core.FlowExecutor;
import com.yomahub.liteflow.exception.NoMatchedRouteChainException;
import com.yomahub.liteflow.flow.LiteflowResponse;
import com.yomahub.liteflow.slot.DefaultContext;
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.List;
/**
* springboot环境EL常规的例子测试
*
* @author Bryan.Zhang
*/
@TestPropertySource(value = "classpath:/base/application.properties")
@SpringBootTest(classes = RouteSpringbootBaseTest.class)
@EnableAutoConfiguration
@ComponentScan({ "com.yomahub.liteflow.test.base.cmp" })
public class RouteSpringbootBaseTest extends BaseTest {
@Resource
private FlowExecutor flowExecutor;
// 最简单的情况两个都满足
@Test
public void testBaseRoute1() throws Exception {
List<LiteflowResponse> responseList = flowExecutor.executeRouteChain(15, DefaultContext.class);
LiteflowResponse response1 = responseList.stream().filter(
liteflowResponse -> liteflowResponse.getChainId().equals("r_chain1")
).findFirst().orElse(null);
assert response1 != null;
Assertions.assertTrue(response1.isSuccess());
Assertions.assertEquals("b==>a", response1.getExecuteStepStr());
LiteflowResponse response2 = responseList.stream().filter(
liteflowResponse -> liteflowResponse.getChainId().equals("r_chain2")
).findFirst().orElse(null);
assert response2 != null;
Assertions.assertTrue(response2.isSuccess());
Assertions.assertEquals("a==>b", response2.getExecuteStepStr());
}
}

View File

@ -5,7 +5,7 @@
* @email weenyc31@163.com
* @Date 2020/4/1
*/
package com.yomahub.liteflow.test.route.cmp;
package com.yomahub.liteflow.test.base.cmp;
import com.yomahub.liteflow.core.NodeComponent;
import org.springframework.stereotype.Component;

View File

@ -5,7 +5,7 @@
* @email weenyc31@163.com
* @Date 2020/4/1
*/
package com.yomahub.liteflow.test.route.cmp;
package com.yomahub.liteflow.test.base.cmp;
import com.yomahub.liteflow.core.NodeComponent;
import org.springframework.stereotype.Component;

View File

@ -1,4 +1,4 @@
package com.yomahub.liteflow.test.route.cmp;
package com.yomahub.liteflow.test.base.cmp;
import com.yomahub.liteflow.annotation.LiteflowComponent;
import com.yomahub.liteflow.core.NodeBooleanComponent;
@ -7,6 +7,7 @@ import com.yomahub.liteflow.core.NodeBooleanComponent;
public class R1 extends NodeBooleanComponent {
@Override
public boolean processBoolean() throws Exception {
return true;
int testInt = this.getRequestData();
return testInt >= 10 && testInt <= 20;
}
}

View File

@ -1,4 +1,4 @@
package com.yomahub.liteflow.test.route.cmp;
package com.yomahub.liteflow.test.base.cmp;
import com.yomahub.liteflow.annotation.LiteflowComponent;
import com.yomahub.liteflow.core.NodeBooleanComponent;
@ -7,6 +7,7 @@ import com.yomahub.liteflow.core.NodeBooleanComponent;
public class R2 extends NodeBooleanComponent {
@Override
public boolean processBoolean() throws Exception {
return false;
int testInt = this.getRequestData();
return testInt > 100;
}
}

View File

@ -0,0 +1,37 @@
package com.yomahub.liteflow.test.exception;
import com.yomahub.liteflow.core.FlowExecutor;
import com.yomahub.liteflow.exception.NoMatchedRouteChainException;
import com.yomahub.liteflow.slot.DefaultContext;
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:/exception/application1.properties")
@SpringBootTest(classes = RouteSpringbootExceptionTest1.class)
@EnableAutoConfiguration
@ComponentScan({ "com.yomahub.liteflow.test.exception.cmp" })
public class RouteSpringbootExceptionTest1 extends BaseTest {
@Resource
private FlowExecutor flowExecutor;
// 定义了但是没有路由满足
@Test
public void testExceptionRoute() throws Exception {
Assertions.assertThrows(NoMatchedRouteChainException.class,
() -> flowExecutor.executeRouteChain(1, DefaultContext.class)
);
}
}

View File

@ -0,0 +1,38 @@
package com.yomahub.liteflow.test.exception;
import com.yomahub.liteflow.core.FlowExecutor;
import com.yomahub.liteflow.exception.NoMatchedRouteChainException;
import com.yomahub.liteflow.exception.RouteChainNotFoundException;
import com.yomahub.liteflow.slot.DefaultContext;
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:/exception/application2.properties")
@SpringBootTest(classes = RouteSpringbootExceptionTest2.class)
@EnableAutoConfiguration
@ComponentScan({ "com.yomahub.liteflow.test.exception.cmp" })
public class RouteSpringbootExceptionTest2 extends BaseTest {
@Resource
private FlowExecutor flowExecutor;
// 没有定义route
@Test
public void testExceptionRoute() throws Exception {
Assertions.assertThrows(RouteChainNotFoundException.class,
() -> flowExecutor.executeRouteChain(1, DefaultContext.class)
);
}
}

View File

@ -0,0 +1,42 @@
package com.yomahub.liteflow.test.exception;
import com.yomahub.liteflow.core.FlowExecutor;
import com.yomahub.liteflow.exception.ChainDuplicateException;
import com.yomahub.liteflow.exception.RouteChainNotFoundException;
import com.yomahub.liteflow.exception.RouteELInvalidException;
import com.yomahub.liteflow.property.LiteflowConfig;
import com.yomahub.liteflow.property.LiteflowConfigGetter;
import com.yomahub.liteflow.slot.DefaultContext;
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
*/
@SpringBootTest(classes = RouteSpringbootExceptionTest3.class)
@EnableAutoConfiguration
@ComponentScan({ "com.yomahub.liteflow.test.exception.cmp" })
public class RouteSpringbootExceptionTest3 extends BaseTest {
@Resource
private FlowExecutor flowExecutor;
// 没有定义route
@Test
public void testExceptionRoute() throws Exception {
Assertions.assertThrows(RouteELInvalidException.class,() -> {
LiteflowConfig config = LiteflowConfigGetter.get();
config.setRuleSource("exception/flow3.xml");
flowExecutor.reloadRule();
});
}
}

View File

@ -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.exception.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!");
}
}

View File

@ -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.exception.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!");
}
}

View File

@ -0,0 +1,13 @@
package com.yomahub.liteflow.test.exception.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 {
int testInt = this.getRequestData();
return testInt >= 10 && testInt <= 20;
}
}

View File

@ -0,0 +1,13 @@
package com.yomahub.liteflow.test.exception.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 {
int testInt = this.getRequestData();
return testInt > 100;
}
}

View File

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

View File

@ -0,0 +1 @@
liteflow.rule-source=exception/flow1.xml

View File

@ -0,0 +1 @@
liteflow.rule-source=exception/flow2.xml

View File

@ -0,0 +1 @@
liteflow.rule-source=exception/flow3.xml

View File

@ -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(b,a);
</body>
</chain>
<chain name="r_chain2">
<route>
OR(r1,r2)
</route>
<body>
THEN(a,b);
</body>
</chain>
</flow>

View File

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

View File

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

View File

@ -1,42 +0,0 @@
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());
}
}

View File

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

View File

@ -38,6 +38,7 @@
<module>liteflow-testcase-el-sql-springboot-dynamic</module>
<module>liteflow-testcase-el-script-java-springboot</module>
<module>liteflow-testcase-el-builder</module>
<module>liteflow-testcase-el-routechain</module>
</modules>
<build>