diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/annotation/FallbackCmp.java b/liteflow-core/src/main/java/com/yomahub/liteflow/annotation/FallbackCmp.java new file mode 100644 index 000000000..caf9deed6 --- /dev/null +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/annotation/FallbackCmp.java @@ -0,0 +1,27 @@ +package com.yomahub.liteflow.annotation; + +import com.yomahub.liteflow.enums.NodeTypeEnum; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 降级组件 + * @author DaleLee + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface FallbackCmp { + + /** + * 节点类型 + * @return NodeTypeEnum + */ + NodeTypeEnum type() default NodeTypeEnum.COMMON; +} diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/operator/NodeOperator.java b/liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/operator/NodeOperator.java index 825b6a758..2013b3b4e 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/operator/NodeOperator.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/builder/el/operator/NodeOperator.java @@ -2,10 +2,16 @@ package com.yomahub.liteflow.builder.el.operator; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; +import com.ql.util.express.ArraySwap; +import com.ql.util.express.IExpressContext; +import com.ql.util.express.InstructionSetContext; +import com.ql.util.express.OperateData; 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.core.NodeComponent; import com.yomahub.liteflow.flow.FlowBus; +import com.yomahub.liteflow.flow.element.FallbackNodeProxy; import com.yomahub.liteflow.flow.element.Node; import com.yomahub.liteflow.property.LiteflowConfig; import com.yomahub.liteflow.property.LiteflowConfigGetter; @@ -17,41 +23,19 @@ import com.yomahub.liteflow.property.LiteflowConfigGetter; * @since 2.8.3 */ public class NodeOperator extends BaseOperator { - + @Override public Node build(Object[] objects) throws Exception { + OperatorHelper.checkObjectSizeEqOne(objects); - String nodeId = OperatorHelper.convert(objects[0], String.class); - + if (FlowBus.containNode(nodeId)) { + // 找到对应节点 return FlowBus.getNode(nodeId); - } - else { - LiteflowConfig liteflowConfig = LiteflowConfigGetter.get(); - if (StrUtil.isNotBlank(liteflowConfig.getSubstituteCmpClass())) { - Node substituteNode = FlowBus.getNodeMap() - .values() - .stream() - .filter(node -> node.getInstance() - .getClass() - .getName() - .equals(liteflowConfig.getSubstituteCmpClass())) - .findFirst() - .orElse(null); - if (ObjectUtil.isNotNull(substituteNode)) { - return substituteNode; - } - else { - String error = StrUtil.format("This node[{}] cannot be found", nodeId); - throw new QLException(error); - } - } - else { - String error = StrUtil.format("This node[{}] cannot be found, or you can configure an substitute node", - nodeId); - throw new QLException(error); - } + } else { + // 生成代理节点 + return new FallbackNodeProxy(nodeId); } } diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/exception/FallbackCmpNotFoundException.java b/liteflow-core/src/main/java/com/yomahub/liteflow/exception/FallbackCmpNotFoundException.java new file mode 100644 index 000000000..a334ea60f --- /dev/null +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/exception/FallbackCmpNotFoundException.java @@ -0,0 +1,30 @@ +package com.yomahub.liteflow.exception; + +/** + * 没有找到降级组件异常 + * + * @author DaleLee + */ +public class FallbackCmpNotFoundException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + /** + * 异常信息 + */ + private String message; + + public FallbackCmpNotFoundException(String message) { + this.message = message; + } + + @Override + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + +} diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/flow/FlowBus.java b/liteflow-core/src/main/java/com/yomahub/liteflow/flow/FlowBus.java index 1f8d1954d..218502db0 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/flow/FlowBus.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/flow/FlowBus.java @@ -6,11 +6,14 @@ * @email weenyc31@163.com * @Date 2020/4/1 */ + package com.yomahub.liteflow.flow; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; +import com.yomahub.liteflow.annotation.FallbackCmp; +import com.yomahub.liteflow.annotation.util.AnnoUtil; import com.yomahub.liteflow.core.*; import com.yomahub.liteflow.enums.FlowParserTypeEnum; import com.yomahub.liteflow.enums.NodeTypeEnum; @@ -31,6 +34,7 @@ import com.yomahub.liteflow.spi.holder.ContextAwareHolder; import com.yomahub.liteflow.spi.local.LocalContextAware; import com.yomahub.liteflow.util.CopyOnWriteHashMap; import com.yomahub.liteflow.util.LiteFlowProxyUtil; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -43,230 +47,247 @@ import java.util.stream.Collectors; * @author Bryan.Zhang */ public class FlowBus { - - private static final LFLog LOG = LFLoggerManager.getLogger(FlowBus.class); - - private static final Map chainMap = new CopyOnWriteHashMap<>(); - - private static final Map nodeMap = new CopyOnWriteHashMap<>(); - - private FlowBus() { - } - - public static Chain getChain(String id) { - return chainMap.get(id); - } - - // 这一方法主要用于第一阶段chain的预装载 - public static void addChain(String chainName) { - if (!chainMap.containsKey(chainName)) { - chainMap.put(chainName, new Chain(chainName)); - } - } - - // 这个方法主要用于第二阶段的替换chain - public static void addChain(Chain chain) { - chainMap.put(chain.getChainId(), chain); - } - - public static boolean containChain(String chainId) { - return chainMap.containsKey(chainId); - } - - public static boolean needInit() { - return MapUtil.isEmpty(chainMap); - } - - public static boolean containNode(String nodeId) { - return nodeMap.containsKey(nodeId); - } - - /** - * 添加已托管的节点(如:Spring、Solon 管理的节点) - * */ - public static void addManagedNode(String nodeId, NodeComponent nodeComponent) { - // 根据class来猜测类型 - NodeTypeEnum type = NodeTypeEnum.guessType(nodeComponent.getClass()); - - if (type == null) { - throw new NullNodeTypeException(StrUtil.format("node type is null for node[{}]", nodeId)); - } - - nodeMap.put(nodeId, - new Node(ComponentInitializer.loadInstance().initComponent(nodeComponent, type, nodeComponent.getName(), nodeId))); - } - - /** - * 添加 node - * @param nodeId 节点id - * @param name 节点名称 - * @param type 节点类型 - * @param cmpClazz 节点组件类 - */ - public static void addNode(String nodeId, String name, NodeTypeEnum type, Class cmpClazz) { - addNode(nodeId, name, type, cmpClazz, null, null); - } - - /** - * 添加 node - * @param nodeId 节点id - * @param name 节点名称 - * @param nodeType 节点类型 - * @param cmpClazzStr 节点组件类路径 - */ - public static void addNode(String nodeId, String name, NodeTypeEnum nodeType, String cmpClazzStr) { - Class cmpClazz; - try { - cmpClazz = Class.forName(cmpClazzStr); - } - catch (Exception e) { - throw new ComponentCannotRegisterException(e.getMessage()); - } - addNode(nodeId, name, nodeType, cmpClazz, null, null); - } - - /** - * 添加脚本 node - * @param nodeId 节点id - * @param name 节点名称 - * @param nodeType 节点类型 - * @param script 脚本 - */ - public static void addScriptNode(String nodeId, String name, NodeTypeEnum nodeType, String script, - String language) { - addNode(nodeId, name, nodeType, ScriptComponent.ScriptComponentClassMap.get(nodeType), script, language); - } - - private static void addNode(String nodeId, String name, NodeTypeEnum type, Class cmpClazz, String script, - String language) { - try { - // 判断此类是否是声明式的组件,如果是声明式的组件,就用动态代理生成实例 - // 如果不是声明式的,就用传统的方式进行判断 - List cmpInstances = new ArrayList<>(); - if (LiteFlowProxyUtil.isDeclareCmp(cmpClazz)) { - // 这里的逻辑要仔细看下 - // 如果是spring体系,把原始的类往spring上下文中进行注册,那么会走到ComponentScanner中 - // 由于ComponentScanner中已经对原始类进行了动态代理,出来的对象已经变成了动态代理类,所以这时候的bean已经是NodeComponent的子类了 - // 所以spring体系下,无需再对这个bean做二次代理 - // 但是在非spring体系下,这个bean依旧是原来那个bean,所以需要对这个bean做一次代理 - // 这里用ContextAware的spi机制来判断是否spring体系 - ContextAware contextAware = ContextAwareHolder.loadContextAware(); - Object bean = ContextAwareHolder.loadContextAware().registerBean(nodeId, cmpClazz); - if (LocalContextAware.class.isAssignableFrom(contextAware.getClass())) { - cmpInstances = LiteFlowProxyUtil.proxy2NodeComponent(bean, nodeId); - } - else { - cmpInstances = ListUtil.toList((NodeComponent) bean); - } - } - else { - // 以node方式配置,本质上是为了适配无spring的环境,如果有spring环境,其实不用这么配置 - // 这里的逻辑是判断是否能从spring上下文中取到,如果没有spring,则就是new instance了 - // 如果是script类型的节点,因为class只有一个,所以也不能注册进spring上下文,注册的时候需要new Instance - if (!type.isScript()) { - cmpInstances = ListUtil - .toList((NodeComponent) ContextAwareHolder.loadContextAware().registerOrGet(nodeId, cmpClazz)); - } - // 去除null元素 - cmpInstances.remove(null); - // 如果为空 - if (cmpInstances.isEmpty()) { - NodeComponent cmpInstance = (NodeComponent) cmpClazz.newInstance(); - cmpInstances.add(cmpInstance); - } - } - // 进行初始化component - cmpInstances = cmpInstances.stream() - .map(cmpInstance -> ComponentInitializer.loadInstance() - .initComponent(cmpInstance, type, name, - cmpInstance.getNodeId() == null ? nodeId : cmpInstance.getNodeId())) - .collect(Collectors.toList()); - - // 初始化Node,把component放到Node里去 - List nodes = cmpInstances.stream().map(Node::new).collect(Collectors.toList()); - - for (int i = 0; i < nodes.size(); i++) { - Node node = nodes.get(i); - NodeComponent cmpInstance = cmpInstances.get(i); - // 如果是脚本节点,则还要加载script脚本 - if (type.isScript()) { - if (StrUtil.isNotBlank(script)) { - node.setScript(script); - node.setLanguage(language); - ((ScriptComponent) cmpInstance).loadScript(script, language); - } - else { - String errorMsg = StrUtil.format("script for node[{}] is empty", nodeId); - throw new ScriptLoadException(errorMsg); - } - } - - String activeNodeId = StrUtil.isEmpty(cmpInstance.getNodeId()) ? nodeId : cmpInstance.getNodeId(); - nodeMap.put(activeNodeId, node); - } - - } - 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(StrUtil.format("{} {}", error, e.getMessage())); - } - } - - public static Node getNode(String nodeId) { - return nodeMap.get(nodeId); - } - - public static Map getNodeMap() { - return nodeMap; - } - - public static Map getChainMap() { - return chainMap; - } - - public static void cleanCache() { - chainMap.clear(); - nodeMap.clear(); - cleanScriptCache(); - } - - public static void cleanScriptCache() { - // 如果引入了脚本组件SPI,则还需要清理脚本的缓存 - try { - ScriptExecutorFactory.loadInstance().cleanScriptCache(); - } - catch (ScriptSpiException ignored) { - } - } - - public static void refreshFlowMetaData(FlowParserTypeEnum type, String content) throws Exception { - if (type.equals(FlowParserTypeEnum.TYPE_EL_XML)) { - new LocalXmlFlowELParser().parse(content); - } - else if (type.equals(FlowParserTypeEnum.TYPE_EL_JSON)) { - new LocalJsonFlowELParser().parse(content); - } - else if (type.equals(FlowParserTypeEnum.TYPE_EL_YML)) { - new LocalYmlFlowELParser().parse(content); - } - } - - public static boolean removeChain(String chainId) { - if (containChain(chainId)) { - chainMap.remove(chainId); - return true; - } - else { - String errMsg = StrUtil.format("cannot find the chain[{}]", chainId); - LOG.error(errMsg); - return false; - } - } - - public static void removeChain(String... chainIds) { - Arrays.stream(chainIds).forEach(FlowBus::removeChain); - } - + + private static final LFLog LOG = LFLoggerManager.getLogger(FlowBus.class); + + private static final Map chainMap = new CopyOnWriteHashMap<>(); + + private static final Map nodeMap = new CopyOnWriteHashMap<>(); + + private static final Map fallbackNodeMap = new CopyOnWriteHashMap<>(); + + private FlowBus() { + } + + public static Chain getChain(String id) { + return chainMap.get(id); + } + + // 这一方法主要用于第一阶段chain的预装载 + public static void addChain(String chainName) { + if (!chainMap.containsKey(chainName)) { + chainMap.put(chainName, new Chain(chainName)); + } + } + + // 这个方法主要用于第二阶段的替换chain + public static void addChain(Chain chain) { + chainMap.put(chain.getChainId(), chain); + } + + public static boolean containChain(String chainId) { + return chainMap.containsKey(chainId); + } + + public static boolean needInit() { + return MapUtil.isEmpty(chainMap); + } + + public static boolean containNode(String nodeId) { + return nodeMap.containsKey(nodeId); + } + + /** + * 添加已托管的节点(如:Spring、Solon 管理的节点) + */ + public static void addManagedNode(String nodeId, NodeComponent nodeComponent) { + // 根据class来猜测类型 + NodeTypeEnum type = NodeTypeEnum.guessType(nodeComponent.getClass()); + + if (type == null) { + throw new NullNodeTypeException(StrUtil.format("node type is null for node[{}]", nodeId)); + } + + Node node = new Node(ComponentInitializer.loadInstance() + .initComponent(nodeComponent, type, nodeComponent.getName(), nodeId)); + nodeMap.put(nodeId, node); + addFallbackNode(node); + } + + /** + * 添加 node + * + * @param nodeId 节点id + * @param name 节点名称 + * @param type 节点类型 + * @param cmpClazz 节点组件类 + */ + public static void addNode(String nodeId, String name, NodeTypeEnum type, Class cmpClazz) { + addNode(nodeId, name, type, cmpClazz, null, null); + } + + /** + * 添加 node + * + * @param nodeId 节点id + * @param name 节点名称 + * @param nodeType 节点类型 + * @param cmpClazzStr 节点组件类路径 + */ + public static void addNode(String nodeId, String name, NodeTypeEnum nodeType, String cmpClazzStr) { + Class cmpClazz; + try { + cmpClazz = Class.forName(cmpClazzStr); + } catch (Exception e) { + throw new ComponentCannotRegisterException(e.getMessage()); + } + addNode(nodeId, name, nodeType, cmpClazz, null, null); + } + + /** + * 添加脚本 node + * + * @param nodeId 节点id + * @param name 节点名称 + * @param nodeType 节点类型 + * @param script 脚本 + */ + public static void addScriptNode(String nodeId, String name, NodeTypeEnum nodeType, String script, + String language) { + addNode(nodeId, name, nodeType, ScriptComponent.ScriptComponentClassMap.get(nodeType), script, language); + } + + private static void addNode(String nodeId, String name, NodeTypeEnum type, Class cmpClazz, String script, + String language) { + try { + // 判断此类是否是声明式的组件,如果是声明式的组件,就用动态代理生成实例 + // 如果不是声明式的,就用传统的方式进行判断 + List cmpInstances = new ArrayList<>(); + if (LiteFlowProxyUtil.isDeclareCmp(cmpClazz)) { + // 这里的逻辑要仔细看下 + // 如果是spring体系,把原始的类往spring上下文中进行注册,那么会走到ComponentScanner中 + // 由于ComponentScanner中已经对原始类进行了动态代理,出来的对象已经变成了动态代理类,所以这时候的bean已经是NodeComponent的子类了 + // 所以spring体系下,无需再对这个bean做二次代理 + // 但是在非spring体系下,这个bean依旧是原来那个bean,所以需要对这个bean做一次代理 + // 这里用ContextAware的spi机制来判断是否spring体系 + ContextAware contextAware = ContextAwareHolder.loadContextAware(); + Object bean = ContextAwareHolder.loadContextAware().registerBean(nodeId, cmpClazz); + if (LocalContextAware.class.isAssignableFrom(contextAware.getClass())) { + cmpInstances = LiteFlowProxyUtil.proxy2NodeComponent(bean, nodeId); + } else { + cmpInstances = ListUtil.toList((NodeComponent) bean); + } + } else { + // 以node方式配置,本质上是为了适配无spring的环境,如果有spring环境,其实不用这么配置 + // 这里的逻辑是判断是否能从spring上下文中取到,如果没有spring,则就是new instance了 + // 如果是script类型的节点,因为class只有一个,所以也不能注册进spring上下文,注册的时候需要new Instance + if (!type.isScript()) { + cmpInstances = ListUtil.toList( + (NodeComponent) ContextAwareHolder.loadContextAware().registerOrGet(nodeId, cmpClazz)); + } + // 去除null元素 + cmpInstances.remove(null); + // 如果为空 + if (cmpInstances.isEmpty()) { + NodeComponent cmpInstance = (NodeComponent) cmpClazz.newInstance(); + cmpInstances.add(cmpInstance); + } + } + // 进行初始化component + cmpInstances = cmpInstances.stream().map(cmpInstance -> ComponentInitializer.loadInstance() + .initComponent(cmpInstance, type, name, + cmpInstance.getNodeId() == null ? nodeId : cmpInstance.getNodeId())) + .collect(Collectors.toList()); + + // 初始化Node,把component放到Node里去 + List nodes = cmpInstances.stream().map(Node::new).collect(Collectors.toList()); + + for (int i = 0; i < nodes.size(); i++) { + Node node = nodes.get(i); + NodeComponent cmpInstance = cmpInstances.get(i); + // 如果是脚本节点,则还要加载script脚本 + if (type.isScript()) { + if (StrUtil.isNotBlank(script)) { + node.setScript(script); + node.setLanguage(language); + ((ScriptComponent) cmpInstance).loadScript(script, language); + } else { + String errorMsg = StrUtil.format("script for node[{}] is empty", nodeId); + throw new ScriptLoadException(errorMsg); + } + } + + String activeNodeId = StrUtil.isEmpty(cmpInstance.getNodeId()) ? nodeId : cmpInstance.getNodeId(); + nodeMap.put(activeNodeId, node); + addFallbackNode(node); + } + + } 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(StrUtil.format("{} {}", error, e.getMessage())); + } + } + + public static Node getNode(String nodeId) { + return nodeMap.get(nodeId); + } + + public static Map getNodeMap() { + return nodeMap; + } + + public static Map getChainMap() { + return chainMap; + } + + public static Node getFallBackNode(NodeTypeEnum nodeType) { + return fallbackNodeMap.get(nodeType); + } + + public static void cleanCache() { + chainMap.clear(); + nodeMap.clear(); + fallbackNodeMap.clear(); + cleanScriptCache(); + } + + public static void cleanScriptCache() { + // 如果引入了脚本组件SPI,则还需要清理脚本的缓存 + try { + ScriptExecutorFactory.loadInstance().cleanScriptCache(); + } catch (ScriptSpiException ignored) { + } + } + + public static void refreshFlowMetaData(FlowParserTypeEnum type, String content) throws Exception { + if (type.equals(FlowParserTypeEnum.TYPE_EL_XML)) { + new LocalXmlFlowELParser().parse(content); + } else if (type.equals(FlowParserTypeEnum.TYPE_EL_JSON)) { + new LocalJsonFlowELParser().parse(content); + } else if (type.equals(FlowParserTypeEnum.TYPE_EL_YML)) { + new LocalYmlFlowELParser().parse(content); + } + } + + public static boolean removeChain(String chainId) { + if (containChain(chainId)) { + chainMap.remove(chainId); + return true; + } else { + String errMsg = StrUtil.format("cannot find the chain[{}]", chainId); + LOG.error(errMsg); + return false; + } + } + + public static void removeChain(String... chainIds) { + Arrays.stream(chainIds).forEach(FlowBus::removeChain); + } + + private static void addFallbackNode(Node node) { + NodeComponent nodeComponent = node.getInstance(); + FallbackCmp fallbackCmp = AnnoUtil.getAnnotation(nodeComponent.getClass(), FallbackCmp.class); + if (fallbackCmp == null) { + return; + } + + NodeTypeEnum nodeType = node.getType(); + if (nodeType == null) { + nodeType = fallbackCmp.type(); + } + fallbackNodeMap.put(nodeType, node); + } + } diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/Condition.java b/liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/Condition.java index 60ccafe18..b485151fe 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/Condition.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/Condition.java @@ -1,10 +1,12 @@ /** *

Title: liteflow

*

Description: 轻量级的组件式流程框架

+ * * @author Bryan.Zhang * @email weenyc31@163.com * @Date 2020/4/1 */ + package com.yomahub.liteflow.flow.element; import cn.hutool.core.collection.CollUtil; @@ -12,7 +14,6 @@ import cn.hutool.core.collection.ListUtil; import cn.hutool.core.util.ObjectUtil; import com.yomahub.liteflow.enums.ExecuteTypeEnum; import com.yomahub.liteflow.exception.ChainEndException; -import com.yomahub.liteflow.flow.element.Executable; import com.yomahub.liteflow.enums.ConditionTypeEnum; import com.yomahub.liteflow.flow.element.condition.ConditionKey; import com.yomahub.liteflow.slot.DataBus; @@ -28,133 +29,133 @@ import java.util.Map; * * @author Bryan.Zhang */ -public abstract class Condition implements Executable{ - - private String id; - - private String tag; - - /** - * 可执行元素的集合 - */ - private final Map> executableGroup = new HashMap<>(); - - /** - * 当前所在的ChainName 如果对于子流程来说,那这个就是子流程所在的Chain - */ - private String currChainId; - - @Override - public void execute(Integer slotIndex) throws Exception { - try { - executeCondition(slotIndex); - } - catch (ChainEndException e) { - // 这里单独catch ChainEndException是因为ChainEndException是用户自己setIsEnd抛出的异常 - // 是属于正常逻辑,所以会在FlowExecutor中判断。这里不作为异常处理 - throw e; - } - catch (Exception e) { - Slot slot = DataBus.getSlot(slotIndex); - String chainId = this.getCurrChainId(); - // 这里事先取到exception set到slot里,为了方便finally取到exception - if (slot.isSubChain(chainId)) { - slot.setSubException(chainId, e); - } - else { - slot.setException(e); - } - throw e; - } - } - - public abstract void executeCondition(Integer slotIndex) throws Exception; - - @Override - public ExecuteTypeEnum getExecuteType() { - return ExecuteTypeEnum.CONDITION; - } - - public List getExecutableList() { - return getExecutableList(ConditionKey.DEFAULT_KEY); - } - - public List getExecutableList(String groupKey) { - List executableList = this.executableGroup.get(groupKey); - if (CollUtil.isEmpty(executableList)) { - executableList = new ArrayList<>(); - } - return executableList; - } - - public Executable getExecutableOne(String groupKey) { - List list = getExecutableList(groupKey); - if (CollUtil.isEmpty(list)) { - return null; - } - else { - return list.get(0); - } - } - - public void setExecutableList(List executableList) { - this.executableGroup.put(ConditionKey.DEFAULT_KEY, executableList); - } - - public void addExecutable(Executable executable) { - addExecutable(ConditionKey.DEFAULT_KEY, executable); - } - - public void addExecutable(String groupKey, Executable executable) { - if (ObjectUtil.isNull(executable)) { - return; - } - List executableList = this.executableGroup.get(groupKey); - if (CollUtil.isEmpty(executableList)) { - this.executableGroup.put(groupKey, ListUtil.toList(executable)); - } - else { - this.executableGroup.get(groupKey).add(executable); - } - } - - public abstract ConditionTypeEnum getConditionType(); - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - @Override - public String getTag() { - return tag; - } - - public void setTag(String tag) { - this.tag = tag; - } - - /** - * 请使用 {@link #setCurrChainId(String)} - */ - @Deprecated - public String getCurrChainName() { - return currChainId; - } - - public String getCurrChainId() { - return currChainId; - } - - @Override - public void setCurrChainId(String currChainId) { - this.currChainId = currChainId; - } - - public Map> getExecutableGroup() { - return executableGroup; - } +public abstract class Condition implements Executable { + + private String id; + + private String tag; + + /** + * 可执行元素的集合 + */ + private final Map> executableGroup = new HashMap<>(); + + /** + * 当前所在的ChainName 如果对于子流程来说,那这个就是子流程所在的Chain + */ + private String currChainId; + + @Override + public void execute(Integer slotIndex) throws Exception { + // 当前 Condition 入栈 + Slot slot = DataBus.getSlot(slotIndex); + try { + slot.pushCondition(this); + executeCondition(slotIndex); + } catch (ChainEndException e) { + // 这里单独catch ChainEndException是因为ChainEndException是用户自己setIsEnd抛出的异常 + // 是属于正常逻辑,所以会在FlowExecutor中判断。这里不作为异常处理 + throw e; + } catch (Exception e) { + String chainId = this.getCurrChainId(); + // 这里事先取到exception set到slot里,为了方便finally取到exception + if (slot.isSubChain(chainId)) { + slot.setSubException(chainId, e); + } else { + slot.setException(e); + } + throw e; + } finally { + // 当前 Condition 出栈 + slot.popCondition(); + } + } + + public abstract void executeCondition(Integer slotIndex) throws Exception; + + @Override + public ExecuteTypeEnum getExecuteType() { + return ExecuteTypeEnum.CONDITION; + } + + public List getExecutableList() { + return getExecutableList(ConditionKey.DEFAULT_KEY); + } + + public List getExecutableList(String groupKey) { + List executableList = this.executableGroup.get(groupKey); + if (CollUtil.isEmpty(executableList)) { + executableList = new ArrayList<>(); + } + return executableList; + } + + public Executable getExecutableOne(String groupKey) { + List list = getExecutableList(groupKey); + if (CollUtil.isEmpty(list)) { + return null; + } else { + return list.get(0); + } + } + + public void setExecutableList(List executableList) { + this.executableGroup.put(ConditionKey.DEFAULT_KEY, executableList); + } + + public void addExecutable(Executable executable) { + addExecutable(ConditionKey.DEFAULT_KEY, executable); + } + + public void addExecutable(String groupKey, Executable executable) { + if (ObjectUtil.isNull(executable)) { + return; + } + List executableList = this.executableGroup.get(groupKey); + if (CollUtil.isEmpty(executableList)) { + this.executableGroup.put(groupKey, ListUtil.toList(executable)); + } else { + this.executableGroup.get(groupKey).add(executable); + } + } + + public abstract ConditionTypeEnum getConditionType(); + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + @Override + public String getTag() { + return tag; + } + + public void setTag(String tag) { + this.tag = tag; + } + + /** + * 请使用 {@link #setCurrChainId(String)} + */ + @Deprecated + public String getCurrChainName() { + return currChainId; + } + + public String getCurrChainId() { + return currChainId; + } + + @Override + public void setCurrChainId(String currChainId) { + this.currChainId = currChainId; + } + + public Map> getExecutableGroup() { + return executableGroup; + } } diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/FallbackNodeProxy.java b/liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/FallbackNodeProxy.java new file mode 100644 index 000000000..13b1cc03d --- /dev/null +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/flow/element/FallbackNodeProxy.java @@ -0,0 +1,178 @@ +package com.yomahub.liteflow.flow.element; + +import cn.hutool.core.text.StrFormatter; +import cn.hutool.core.util.ObjectUtil; +import com.yomahub.liteflow.enums.ConditionTypeEnum; +import com.yomahub.liteflow.enums.NodeTypeEnum; +import com.yomahub.liteflow.exception.FallbackCmpNotFoundException; +import com.yomahub.liteflow.exception.FlowSystemException; +import com.yomahub.liteflow.flow.FlowBus; +import com.yomahub.liteflow.flow.element.condition.ConditionKey; +import com.yomahub.liteflow.flow.element.condition.ForCondition; +import com.yomahub.liteflow.flow.element.condition.IfCondition; +import com.yomahub.liteflow.flow.element.condition.IteratorCondition; +import com.yomahub.liteflow.flow.element.condition.LoopCondition; +import com.yomahub.liteflow.flow.element.condition.SwitchCondition; +import com.yomahub.liteflow.flow.element.condition.WhileCondition; +import com.yomahub.liteflow.slot.DataBus; +import com.yomahub.liteflow.slot.Slot; + +public class FallbackNodeProxy extends Node { + + private String originalNodeId; + + private Node fallbackNode;` + + public FallbackNodeProxy() { + } + + public FallbackNodeProxy(String originalNodeId) { + this.originalNodeId = originalNodeId; + } + + @Override + public void execute(Integer slotIndex) throws Exception { + loadFallBackNode(slotIndex); + this.fallbackNode.setCurrChainId(this.getCurrChainId()); + this.fallbackNode.execute(slotIndex); + } + + private void loadFallBackNode(Integer slotIndex) throws Exception { + if (ObjectUtil.isNotNull(this.fallbackNode)) { + // 已经加载过了 + return; + } + Slot slot = DataBus.getSlot(slotIndex); + Condition curCondition = slot.getCurrentCondition(); + if (ObjectUtil.isNotNull(curCondition)) { + throw new FlowSystemException("The current executing condition could not be found."); + } + Node node = findFallbackNode(curCondition); + if (ObjectUtil.isNull(node)) { + throw new FallbackCmpNotFoundException( + StrFormatter.format("No fallback component found for \"{}\" in {}.", + this.originalNodeId, this.getCurrChainId())); + } + // 使用 node 的副本 + this.fallbackNode = node.copy(); + } + + private Node findFallbackNode(Condition condition) { + ConditionTypeEnum conditionType = condition.getConditionType(); + switch (conditionType) { + case TYPE_THEN: + case TYPE_WHEN: + case TYPE_PRE: + case TYPE_FINALLY: + case TYPE_CATCH: + return FlowBus.getFallBackNode(NodeTypeEnum.COMMON); + case TYPE_IF: + return findNodeInIf((IfCondition) condition); + case TYPE_SWITCH: + return findNodeInSwitch((SwitchCondition) condition); + case TYPE_FOR: + return findNodeInFor((ForCondition) condition); + case TYPE_WHILE: + return findNodeInWhile((WhileCondition) condition); + case TYPE_ITERATOR: + return findNodeInIterator((IteratorCondition) condition); + case TYPE_NOT_OPT: + case TYPE_AND_OR_OPT: + return FlowBus.getFallBackNode(NodeTypeEnum.IF); + default: + return null; + } + } + + private Node findNodeInIf(IfCondition ifCondition) { + Executable ifItem = ifCondition.getIfItem(); + if (ifItem == this) { + // 需要条件组件 + return FlowBus.getFallBackNode(NodeTypeEnum.IF); + } + + // 需要普通组件 + return FlowBus.getFallBackNode(NodeTypeEnum.COMMON); + } + + private Node findNodeInSwitch(SwitchCondition switchCondition) { + Node switchNode = switchCondition.getSwitchNode(); + if (switchNode == this) { + return FlowBus.getFallBackNode(NodeTypeEnum.SWITCH); + } + + return FlowBus.getFallBackNode(NodeTypeEnum.COMMON); + } + + private Node findNodeInFor(ForCondition forCondition) { + Node forNode = forCondition.getForNode(); + if (forNode == this) { + return FlowBus.getFallBackNode(NodeTypeEnum.FOR); + } + + return findNodeInLoop(forCondition); + } + + private Node findNodeInWhile(WhileCondition whileCondition) { + Executable whileItem = whileCondition.getWhileItem(); + if (whileItem == this) { + return FlowBus.getFallBackNode(NodeTypeEnum.WHILE); + } + + Executable breakItem = whileCondition.getExecutableOne(ConditionKey.BREAK_KEY); + if (breakItem == this) { + return FlowBus.getFallBackNode(NodeTypeEnum.BREAK); + } + + return findNodeInLoop(whileCondition); + } + + private Node findNodeInLoop(LoopCondition loopCondition) { + Executable breakItem = loopCondition.getExecutableOne(ConditionKey.BREAK_KEY); + if (breakItem == this) { + return FlowBus.getFallBackNode(NodeTypeEnum.BREAK); + } + + return FlowBus.getFallBackNode(NodeTypeEnum.COMMON); + } + + private Node findNodeInIterator(IteratorCondition iteratorCondition) { + Node iteratorNode = iteratorCondition.getIteratorNode(); + if (iteratorNode == this) { + return FlowBus.getFallBackNode(NodeTypeEnum.ITERATOR); + } + + return FlowBus.getFallBackNode(NodeTypeEnum.COMMON); + } + + @Override + public T getItemResultMetaValue(Integer slotIndex) { + return this.fallbackNode.getItemResultMetaValue(slotIndex); + } + + @Override + public boolean isAccess(Integer slotIndex) throws Exception { + // WHEN 可能会先访问这个方法,所以在这里就要加载降级节点 + loadFallBackNode(slotIndex); + return this.fallbackNode.isAccess(slotIndex); + } + + @Override + public String getId() { + return this.fallbackNode.getId(); + } + + @Override + public Node copy() { + // 代理节点不复制 + return this; + } + + public String getOriginalNodeId() { + return originalNodeId; + } + + public void setOriginalNodeId(String originalNodeId) { + this.originalNodeId = originalNodeId; + } +} diff --git a/liteflow-core/src/main/java/com/yomahub/liteflow/slot/Slot.java b/liteflow-core/src/main/java/com/yomahub/liteflow/slot/Slot.java index 3a2516266..8b3275187 100644 --- a/liteflow-core/src/main/java/com/yomahub/liteflow/slot/Slot.java +++ b/liteflow-core/src/main/java/com/yomahub/liteflow/slot/Slot.java @@ -13,6 +13,7 @@ import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.yomahub.liteflow.exception.NoSuchContextBeanException; import com.yomahub.liteflow.exception.NullParamException; +import com.yomahub.liteflow.flow.element.Condition; import com.yomahub.liteflow.flow.entity.CmpStep; import com.yomahub.liteflow.flow.id.IdGeneratorHolder; import com.yomahub.liteflow.log.LFLog; @@ -26,7 +27,6 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.function.Consumer; /** * Slot的抽象类实现 @@ -88,6 +88,8 @@ public class Slot { protected ConcurrentHashMap metaDataMap = new ConcurrentHashMap<>(); private List contextBeanList; + + private final Deque conditionStack = new ConcurrentLinkedDeque<>(); public Slot() { } @@ -287,6 +289,18 @@ public class Slot { public Iterator getIteratorResult(String key) { return getThreadMetaData(ITERATOR_PREFIX + key); } + + public Condition getCurrentCondition() { + return this.conditionStack.peek(); + } + + public void pushCondition(Condition condition) { + this.conditionStack.push(condition); + } + + public void popCondition() { + this.conditionStack.pop(); + } /** * @deprecated 请使用 {@link #setChainId(String)} diff --git a/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/FallbackSpringbootTest.java b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/FallbackSpringbootTest.java new file mode 100644 index 000000000..c1c4ecdcb --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/FallbackSpringbootTest.java @@ -0,0 +1,30 @@ +package com.yomahub.liteflow.test.fallback; + +import com.yomahub.liteflow.core.FlowExecutor; +import com.yomahub.liteflow.test.execute2Future.Executor2FutureELSpringbootTest; +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; + +@TestPropertySource(value = "classpath:/fallback/application.properties") +@SpringBootTest(classes = FallbackSpringbootTest.class) +@EnableAutoConfiguration +@ComponentScan({ "com.yomahub.liteflow.test.fallback.cmp" }) +public class FallbackSpringbootTest { + @Resource + private FlowExecutor flowExecutor; + + @Test + public void test1() { + flowExecutor.execute2Resp("chain1"); + } + + @Test + public void test2() { + flowExecutor.execute2Resp("chain2"); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/ACmp.java b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/ACmp.java new file mode 100644 index 000000000..05102a985 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/ACmp.java @@ -0,0 +1,21 @@ +/** + *

Title: liteflow

+ *

Description: 轻量级的组件式流程框架

+ * @author Bryan.Zhang + * @email weenyc31@163.com + * @Date 2020/4/1 + */ +package com.yomahub.liteflow.test.fallback.cmp; + +import com.yomahub.liteflow.annotation.LiteflowComponent; +import com.yomahub.liteflow.core.NodeComponent; + +@LiteflowComponent("a") +public class ACmp extends NodeComponent { + + @Override + public void process() { + System.out.println("ACmp executed!"); + } + +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/BCmp.java b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/BCmp.java new file mode 100644 index 000000000..c634c78fc --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/BCmp.java @@ -0,0 +1,24 @@ +/** + *

Title: liteflow

+ *

Description: 轻量级的组件式流程框架

+ * @author Bryan.Zhang + * @email weenyc31@163.com + * @Date 2020/4/1 + */ +package com.yomahub.liteflow.test.fallback.cmp; + +import com.yomahub.liteflow.annotation.LiteflowComponent; +import com.yomahub.liteflow.core.NodeComponent; +import com.yomahub.liteflow.test.customNodes.domain.DemoDomain; + +import javax.annotation.Resource; + +@LiteflowComponent("b") +public class BCmp extends NodeComponent { + + @Override + public void process() { + System.out.println("BCmp executed!"); + } + +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/CCmp.java b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/CCmp.java new file mode 100644 index 000000000..3ec079168 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/java/com/yomahub/liteflow/test/fallback/cmp/CCmp.java @@ -0,0 +1,23 @@ +/** + *

Title: liteflow

+ *

Description: 轻量级的组件式流程框架

+ * @author Bryan.Zhang + * @email weenyc31@163.com + * @Date 2020/4/1 + */ +package com.yomahub.liteflow.test.fallback.cmp; + +import com.yomahub.liteflow.annotation.FallbackCmp; +import com.yomahub.liteflow.core.NodeComponent; +import org.springframework.stereotype.Component; + +@Component("c") +@FallbackCmp +public class CCmp extends NodeComponent { + + @Override + public void process() { + System.out.println("CCmp executed!"); + } + +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/fallback/application.properties b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/fallback/application.properties new file mode 100644 index 000000000..6e04fd049 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/fallback/application.properties @@ -0,0 +1 @@ +liteflow.rule-source=fallback/flow.el.xml \ No newline at end of file diff --git a/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/fallback/flow.el.xml b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/fallback/flow.el.xml new file mode 100644 index 000000000..b4334e016 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-springboot/src/test/resources/fallback/flow.el.xml @@ -0,0 +1,11 @@ + + + + THEN(a, node("d")); + + + + THEN(a, WHEN(b,node("d"))); + + + \ No newline at end of file