This commit is contained in:
noear
2023-02-02 15:05:13 +08:00
65 changed files with 1151 additions and 128 deletions

View File

@@ -1,23 +1,19 @@
package com.yomahub.liteflow.builder;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.StrUtil;
import com.yomahub.liteflow.enums.NodeTypeEnum;
import com.yomahub.liteflow.exception.NodeBuildException;
import com.yomahub.liteflow.flow.FlowBus;
import com.yomahub.liteflow.flow.element.Node;
import com.yomahub.liteflow.spi.holder.PathContentParserHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
public class LiteFlowNodeBuilder {
@@ -53,6 +49,10 @@ public class LiteFlowNodeBuilder {
return new LiteFlowNodeBuilder(NodeTypeEnum.BREAK);
}
public static LiteFlowNodeBuilder createIteratorNode() {
return new LiteFlowNodeBuilder(NodeTypeEnum.ITERATOR);
}
public static LiteFlowNodeBuilder createScriptNode() {
return new LiteFlowNodeBuilder(NodeTypeEnum.SCRIPT);
}
@@ -130,8 +130,15 @@ public class LiteFlowNodeBuilder {
if (StrUtil.isBlank(filePath)) {
return this;
}
String script = ResourceUtil.readUtf8Str(StrUtil.format("classpath: {}", filePath.trim()));
return setScript(script);
try {
List<String> scriptList = PathContentParserHolder.loadContextAware().parseContent(ListUtil.toList(filePath));
String script = CollUtil.getFirst(scriptList);
setScript(script);
} catch (Exception e) {
String errMsg = StrUtil.format("An exception occurred while building the node[{}],{}", this.node.getId(), e.getMessage());
throw new NodeBuildException(errMsg);
}
return this;
}
public void build() {

View File

@@ -1,18 +1,22 @@
package com.yomahub.liteflow.builder.el;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.StrUtil;
import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;
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.DataNofFoundException;
import com.yomahub.liteflow.exception.DataNotFoundException;
import com.yomahub.liteflow.exception.ELParseException;
import com.yomahub.liteflow.exception.FlowSystemException;
import com.yomahub.liteflow.flow.FlowBus;
import com.yomahub.liteflow.flow.element.Chain;
import com.yomahub.liteflow.flow.element.condition.*;
import com.yomahub.liteflow.flow.element.Node;
import com.yomahub.liteflow.flow.element.condition.Condition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -69,6 +73,7 @@ public class LiteFlowChainELBuilder {
EXPRESS_RUNNER.addFunction(ChainConstant.NODE, new NodeOperator());
EXPRESS_RUNNER.addFunction(ChainConstant.FOR, new ForOperator());
EXPRESS_RUNNER.addFunction(ChainConstant.WHILE, new WhileOperator());
EXPRESS_RUNNER.addFunction(ChainConstant.ITERATOR, new IteratorOperator());
EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.DO, Object.class, new DoOperator());
EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.BREAK, Object.class, new BreakOperator());
EXPRESS_RUNNER.addFunctionAndClassMethod(ChainConstant.DATA, Object.class, new DataOperator());
@@ -151,8 +156,10 @@ public class LiteFlowChainELBuilder {
return this;
} catch (QLException e) {
// EL 底层会包装异常,这里是曲线处理
if (Objects.equals(e.getCause().getMessage(), DataNofFoundException.MSG)) {
throw new ELParseException(String.format("[node/chain is not exist or node/chain not register]elStr=%s", elStr));
if (Objects.equals(e.getCause().getMessage(), DataNotFoundException.MSG)) {
// 构建错误信息
String msg = buildDataNotFoundExceptionMsg(elStr);
throw new ELParseException(msg);
}
throw new ELParseException(e.getCause().getMessage());
} catch (Exception e) {
@@ -162,17 +169,18 @@ public class LiteFlowChainELBuilder {
/**
* EL表达式校验
*
* @param elStr EL表达式
* @return true 校验成功 false 校验失败
*/
public static boolean validate(String elStr) {
try {
LiteFlowChainELBuilder.createChain().setEL(elStr);
return Boolean.TRUE;
} catch (ELParseException e) {
LOG.error(e.getMessage());
}
return Boolean.FALSE;
try {
LiteFlowChainELBuilder.createChain().setEL(elStr);
return Boolean.TRUE;
} catch (ELParseException e) {
LOG.error(e.getMessage());
}
return Boolean.FALSE;
}
public void build() {
@@ -183,6 +191,8 @@ public class LiteFlowChainELBuilder {
FlowBus.addChain(this.chain);
}
//#region private method
/**
* build 前简单校验
*/
@@ -195,4 +205,57 @@ public class LiteFlowChainELBuilder {
throw new RuntimeException(CollUtil.join(errorList, ",", "[", "]"));
}
}
/**
* 解析 EL 表达式,查找未定义的 id 并构建错误信息
*
* @param elStr el 表达式
*/
private String buildDataNotFoundExceptionMsg(String elStr) {
String msg = String.format("[node/chain is not exist or node/chain not register]\n EL: %s", StrUtil.trim(elStr));
try {
InstructionSet parseResult = EXPRESS_RUNNER.getInstructionSetFromLocalCache(elStr);
if (parseResult == null) {
return msg;
}
String[] outAttrNames = parseResult.getOutAttrNames();
if (ArrayUtil.isEmpty(outAttrNames)) {
return msg;
}
List<String> chainIds = CollUtil.map(FlowBus.getChainMap().values(), Chain::getChainId, true);
List<String> nodeIds = CollUtil.map(FlowBus.getNodeMap().values(), Node::getId, true);
for (String attrName : outAttrNames) {
if (!chainIds.contains(attrName) && !nodeIds.contains(attrName)) {
msg = String.format("[%s] is not exist or [%s] is not registered, you need to define a node or chain with id [%s] and register it \n EL: ", attrName, attrName, attrName);
// 去除 EL 表达式中的空格和换行符
String sourceEl = StrUtil.removeAll(elStr, CharUtil.SPACE, CharUtil.LF, CharUtil.CR);
// 这里需要注意的是nodeId 和 chainId 可能是关键字的一部分,如果直接 indexOf(attrName) 会出现误判
// 所以需要判断 attrName 前后是否有 ","
int commaRightIndex = sourceEl.indexOf(attrName + StrUtil.COMMA);
if (commaRightIndex != -1) {
// 需要加上 "EL: " 的长度 4再加上 "^" 的长度 1indexOf 从 0 开始,所以还需要加 1
return msg + sourceEl + "\n" + StrUtil.fill("^", CharUtil.SPACE, commaRightIndex + 6, true);
}
int commaLeftIndex = sourceEl.indexOf(StrUtil.COMMA + attrName);
if (commaLeftIndex != -1) {
// 需要加上 "EL: " 的长度 4再加上 "^" 的长度 1再加上 "," 的长度 1indexOf 从 0 开始,所以还需要加 1
return msg + sourceEl + "\n" + StrUtil.fill("^", CharUtil.SPACE, commaLeftIndex + 7, true);
}
// 还有一种特殊情况,就是 EL 表达式中的节点使用 node("a")
int nodeIndex = sourceEl.indexOf(String.format("node(\"%s\")", attrName));
if (nodeIndex != -1) {
// 需要加上 "EL: " 的长度 4再加上 “node("” 长度 6再加上 "^" 的长度 1indexOf 从 0 开始,所以还需要加 1
return msg + sourceEl + "\n" + StrUtil.fill("^", CharUtil.SPACE, commaLeftIndex + 12, true);
}
}
}
} catch (Exception ex) {
// ignore
}
return msg;
}
//#endregion
}

View File

@@ -20,7 +20,7 @@ public class IgnoreErrorOperator extends BaseOperator<WhenCondition> {
WhenCondition condition = OperatorHelper.convert(objects[0], WhenCondition.class);
Boolean ignoreError = OperatorHelper.convert(objects[1], Boolean.class);
condition.setErrorResume(ignoreError);
condition.setIgnoreError(ignoreError);
return condition;
}

View File

@@ -0,0 +1,26 @@
package com.yomahub.liteflow.builder.el.operator;
import cn.hutool.core.collection.ListUtil;
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.enums.NodeTypeEnum;
import com.yomahub.liteflow.flow.element.Node;
import com.yomahub.liteflow.flow.element.condition.IteratorCondition;
public class IteratorOperator extends BaseOperator<IteratorCondition> {
@Override
public IteratorCondition build(Object[] objects) throws Exception {
OperatorHelper.checkObjectSizeEq(objects, 1);
Node node = OperatorHelper.convert(objects[0], Node.class);
if (!ListUtil.toList(NodeTypeEnum.ITERATOR).contains(node.getType())) {
throw new QLException("The parameter must be iterator-node item");
}
IteratorCondition iteratorCondition = new IteratorCondition();
iteratorCondition.setIteratorNode(node);
return iteratorCondition;
}
}

View File

@@ -2,7 +2,7 @@ package com.yomahub.liteflow.builder.el.operator.base;
import cn.hutool.core.util.StrUtil;
import com.ql.util.express.exception.QLException;
import com.yomahub.liteflow.exception.DataNofFoundException;
import com.yomahub.liteflow.exception.DataNotFoundException;
import com.yomahub.liteflow.flow.element.Node;
import java.util.Objects;
@@ -156,7 +156,7 @@ public class OperatorHelper {
public static void checkNodeAndChainExist(Object[] objects) throws QLException {
for (Object object : objects) {
if (Objects.isNull(object)) {
throw new QLException(DataNofFoundException.MSG);
throw new QLException(DataNotFoundException.MSG);
}
}
}

View File

@@ -63,6 +63,8 @@ public interface ChainConstant {
String DATA = "data";
String ITERATOR = "ITERATOR";
String MONITOR_BUS = "monitorBus";
String CURR_CHAIN_ID = "currChainId";

View File

@@ -9,6 +9,7 @@
package com.yomahub.liteflow.core;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.*;
import com.yomahub.liteflow.enums.InnerChainTypeEnum;
import com.yomahub.liteflow.exception.*;
@@ -82,14 +83,16 @@ public class FlowExecutor {
//进行id生成器的初始化
IdGeneratorHolder.init();
if (StrUtil.isBlank(liteflowConfig.getRuleSource())) {
String ruleSource = liteflowConfig.getRuleSource();
if (StrUtil.isBlank(ruleSource)) {
//查看有没有Parser的SPI实现
//所有的Parser的SPI实现都是以custom形式放入的且只支持xml形式
ServiceLoader<ParserClassNameSpi> loader = ServiceLoader.load(ParserClassNameSpi.class);
Iterator<ParserClassNameSpi> it = loader.iterator();
if (it.hasNext()){
ParserClassNameSpi parserClassNameSpi = it.next();
liteflowConfig.setRuleSource("el_xml:" + parserClassNameSpi.getSpiClassName());
ruleSource = "el_xml:" + parserClassNameSpi.getSpiClassName();
liteflowConfig.setRuleSource(ruleSource);
}else{
//ruleSource为空而且没有spi形式的扩展那么说明真的没有ruleSource
//这种情况有可能是基于代码动态构建的
@@ -100,10 +103,11 @@ public class FlowExecutor {
//如果有前缀的,则不需要再进行分割了,说明是一个整体
//如果没有前缀,说明是本地文件,可能配置多个,所以需要分割
List<String> sourceRulePathList;
if (ReUtil.contains(PREFIX_FORMAT_CONFIG_REGEX, liteflowConfig.getRuleSource())){
sourceRulePathList = ListUtil.toList(liteflowConfig.getRuleSource());
}else{
sourceRulePathList = ListUtil.toList(liteflowConfig.getRuleSource().split(",|;"));
if (ReUtil.contains(PREFIX_FORMAT_CONFIG_REGEX, ruleSource)) {
sourceRulePathList = ListUtil.toList(ruleSource);
} else {
String afterHandleRuleSource = ruleSource.replace(StrUtil.SPACE, StrUtil.EMPTY);
sourceRulePathList = ListUtil.toList(afterHandleRuleSource.split(",|;"));
}
FlowParser parser = null;
@@ -163,9 +167,11 @@ public class FlowExecutor {
}
//如果是ruleSource方式的最后判断下有没有解析出来,如果没有解析出来则报错
if (FlowBus.getChainMap().isEmpty()){
String errMsg = StrUtil.format("no valid rule config found in rule path [{}]", liteflowConfig.getRuleSource());
throw new ConfigErrorException(errMsg);
if (StrUtil.isBlank(liteflowConfig.getRuleSourceExtData()) && MapUtil.isEmpty(liteflowConfig.getRuleSourceExtDataMap())){
if (FlowBus.getChainMap().isEmpty()){
String errMsg = StrUtil.format("no valid rule config found in rule path [{}]", liteflowConfig.getRuleSource());
throw new ConfigErrorException(errMsg);
}
}
//执行钩子

View File

@@ -92,7 +92,7 @@ public abstract class NodeComponent{
try{
//前置处理
self.beforeProcess(this.getNodeId(), slot);
self.beforeProcess();
//主要的处理逻辑
self.process();
@@ -118,7 +118,7 @@ public abstract class NodeComponent{
throw e;
} finally {
//后置处理
self.afterProcess(this.getNodeId(), slot);
self.afterProcess();
stopWatch.stop();
final long timeSpent = stopWatch.getTotalTimeMillis();
@@ -135,10 +135,10 @@ public abstract class NodeComponent{
}
}
public <T> void beforeProcess(String nodeId, Slot slot){
public void beforeProcess(){
//全局切面只在spring体系下生效这里用了spi机制取到相应环境下的实现类
//非spring环境下全局切面为空实现
CmpAroundAspectHolder.loadCmpAroundAspect().beforeProcess(nodeId, slot);
CmpAroundAspectHolder.loadCmpAroundAspect().beforeProcess(nodeId, this.getSlot());
}
public abstract void process() throws Exception;
@@ -151,8 +151,8 @@ public abstract class NodeComponent{
//如果需要在抛错后回调某一段逻辑,请覆盖这个方法
}
public <T> void afterProcess(String nodeId, Slot slot){
CmpAroundAspectHolder.loadCmpAroundAspect().afterProcess(nodeId, slot);
public void afterProcess(){
CmpAroundAspectHolder.loadCmpAroundAspect().afterProcess(nodeId, this.getSlot());
}
//是否进入该节点
@@ -349,6 +349,10 @@ public abstract class NodeComponent{
return this.refNodeTL.get().getLoopIndex();
}
public <T> T getCurrLoopObj(){
return this.refNodeTL.get().getCurrLoopObject();
}
@Deprecated
public void invoke(String chainId, Object param) throws Exception {
FlowExecutorHolder.loadInstance().invoke(chainId, param, this.getSlotIndex());

View File

@@ -0,0 +1,24 @@
package com.yomahub.liteflow.core;
import com.yomahub.liteflow.slot.Slot;
import com.yomahub.liteflow.util.LiteFlowProxyUtil;
import java.util.Iterator;
/**
* ITERATOR迭代器循环组件抽象类
* @author Bryan.Zhang
* @since 2.9.7
*/
public abstract class NodeIteratorComponent extends NodeComponent{
@Override
public void process() throws Exception {
Iterator<?> it = processIterator();
Slot slot = this.getSlot();
Class<?> originalClass = LiteFlowProxyUtil.getUserClass(this.getClass());
slot.setIteratorResult(originalClass.getName(), it);
}
public abstract Iterator<?> processIterator() throws Exception;
}

View File

@@ -194,26 +194,17 @@ public class ComponentProxy {
).findFirst().orElse(null);
//如果被代理的对象里有此标注标的方法,则调用此被代理的对象里的方法,如果没有,则调用父类里的方法
//beforeProcess和afterProcess这2个方法除外
if (!ListUtil.toList("beforeProcess","afterProcess").contains(liteFlowMethodBean.getMethodName())) {
//进行检查检查被代理的bean里是否有且仅有NodeComponent这个类型的参数
boolean checkFlag = liteFlowMethodBean.getMethod().getParameterTypes().length == 1
&& Arrays.asList(liteFlowMethodBean.getMethod().getParameterTypes()).contains(NodeComponent.class);
if (!checkFlag) {
String errMsg = StrUtil.format("Method[{}.{}] must have NodeComponent parameter(and only one parameter)", bean.getClass().getName(), liteFlowMethodBean.getMethod().getName());
LOG.error(errMsg);
throw new ComponentMethodDefineErrorException(errMsg);
}
try{
return liteFlowMethodBean.getMethod().invoke(bean, proxy);
}catch (Exception e){
InvocationTargetException targetEx = (InvocationTargetException)e;
throw targetEx.getTargetException();
}
//进行检查检查被代理的bean里是否有且仅有NodeComponent这个类型的参数
boolean checkFlag = liteFlowMethodBean.getMethod().getParameterTypes().length == 1
&& Arrays.asList(liteFlowMethodBean.getMethod().getParameterTypes()).contains(NodeComponent.class);
if (!checkFlag) {
String errMsg = StrUtil.format("Method[{}.{}] must have NodeComponent parameter(and only one parameter)", bean.getClass().getName(), liteFlowMethodBean.getMethod().getName());
LOG.error(errMsg);
throw new ComponentMethodDefineErrorException(errMsg);
}
try{
return liteFlowMethodBean.getMethod().invoke(bean, args);
return liteFlowMethodBean.getMethod().invoke(bean, proxy);
}catch (Exception e){
InvocationTargetException targetEx = (InvocationTargetException)e;
throw targetEx.getTargetException();

View File

@@ -14,7 +14,9 @@ public enum ConditionTypeEnum {
TYPE_FOR("for", "for"),
TYPE_WHILE("while", "while")
TYPE_WHILE("while", "while"),
TYPE_ITERATOR("iterator", "iterator")
;
private String type;
private String name;

View File

@@ -7,6 +7,9 @@ public enum LiteFlowMethodEnum {
PROCESS_FOR("processFor", true),
PROCESS_WHILE("processWhile", true),
PROCESS_BREAK("processBreak", true),
PROCESS_ITERATOR("processIterator", true),
IS_ACCESS("isAccess", false),
IS_END("isEnd", false),

View File

@@ -35,6 +35,8 @@ public enum NodeTypeEnum {
BREAK("break", "循环跳出", false, NodeBreakComponent.class),
ITERATOR("iterator", "循环迭代", false, NodeIteratorComponent.class),
SCRIPT("script", "脚本", true, ScriptCommonComponent.class),
SWITCH_SCRIPT("switch_script", "选择脚本", true, ScriptSwitchComponent.class),

View File

@@ -4,8 +4,8 @@ package com.yomahub.liteflow.exception;
* 未找到数据异常
* @author tangkc
*/
public class DataNofFoundException extends RuntimeException {
public static final String MSG = "DataNofFoundException";
public class DataNotFoundException extends RuntimeException {
public static final String MSG = "DataNotFoundException";
private static final long serialVersionUID = 1L;
@@ -14,11 +14,11 @@ public class DataNofFoundException extends RuntimeException {
*/
private String message;
public DataNofFoundException() {
public DataNotFoundException() {
this.message = MSG;
}
public DataNofFoundException(String message) {
public DataNotFoundException(String message) {
this.message = message;
}

View File

@@ -0,0 +1,29 @@
package com.yomahub.liteflow.exception;
/**
* 没有节点异常
* @author Yun
*/
public class NoIteratorNodeException extends RuntimeException {
private static final long serialVersionUID = 1L;
/**
* 异常信息
*/
private String message;
public NoIteratorNodeException(String message) {
this.message = message;
}
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}

View File

@@ -207,7 +207,7 @@ public class FlowBus {
} catch (Exception e) {
String error = StrUtil.format("component[{}] register error", StrUtil.isEmpty(name) ? nodeId : StrUtil.format("{}({})", nodeId, name));
LOG.error(e.getMessage());
throw new ComponentCannotRegisterException(error);
throw new ComponentCannotRegisterException(StrUtil.format("{} {}", error, e.getMessage()));
}
}

View File

@@ -56,6 +56,8 @@ public class Node implements Executable,Cloneable{
private TransmittableThreadLocal<Integer> loopIndexTL = new TransmittableThreadLocal<>();
private TransmittableThreadLocal<Object> currLoopObject = new TransmittableThreadLocal<>();
public Node(){
}
@@ -241,4 +243,16 @@ public class Node implements Executable,Cloneable{
public void removeLoopIndex(){
this.loopIndexTL.remove();
}
public void setCurrLoopObject(Object obj){
this.currLoopObject.set(obj);
}
public <T> T getCurrLoopObject(){
return (T)this.currLoopObject.get();
}
public void removeCurrLoopObject(){
this.currLoopObject.remove();
}
}

View File

@@ -49,6 +49,7 @@ public class ForCondition extends LoopCondition{
//如果break组件不为空则去执行
if (ObjectUtil.isNotNull(breakNode)){
breakNode.setCurrChainId(this.getCurrChainId());
setLoopIndex(breakNode, i);
breakNode.execute(slotIndex);
Class<?> originalBreakClass = LiteFlowProxyUtil.getUserClass(this.breakNode.getInstance().getClass());
boolean isBreak = slot.getBreakResult(originalBreakClass.getName());
@@ -64,10 +65,6 @@ public class ForCondition extends LoopCondition{
return ConditionTypeEnum.TYPE_FOR;
}
public Executable getDoExecutor() {
return this.getExecutableList().get(0);
}
public Node getForNode() {
return forNode;
}

View File

@@ -0,0 +1,78 @@
package com.yomahub.liteflow.flow.element.condition;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.yomahub.liteflow.enums.ConditionTypeEnum;
import com.yomahub.liteflow.exception.NoIteratorNodeException;
import com.yomahub.liteflow.flow.element.Executable;
import com.yomahub.liteflow.flow.element.Node;
import com.yomahub.liteflow.slot.DataBus;
import com.yomahub.liteflow.slot.Slot;
import com.yomahub.liteflow.util.LiteFlowProxyUtil;
import java.util.Iterator;
public class IteratorCondition extends LoopCondition{
private Node iteratorNode;
@Override
public void execute(Integer slotIndex) throws Exception {
Slot slot = DataBus.getSlot(slotIndex);
if (ObjectUtil.isNull(iteratorNode)){
String errorInfo = StrUtil.format("[{}]:no iterator-node found", slot.getRequestId());
throw new NoIteratorNodeException(errorInfo);
}
//执行Iterator组件
iteratorNode.setCurrChainId(this.getCurrChainId());
iteratorNode.execute(slotIndex);
//这里可能会有spring代理过的bean所以拿到user原始的class
Class<?> originalForCountClass = LiteFlowProxyUtil.getUserClass(this.iteratorNode.getInstance().getClass());
//获得迭代器
Iterator<?> it = slot.getIteratorResult(originalForCountClass.getName());
//获得要循环的可执行对象
Executable executableItem = this.getDoExecutor();
int index = 0;
while(it.hasNext()){
Object itObj = it.next();
executableItem.setCurrChainId(this.getCurrChainId());
//设置循环index
setLoopIndex(executableItem, index);
//设置循环迭代器对象
setCurrLoopObject(executableItem, itObj);
//执行可执行对象
executableItem.execute(slotIndex);
//如果break组件不为空则去执行
if (ObjectUtil.isNotNull(breakNode)){
breakNode.setCurrChainId(this.getCurrChainId());
setLoopIndex(breakNode, index);
setCurrLoopObject(breakNode, itObj);
breakNode.execute(slotIndex);
Class<?> originalBreakClass = LiteFlowProxyUtil.getUserClass(this.breakNode.getInstance().getClass());
boolean isBreak = slot.getBreakResult(originalBreakClass.getName());
if (isBreak){
break;
}
}
index++;
}
}
@Override
public ConditionTypeEnum getConditionType() {
return ConditionTypeEnum.TYPE_ITERATOR;
}
public Node getIteratorNode() {
return iteratorNode;
}
public void setIteratorNode(Node iteratorNode) {
this.iteratorNode = iteratorNode;
}
}

View File

@@ -32,4 +32,18 @@ public abstract class LoopCondition extends Condition {
((Node)executableItem).setLoopIndex(index);
}
}
protected void setCurrLoopObject(Executable executableItem, Object obj){
if (executableItem instanceof Chain){
((Chain)executableItem).getConditionList().forEach(condition -> setCurrLoopObject(condition, obj));
}else if(executableItem instanceof Condition){
((Condition)executableItem).getExecutableList().forEach(executable -> setCurrLoopObject(executable, obj));
}else if(executableItem instanceof Node){
((Node)executableItem).setCurrLoopObject(obj);
}
}
protected Executable getDoExecutor() {
return this.getExecutableList().get(0);
}
}

View File

@@ -45,9 +45,9 @@ public class SwitchCondition extends Condition{
//这里可能会有spring代理过的bean所以拿到user原始的class
Class<?> originalClass = LiteFlowProxyUtil.getUserClass(this.getSwitchNode().getInstance().getClass());
String targetId = slot.getSwitchResult(originalClass.getName());
if (StrUtil.isNotBlank(targetId)) {
Executable targetExecutor;
Executable targetExecutor = null;
if (StrUtil.isNotBlank(targetId)) {
//这里要判断是否使用tag模式跳转
if (targetId.contains(TAG_FLAG)){
String[] target = targetId.split(TAG_FLAG, 2);
@@ -66,26 +66,26 @@ public class SwitchCondition extends Condition{
executable -> executable.getExecuteId().equals(targetId)
).findFirst().orElse(null);
}
}
if (ObjectUtil.isNull(targetExecutor)) {
//没有匹配到执行节点,则走默认的执行节点
targetExecutor = defaultExecutor;
}
if (ObjectUtil.isNull(targetExecutor)) {
//没有匹配到执行节点,则走默认的执行节点
targetExecutor = defaultExecutor;
}
if (ObjectUtil.isNotNull(targetExecutor)) {
//switch的目标不能是Pre节点或者Finally节点
if (targetExecutor instanceof PreCondition || targetExecutor instanceof FinallyCondition){
String errorInfo = StrUtil.format("[{}]:switch component[{}] error, switch target node cannot be pre or finally",
slot.getRequestId(), this.getSwitchNode().getInstance().getDisplayName());
throw new SwitchTargetCannotBePreOrFinallyException(errorInfo);
}
targetExecutor.setCurrChainId(this.getCurrChainId());
targetExecutor.execute(slotIndex);
}else{
String errorInfo = StrUtil.format("[{}]:no target node find for the component[{}],target str is [{}]",
slot.getRequestId(), this.getSwitchNode().getInstance().getDisplayName(), targetId);
throw new NoSwitchTargetNodeException(errorInfo);
if (ObjectUtil.isNotNull(targetExecutor)) {
//switch的目标不能是Pre节点或者Finally节点
if (targetExecutor instanceof PreCondition || targetExecutor instanceof FinallyCondition){
String errorInfo = StrUtil.format("[{}]:switch component[{}] error, switch target node cannot be pre or finally",
slot.getRequestId(), this.getSwitchNode().getInstance().getDisplayName());
throw new SwitchTargetCannotBePreOrFinallyException(errorInfo);
}
targetExecutor.setCurrChainId(this.getCurrChainId());
targetExecutor.execute(slotIndex);
}else{
String errorInfo = StrUtil.format("[{}]:no target node find for the component[{}],target str is [{}]",
slot.getRequestId(), this.getSwitchNode().getInstance().getDisplayName(), targetId);
throw new NoSwitchTargetNodeException(errorInfo);
}
}else{
throw new SwitchTypeErrorException("switch instance must be NodeSwitchComponent");

View File

@@ -37,7 +37,7 @@ public class WhenCondition extends Condition {
private final Logger LOG = LoggerFactory.getLogger(this.getClass());
//只在when类型下有效以区分当when调用链调用失败时是否继续往下执行 默认false不继续执行
private boolean errorResume = false;
private boolean ignoreError = false;
//只在when类型下有效用于不同node进行同组合并相同的组会进行合并不同的组不会进行合并
//此属性已弃用
@@ -150,8 +150,8 @@ public class WhenCondition extends Condition {
timeOutWhenFutureObjList.forEach(whenFutureObj ->
LOG.warn("requestId [{}] executing thread has reached max-wait-seconds, thread canceled.Execute-item: [{}]", slot.getRequestId(), whenFutureObj.getExecutorName()));
//当配置了errorResume = false出现interrupted或者!f.get()的情况将抛出WhenExecuteException
if (!this.isErrorResume()) {
//当配置了ignoreError = false出现interrupted或者!f.get()的情况将抛出WhenExecuteException
if (!this.isIgnoreError()) {
if (interrupted[0]) {
throw new WhenExecuteException(StrUtil.format("requestId [{}] when execute interrupted. errorResume [false].", slot.getRequestId()));
}
@@ -164,17 +164,17 @@ public class WhenCondition extends Condition {
}
}
} else if (interrupted[0]) {
// 这里由于配置了errorResume所以只打印warn日志
// 这里由于配置了ignoreError所以只打印warn日志
LOG.warn("requestId [{}] executing when condition timeout , but ignore with errorResume.", slot.getRequestId());
}
}
public boolean isErrorResume() {
return errorResume;
public boolean isIgnoreError() {
return ignoreError;
}
public void setErrorResume(boolean errorResume) {
this.errorResume = errorResume;
public void setIgnoreError(boolean ignoreError) {
this.ignoreError = ignoreError;
}
public String getGroup() {

View File

@@ -35,11 +35,12 @@ public class WhileCondition extends LoopCondition{
int index = 0;
while(getWhileResult(slotIndex)){
executableItem.setCurrChainId(this.getCurrChainId());
setLoopIndex(executableItem, index++);
setLoopIndex(executableItem, index);
executableItem.execute(slotIndex);
//如果break组件不为空则去执行
if (ObjectUtil.isNotNull(breakNode)){
breakNode.setCurrChainId(this.getCurrChainId());
setLoopIndex(breakNode, index);
breakNode.execute(slotIndex);
Class<?> originalBreakClass = LiteFlowProxyUtil.getUserClass(this.breakNode.getInstance().getClass());
boolean isBreak = slot.getBreakResult(originalBreakClass.getName());
@@ -47,22 +48,19 @@ public class WhileCondition extends LoopCondition{
break;
}
}
index++;
}
}
private boolean getWhileResult(Integer slotIndex) throws Exception{
Slot slot = DataBus.getSlot(slotIndex);
//执行while组件
whileNode.setCurrChainName(this.getCurrChainName());
whileNode.setCurrChainId(this.getCurrChainId());
whileNode.execute(slotIndex);
Class<?> originalWhileClass = LiteFlowProxyUtil.getUserClass(this.whileNode.getInstance().getClass());
return slot.getWhileResult(originalWhileClass.getName());
}
public Executable getDoExecutor() {
return this.getExecutableList().get(0);
}
@Override
public ConditionTypeEnum getConditionType() {
return ConditionTypeEnum.TYPE_WHILE;

View File

@@ -10,6 +10,7 @@ package com.yomahub.liteflow.slot;
import cn.hutool.core.collection.ConcurrentHashSet;
import cn.hutool.core.collection.ListUtil;
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.entity.CmpStep;
@@ -52,6 +53,8 @@ public class Slot{
private static final String WHILE_PREFIX = "_while_";
private static final String ITERATOR_PREFIX = "_iterator_";
private static final String BREAK_PREFIX = "_break_";
private static final String NODE_INPUT_PREFIX = "_input_";
@@ -89,6 +92,16 @@ public class Slot{
return metaDataMap.containsKey(key);
}
private <T> void putThreadMetaDataMap(String key, T t){
String threadKey = StrUtil.format("{}_{}", key, Thread.currentThread().getName());
putMetaDataMap(threadKey, t);
}
private <T> T getThreadMetaData(String key){
String threadKey = StrUtil.format("{}_{}", key, Thread.currentThread().getName());
return (T)metaDataMap.get(threadKey);
}
private <T> void putMetaDataMap(String key, T t) {
if (ObjectUtil.isNull(t)) {
//data slot is a ConcurrentHashMap, so null value will trigger NullPointerException
@@ -197,43 +210,51 @@ public class Slot{
}
public <T> void setSwitchResult(String key, T t){
putMetaDataMap(SWITCH_NODE_PREFIX + key, t);
putThreadMetaDataMap(SWITCH_NODE_PREFIX + key, t);
}
public <T> T getSwitchResult(String key){
return (T) metaDataMap.get(SWITCH_NODE_PREFIX + key);
return getThreadMetaData(SWITCH_NODE_PREFIX + key);
}
public void setIfResult(String key, boolean result){
putMetaDataMap(IF_NODE_PREFIX + key, result);
putThreadMetaDataMap(IF_NODE_PREFIX + key, result);
}
public boolean getIfResult(String key){
return (boolean) metaDataMap.get(IF_NODE_PREFIX + key);
return getThreadMetaData(IF_NODE_PREFIX + key);
}
public void setForResult(String key, int forCount){
putMetaDataMap(FOR_PREFIX + key, forCount);
putThreadMetaDataMap(FOR_PREFIX + key, forCount);
}
public int getForResult(String key){
return (int) metaDataMap.get(FOR_PREFIX + key);
return getThreadMetaData(FOR_PREFIX + key);
}
public void setWhileResult(String key, boolean whileFlag){
putMetaDataMap(WHILE_PREFIX + key, whileFlag);
putThreadMetaDataMap(WHILE_PREFIX + key, whileFlag);
}
public boolean getWhileResult(String key){
return (boolean) metaDataMap.get(WHILE_PREFIX + key);
return getThreadMetaData(WHILE_PREFIX + key);
}
public void setBreakResult(String key, boolean breakFlag){
putMetaDataMap(BREAK_PREFIX + key, breakFlag);
putThreadMetaDataMap(BREAK_PREFIX + key, breakFlag);
}
public boolean getBreakResult(String key){
return (boolean) metaDataMap.get(BREAK_PREFIX + key);
return getThreadMetaData(BREAK_PREFIX + key);
}
public void setIteratorResult(String key, Iterator<?> it){
putThreadMetaDataMap(ITERATOR_PREFIX + key, it);
}
public Iterator<?> getIteratorResult(String key){
return getThreadMetaData(ITERATOR_PREFIX + key);
}
/**

View File

@@ -1,15 +1,19 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!ELEMENT flow ((nodes) , (chain)+)>
<!ELEMENT flow ((nodes)? , (chain)+)>
<!ELEMENT nodes (node)+>
<!ELEMENT node (#PCDATA | EMPTY)*>
<!ELEMENT chain (#PCDATA)>
<!ATTLIST node
id CDATA #REQUIRED
name CDATA #IMPLIED
type (script|if_script|switch_script|while_script|for_script|break_script) #IMPLIED
class CDATA #IMPLIED
file CDATA #IMPLIED
language (groovy|js|python|lua) #IMPLIED
>
<!ATTLIST chain
name CDATA>
id CDATA #IMPLIED
name CDATA #IMPLIED
>

View File

@@ -53,7 +53,7 @@ public class NodeComponentOfMethod extends NodeComponent {
@Override
public <T> void beforeProcess(String nodeId, Slot slot) {
public void beforeProcess() {
if(methodEnum != LiteFlowMethodEnum.BEFORE_PROCESS){
return;
}
@@ -68,7 +68,7 @@ public class NodeComponentOfMethod extends NodeComponent {
}
@Override
public <T> void afterProcess(String nodeId, Slot slot) {
public void afterProcess() {
if (methodEnum != LiteFlowMethodEnum.AFTER_PROCESS) {
return;
}

View File

@@ -1,6 +1,5 @@
package com.yomahub.liteflow.spi.spring;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.io.FileUtil;
@@ -9,19 +8,15 @@ import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import com.yomahub.liteflow.exception.ConfigErrorException;
import com.yomahub.liteflow.property.LiteflowConfig;
import com.yomahub.liteflow.property.LiteflowConfigGetter;
import com.yomahub.liteflow.spi.PathContentParser;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.ResourceUtils;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class SpringPathContentParser implements PathContentParser {
@Override

View File

@@ -20,12 +20,12 @@ public class CmpConfig {
}
@LiteflowMethod(value = LiteFlowMethodEnum.BEFORE_PROCESS,nodeId = "a")
public void beforeAcmp(String nodeId, Slot slot){
public void beforeAcmp(NodeComponent bindCmp){
System.out.println("before A");
}
@LiteflowMethod(value = LiteFlowMethodEnum.AFTER_PROCESS,nodeId = "a")
public void afterAcmp(String nodeId, Slot slot){
public void afterAcmp(NodeComponent bindCmp){
System.out.println("after A");
}

View File

@@ -0,0 +1,57 @@
package com.yomahub.liteflow.test.iterator;
import cn.hutool.core.collection.ListUtil;
import com.yomahub.liteflow.core.FlowExecutor;
import com.yomahub.liteflow.flow.LiteflowResponse;
import com.yomahub.liteflow.slot.DefaultContext;
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.List;
/**
* springboot环境最普通的例子测试
* @author Bryan.Zhang
* @since 2.6.4
*/
@RunWith(SpringRunner.class)
@TestPropertySource(value = "classpath:/iterator/application.properties")
@SpringBootTest(classes = IteratorELDeclMultiSpringbootTest.class)
@EnableAutoConfiguration
@ComponentScan({"com.yomahub.liteflow.test.iterator.cmp"})
public class IteratorELDeclMultiSpringbootTest extends BaseTest {
@Resource
private FlowExecutor flowExecutor;
//最简单的情况
@Test
public void testIt1() throws Exception{
List<String> list = ListUtil.toList("1","2","3");
LiteflowResponse response = flowExecutor.execute2Resp("chain1", list);
Assert.assertTrue(response.isSuccess());
DefaultContext context = response.getFirstContextBean();
String str = context.getData("test");
Assert.assertEquals("123", str);
}
//迭代器带break
@Test
public void testIt2() throws Exception{
List<String> list = ListUtil.toList("1","2","3");
LiteflowResponse response = flowExecutor.execute2Resp("chain2", list);
Assert.assertTrue(response.isSuccess());
DefaultContext context = response.getFirstContextBean();
String str = context.getData("test");
Assert.assertEquals("12", str);
}
}

View File

@@ -0,0 +1,42 @@
package com.yomahub.liteflow.test.iterator.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;
import com.yomahub.liteflow.enums.NodeTypeEnum;
import com.yomahub.liteflow.slot.DefaultContext;
import com.yomahub.liteflow.test.base.cmp.TestDomain;
import javax.annotation.Resource;
import java.util.Iterator;
import java.util.List;
@LiteflowComponent
public class CmpConfig {
@LiteflowMethod(value = LiteFlowMethodEnum.PROCESS, nodeId = "a")
public void processA(NodeComponent bindCmp) {
String key = "test";
DefaultContext context = bindCmp.getFirstContextBean();
if (!context.hasData(key)){
context.setData(key, bindCmp.getCurrLoopObj());
}else{
String str = context.getData(key);
str += bindCmp.getCurrLoopObj();
context.setData(key, str);
}
}
@LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_BREAK, nodeId = "b", nodeType = NodeTypeEnum.BREAK)
public boolean processB(NodeComponent bindCmp) {
return bindCmp.getLoopIndex() == 1;
}
@LiteflowMethod(value = LiteFlowMethodEnum.PROCESS_ITERATOR, nodeId = "it", nodeType = NodeTypeEnum.ITERATOR)
public Iterator<?> processIT(NodeComponent bindCmp) {
List<String> list = bindCmp.getRequestData();
return list.iterator();
}
}

View File

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

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE flow PUBLIC "liteflow" "liteflow.dtd">
<flow>
<chain name="chain1">
ITERATOR(it).DO(a);
</chain>
<chain name="chain2">
ITERATOR(it).DO(a).BREAK(b);
</chain>
</flow>

View File

@@ -27,12 +27,12 @@ public class ACmp{
}
@LiteflowMethod(LiteFlowMethodEnum.BEFORE_PROCESS)
public void beforeAcmp(String nodeId, Slot slot){
public void beforeAcmp(NodeComponent bindCmp){
System.out.println("before A");
}
@LiteflowMethod(LiteFlowMethodEnum.AFTER_PROCESS)
public void afterAcmp(String nodeId, Slot slot){
public void afterAcmp(NodeComponent bindCmp){
System.out.println("after A");
}

View File

@@ -0,0 +1,57 @@
package com.yomahub.liteflow.test.iterator;
import cn.hutool.core.collection.ListUtil;
import com.yomahub.liteflow.core.FlowExecutor;
import com.yomahub.liteflow.flow.LiteflowResponse;
import com.yomahub.liteflow.slot.DefaultContext;
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.List;
/**
* springboot环境最普通的例子测试
* @author Bryan.Zhang
* @since 2.6.4
*/
@RunWith(SpringRunner.class)
@TestPropertySource(value = "classpath:/iterator/application.properties")
@SpringBootTest(classes = IteratorELDeclSpringbootTest.class)
@EnableAutoConfiguration
@ComponentScan({"com.yomahub.liteflow.test.iterator.cmp"})
public class IteratorELDeclSpringbootTest extends BaseTest {
@Resource
private FlowExecutor flowExecutor;
//最简单的情况
@Test
public void testIt1() throws Exception{
List<String> list = ListUtil.toList("1","2","3");
LiteflowResponse response = flowExecutor.execute2Resp("chain1", list);
Assert.assertTrue(response.isSuccess());
DefaultContext context = response.getFirstContextBean();
String str = context.getData("test");
Assert.assertEquals("123", str);
}
//迭代器带break
@Test
public void testIt2() throws Exception{
List<String> list = ListUtil.toList("1","2","3");
LiteflowResponse response = flowExecutor.execute2Resp("chain2", list);
Assert.assertTrue(response.isSuccess());
DefaultContext context = response.getFirstContextBean();
String str = context.getData("test");
Assert.assertEquals("12", str);
}
}

View File

@@ -0,0 +1,34 @@
/**
* <p>Title: liteflow</p>
* <p>Description: 轻量级的组件式流程框架</p>
* @author Bryan.Zhang
* @email weenyc31@163.com
* @Date 2020/4/1
*/
package com.yomahub.liteflow.test.iterator.cmp;
import com.yomahub.liteflow.annotation.LiteflowCmpDefine;
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 com.yomahub.liteflow.slot.DefaultContext;
import org.springframework.stereotype.Component;
@Component("a")
@LiteflowCmpDefine(NodeTypeEnum.COMMON)
public class ACmp{
@LiteflowMethod(LiteFlowMethodEnum.PROCESS)
public void process(NodeComponent bindCmp) {
String key = "test";
DefaultContext context = bindCmp.getFirstContextBean();
if (!context.hasData(key)){
context.setData(key, bindCmp.getCurrLoopObj());
}else{
String str = context.getData(key);
str += bindCmp.getCurrLoopObj();
context.setData(key, str);
}
}
}

View File

@@ -0,0 +1,20 @@
package com.yomahub.liteflow.test.iterator.cmp;
import com.yomahub.liteflow.annotation.LiteflowCmpDefine;
import com.yomahub.liteflow.annotation.LiteflowMethod;
import com.yomahub.liteflow.core.NodeBreakComponent;
import com.yomahub.liteflow.core.NodeComponent;
import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
import com.yomahub.liteflow.enums.NodeTypeEnum;
import org.springframework.stereotype.Component;
@Component("b")
@LiteflowCmpDefine(NodeTypeEnum.BREAK)
public class BCmp{
@LiteflowMethod(LiteFlowMethodEnum.PROCESS_BREAK)
public boolean processBreak(NodeComponent bindCmp) throws Exception {
return bindCmp.getLoopIndex() == 1;
}
}

View File

@@ -0,0 +1,22 @@
package com.yomahub.liteflow.test.iterator.cmp;
import com.yomahub.liteflow.annotation.LiteflowCmpDefine;
import com.yomahub.liteflow.annotation.LiteflowMethod;
import com.yomahub.liteflow.core.NodeComponent;
import com.yomahub.liteflow.core.NodeIteratorComponent;
import com.yomahub.liteflow.enums.LiteFlowMethodEnum;
import com.yomahub.liteflow.enums.NodeTypeEnum;
import org.springframework.stereotype.Component;
import java.util.Iterator;
import java.util.List;
@Component("it")
@LiteflowCmpDefine(NodeTypeEnum.ITERATOR)
public class ITCmp{
@LiteflowMethod(LiteFlowMethodEnum.PROCESS_ITERATOR)
public Iterator<?> processIterator(NodeComponent bindCmp) throws Exception {
List<String> list = bindCmp.getRequestData();
return list.iterator();
}
}

View File

@@ -47,7 +47,7 @@ public class SubflowInDifferentConfigELDeclSpringbootTest extends BaseTest {
@Test(expected = MultipleParsersException.class)
public void testExplicitSubFlow2() {
LiteflowConfig config = context.getBean(LiteflowConfig.class);
config.setRuleSource("subflow/flow-main.xml,subflow/flow-sub1.xml,subflow/flow-sub2.yml");
config.setRuleSource("subflow/flow-main.xml, subflow/flow-sub1.xml,subflow/flow-sub2.yml");
flowExecutor.reloadRule();
}
}

View File

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

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE flow PUBLIC "liteflow" "liteflow.dtd">
<flow>
<chain name="chain1">
ITERATOR(it).DO(a);
</chain>
<chain name="chain2">
ITERATOR(it).DO(a).BREAK(b);
</chain>
</flow>

View File

@@ -0,0 +1,48 @@
package com.yomahub.liteflow.test.iterator;
import cn.hutool.core.collection.ListUtil;
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.slot.DefaultContext;
import com.yomahub.liteflow.test.BaseTest;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.List;
public class IteratorTest extends BaseTest{
private static FlowExecutor flowExecutor;
@BeforeClass
public static void init(){
LiteflowConfig config = new LiteflowConfig();
config.setRuleSource("iterator/flow.xml");
flowExecutor = FlowExecutorHolder.loadInstance(config);
}
//最简单的情况
@Test
public void testIt1() throws Exception{
List<String> list = ListUtil.toList("1","2","3");
LiteflowResponse response = flowExecutor.execute2Resp("chain1", list);
Assert.assertTrue(response.isSuccess());
DefaultContext context = response.getFirstContextBean();
String str = context.getData("test");
Assert.assertEquals("123", str);
}
//迭代器带break
@Test
public void testIt2() throws Exception{
List<String> list = ListUtil.toList("1","2","3");
LiteflowResponse response = flowExecutor.execute2Resp("chain2", list);
Assert.assertTrue(response.isSuccess());
DefaultContext context = response.getFirstContextBean();
String str = context.getData("test");
Assert.assertEquals("12", str);
}
}

View File

@@ -0,0 +1,27 @@
/**
* <p>Title: liteflow</p>
* <p>Description: 轻量级的组件式流程框架</p>
* @author Bryan.Zhang
* @email weenyc31@163.com
* @Date 2020/4/1
*/
package com.yomahub.liteflow.test.iterator.cmp;
import com.yomahub.liteflow.core.NodeComponent;
import com.yomahub.liteflow.slot.DefaultContext;
public class ACmp extends NodeComponent {
@Override
public void process() {
String key = "test";
DefaultContext context = this.getFirstContextBean();
if (!context.hasData(key)){
context.setData(key, this.getCurrLoopObj());
}else{
String str = context.getData(key);
str += this.getCurrLoopObj();
context.setData(key, str);
}
}
}

View File

@@ -0,0 +1,12 @@
package com.yomahub.liteflow.test.iterator.cmp;
import com.yomahub.liteflow.core.NodeBreakComponent;
public class BCmp extends NodeBreakComponent {
@Override
public boolean processBreak() throws Exception {
return this.getLoopIndex() == 1;
}
}

View File

@@ -0,0 +1,14 @@
package com.yomahub.liteflow.test.iterator.cmp;
import com.yomahub.liteflow.core.NodeIteratorComponent;
import java.util.Iterator;
import java.util.List;
public class ITCmp extends NodeIteratorComponent {
@Override
public Iterator<?> processIterator() throws Exception {
List<String> list = this.getRequestData();
return list.iterator();
}
}

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE flow PUBLIC "liteflow" "liteflow.dtd">
<flow>
<nodes>
<node id="a" class="com.yomahub.liteflow.test.iterator.cmp.ACmp"/>
<node id="b" class="com.yomahub.liteflow.test.iterator.cmp.BCmp"/>
<node id="it" class="com.yomahub.liteflow.test.iterator.cmp.ITCmp"/>
</nodes>
<chain name="chain1">
ITERATOR(it).DO(a);
</chain>
<chain name="chain2">
ITERATOR(it).DO(a).BREAK(b);
</chain>
</flow>

View File

@@ -1,5 +1,6 @@
package com.yomahub.liteflow.test.script.groovy.common;
import cn.hutool.core.io.resource.ClassPathResource;
import com.yomahub.liteflow.builder.LiteFlowNodeBuilder;
import com.yomahub.liteflow.builder.el.LiteFlowChainELBuilder;
import com.yomahub.liteflow.core.FlowExecutor;
@@ -25,6 +26,44 @@ public class LiteFlowXmlScriptBuilderGroovyELTest extends BaseTest {
@Resource
private FlowExecutor flowExecutor;
/**
* 测试通过builder方式运行普通script节点以file绝对路径的方式运行
*/
@Test
public void testAbsoluteScriptFilePath(){
String absolutePath = new ClassPathResource("classpath:builder/s2.groovy").getAbsolutePath();
LiteFlowNodeBuilder.createNode().setId("d")
.setName("组件D")
.setType(NodeTypeEnum.COMMON)
.setClazz("com.yomahub.liteflow.test.script.groovy.common.cmp.DCmp")
.build();
LiteFlowNodeBuilder.createNode().setId("s2")
.setName("条件脚本S2")
.setType(NodeTypeEnum.SWITCH_SCRIPT)
.setFile(absolutePath)
.build();
LiteFlowNodeBuilder.createNode().setId("a")
.setName("组件A")
.setType(NodeTypeEnum.COMMON)
.setClazz("com.yomahub.liteflow.test.script.groovy.common.cmp.ACmp")
.build();
LiteFlowNodeBuilder.createNode().setId("b")
.setName("组件B")
.setType(NodeTypeEnum.COMMON)
.setClazz("com.yomahub.liteflow.test.script.groovy.common.cmp.BCmp")
.build();
LiteFlowChainELBuilder.createChain().setChainName("chain2")
.setEL("THEN(d,SWITCH(s2).to(a,b))")
.build();
LiteflowResponse response = flowExecutor.execute2Resp("chain2","arg1");
DefaultContext context = response.getFirstContextBean();
Assert.assertTrue(response.isSuccess());
Assert.assertEquals("d[组件D]==>s2[条件脚本S2]==>a[组件A]", response.getExecuteStepStr());
}
//测试通过builder方式运行普通script节点以脚本文本的方式运行
@Test
public void testBuilderScript1() {

View File

@@ -3,6 +3,7 @@ package com.yomahub.liteflow.test.script.groovy.scriptbean;
import com.yomahub.liteflow.core.FlowExecutor;
import com.yomahub.liteflow.exception.ScriptBeanMethodInvokeException;
import com.yomahub.liteflow.flow.LiteflowResponse;
import com.yomahub.liteflow.script.ScriptBeanManager;
import com.yomahub.liteflow.slot.DefaultContext;
import com.yomahub.liteflow.test.BaseTest;
import org.junit.Assert;
@@ -15,6 +16,8 @@ import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
@RunWith(SpringRunner.class)
@TestPropertySource(value = "classpath:/scriptbean/application.properties")
@@ -75,4 +78,15 @@ public class LiteFlowScriptScriptbeanGroovyELTest extends BaseTest {
Assert.assertFalse(response.isSuccess());
Assert.assertEquals(ScriptBeanMethodInvokeException.class, response.getCause().getClass());
}
//测试在ScriptBeanManager里放入上下文实现自定义脚本引用名称
@Test
public void testScriptBean7() throws Exception{
Map<String, String> map = new HashMap<>();
ScriptBeanManager.addScriptBean("abcCx", map);
LiteflowResponse response = flowExecutor.execute2Resp("chain7", "arg", map);
Assert.assertTrue(response.isSuccess());
Map<String, String> context = response.getFirstContextBean();
Assert.assertEquals("hello", context.get("demo"));
}
}

View File

@@ -43,6 +43,13 @@
defaultContext.setData("demo", str)
]]>
</node>
<node id="s5" type="script" language="groovy">
<![CDATA[
def str = demo.getDemoStr1()
abcCx.put("demo", str)
]]>
</node>
</nodes>
<chain name="chain1">
@@ -68,4 +75,8 @@
<chain name="chain6">
THEN(a,b,c,s4);
</chain>
<chain name="chain7">
THEN(a,b,c,s5);
</chain>
</flow>

View File

@@ -1,8 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE flow PUBLIC "liteflow" "liteflow.dtd">
<flow>
<nodes>
<node id="s1" name="普通脚本1" type="script">
<node id="s1" name="普通脚本1" type="script" language="python">
<![CDATA[
import json
x='{"name": "Jack", "age": 75, "nationality": "China"}'
jsonData=json.loads(x)
temperature=jsonData['name']
print(temperature)
a=6
b=10
if a>5:

View File

@@ -0,0 +1,55 @@
package com.yomahub.liteflow.test.iterator;
import cn.hutool.core.collection.ListUtil;
import com.yomahub.liteflow.core.FlowExecutor;
import com.yomahub.liteflow.flow.LiteflowResponse;
import com.yomahub.liteflow.slot.DefaultContext;
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.List;
/**
* springboot环境EL常规的例子测试
* @author Bryan.Zhang
*/
@RunWith(SpringRunner.class)
@TestPropertySource(value = "classpath:/iterator/application.properties")
@SpringBootTest(classes = IteratorELSpringbootTest.class)
@EnableAutoConfiguration
@ComponentScan({"com.yomahub.liteflow.test.iterator.cmp"})
public class IteratorELSpringbootTest extends BaseTest {
@Resource
private FlowExecutor flowExecutor;
//最简单的情况
@Test
public void testIt1() throws Exception{
List<String> list = ListUtil.toList("1","2","3");
LiteflowResponse response = flowExecutor.execute2Resp("chain1", list);
Assert.assertTrue(response.isSuccess());
DefaultContext context = response.getFirstContextBean();
String str = context.getData("test");
Assert.assertEquals("123", str);
}
//迭代器带break
@Test
public void testIt2() throws Exception{
List<String> list = ListUtil.toList("1","2","3");
LiteflowResponse response = flowExecutor.execute2Resp("chain2", list);
Assert.assertTrue(response.isSuccess());
DefaultContext context = response.getFirstContextBean();
String str = context.getData("test");
Assert.assertEquals("12", str);
}
}

View File

@@ -0,0 +1,29 @@
/**
* <p>Title: liteflow</p>
* <p>Description: 轻量级的组件式流程框架</p>
* @author Bryan.Zhang
* @email weenyc31@163.com
* @Date 2020/4/1
*/
package com.yomahub.liteflow.test.iterator.cmp;
import com.yomahub.liteflow.core.NodeComponent;
import com.yomahub.liteflow.slot.DefaultContext;
import org.springframework.stereotype.Component;
@Component("a")
public class ACmp extends NodeComponent {
@Override
public void process() {
String key = "test";
DefaultContext context = this.getFirstContextBean();
if (!context.hasData(key)){
context.setData(key, this.getCurrLoopObj());
}else{
String str = context.getData(key);
str += this.getCurrLoopObj();
context.setData(key, str);
}
}
}

View File

@@ -0,0 +1,15 @@
package com.yomahub.liteflow.test.iterator.cmp;
import com.yomahub.liteflow.core.NodeBreakComponent;
import com.yomahub.liteflow.core.NodeComponent;
import org.springframework.stereotype.Component;
@Component("b")
public class BCmp extends NodeBreakComponent {
@Override
public boolean processBreak() throws Exception {
return this.getLoopIndex() == 1;
}
}

View File

@@ -0,0 +1,16 @@
package com.yomahub.liteflow.test.iterator.cmp;
import com.yomahub.liteflow.core.NodeIteratorComponent;
import org.springframework.stereotype.Component;
import java.util.Iterator;
import java.util.List;
@Component("it")
public class ITCmp extends NodeIteratorComponent {
@Override
public Iterator<?> processIterator() throws Exception {
List<String> list = this.getRequestData();
return list.iterator();
}
}

View File

@@ -84,4 +84,12 @@ public class SwitchELSpringbootTest extends BaseTest {
Assert.assertEquals("a==>i==>d",response.getExecuteStepStr());
}
//switch返回如果是空会走default选项
@Test
public void testSwitch8() throws Exception{
LiteflowResponse response = flowExecutor.execute2Resp("chain8", "arg");
Assert.assertTrue(response.isSuccess());
Assert.assertEquals("a==>j==>d",response.getExecuteStepStr());
}
}

View File

@@ -0,0 +1,20 @@
/**
* <p>Title: liteflow</p>
* <p>Description: 轻量级的组件式流程框架</p>
* @author Tingliang Wang
* @email bytlwang@126.com
* @Date 2022/12/09
*/
package com.yomahub.liteflow.test.switchcase.cmp;
import com.yomahub.liteflow.core.NodeSwitchComponent;
import org.springframework.stereotype.Component;
@Component("j")
public class JSwitchCmp extends NodeSwitchComponent {
@Override
public String processSwitch() throws Exception {
return "";
}
}

View File

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

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE flow PUBLIC "liteflow" "liteflow.dtd">
<flow>
<chain name="chain1">
ITERATOR(it).DO(a);
</chain>
<chain name="chain2">
ITERATOR(it).DO(a).BREAK(b);
</chain>
</flow>

View File

@@ -50,4 +50,10 @@
);
</chain>
<chain name="chain8">
THEN(
a,
SWITCH(j).to(b, c).DEFAULT(d)
);
</chain>
</flow>

View File

@@ -0,0 +1,45 @@
package com.yomahub.liteflow.test.iterator;
import cn.hutool.core.collection.ListUtil;
import com.yomahub.liteflow.core.FlowExecutor;
import com.yomahub.liteflow.flow.LiteflowResponse;
import com.yomahub.liteflow.slot.DefaultContext;
import com.yomahub.liteflow.test.BaseTest;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
import java.util.List;
@RunWith(SpringRunner.class)
@ContextConfiguration("classpath:/iterator/application.xml")
public class IteratorELSpringTest extends BaseTest {
@Resource
private FlowExecutor flowExecutor;
//最简单的情况
@Test
public void testIt1() throws Exception{
List<String> list = ListUtil.toList("1","2","3");
LiteflowResponse response = flowExecutor.execute2Resp("chain1", list);
Assert.assertTrue(response.isSuccess());
DefaultContext context = response.getFirstContextBean();
String str = context.getData("test");
Assert.assertEquals("123", str);
}
//迭代器带break
@Test
public void testIt2() throws Exception{
List<String> list = ListUtil.toList("1","2","3");
LiteflowResponse response = flowExecutor.execute2Resp("chain2", list);
Assert.assertTrue(response.isSuccess());
DefaultContext context = response.getFirstContextBean();
String str = context.getData("test");
Assert.assertEquals("12", str);
}
}

View File

@@ -0,0 +1,29 @@
/**
* <p>Title: liteflow</p>
* <p>Description: 轻量级的组件式流程框架</p>
* @author Bryan.Zhang
* @email weenyc31@163.com
* @Date 2020/4/1
*/
package com.yomahub.liteflow.test.iterator.cmp;
import com.yomahub.liteflow.core.NodeComponent;
import com.yomahub.liteflow.slot.DefaultContext;
import org.springframework.stereotype.Component;
@Component("a")
public class ACmp extends NodeComponent {
@Override
public void process() {
String key = "test";
DefaultContext context = this.getFirstContextBean();
if (!context.hasData(key)){
context.setData(key, this.getCurrLoopObj());
}else{
String str = context.getData(key);
str += this.getCurrLoopObj();
context.setData(key, str);
}
}
}

View File

@@ -0,0 +1,14 @@
package com.yomahub.liteflow.test.iterator.cmp;
import com.yomahub.liteflow.core.NodeBreakComponent;
import org.springframework.stereotype.Component;
@Component("b")
public class BCmp extends NodeBreakComponent {
@Override
public boolean processBreak() throws Exception {
return this.getLoopIndex() == 1;
}
}

View File

@@ -0,0 +1,16 @@
package com.yomahub.liteflow.test.iterator.cmp;
import com.yomahub.liteflow.core.NodeIteratorComponent;
import org.springframework.stereotype.Component;
import java.util.Iterator;
import java.util.List;
@Component("it")
public class ITCmp extends NodeIteratorComponent {
@Override
public Iterator<?> processIterator() throws Exception {
List<String> list = this.getRequestData();
return list.iterator();
}
}

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<context:component-scan base-package="com.yomahub.liteflow.test.iterator.cmp" />
<bean id="springAware" class="com.yomahub.liteflow.spi.spring.SpringAware"/>
<bean class="com.yomahub.liteflow.spring.ComponentScanner"/>
<bean id="liteflowConfig" class="com.yomahub.liteflow.property.LiteflowConfig">
<property name="ruleSource" value="iterator/flow.xml"/>
</bean>
<bean id="flowExecutor" class="com.yomahub.liteflow.core.FlowExecutor">
<constructor-arg name="liteflowConfig" ref="liteflowConfig"/>
</bean>
</beans>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE flow PUBLIC "liteflow" "liteflow.dtd">
<flow>
<chain name="chain1">
ITERATOR(it).DO(a);
</chain>
<chain name="chain2">
ITERATOR(it).DO(a).BREAK(b);
</chain>
</flow>

View File

@@ -39,7 +39,7 @@
</scm>
<properties>
<revision>2.9.6</revision>
<revision>2.9.7</revision>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>8</maven.compiler.source>
@@ -52,7 +52,7 @@
<dom4j.version>2.1.3</dom4j.version>
<curator.version>5.3.0</curator.version>
<junit.version>4.12</junit.version>
<hutool-core.version>5.8.8</hutool-core.version>
<hutool-core.version>5.8.11</hutool-core.version>
<transmittable-thread-local.version>2.12.3</transmittable-thread-local.version>
<curator-test.version>5.1.0</curator-test.version>
<zkclient.version>0.10</zkclient.version>
@@ -64,9 +64,7 @@
<bytebuddy.version>1.11.13</bytebuddy.version>
<aspectjweaver.version>1.8.13</aspectjweaver.version>
<logback-classic.version>1.2.3</logback-classic.version>
<solon.version>1.12.0</solon.version>
<solon.version>2.0.0</solon.version>
<netty.version>4.1.84.Final</netty.version>
<guava.version>31.1-jre</guava.version>
<httpclient.version>4.5.13</httpclient.version>