# Conflicts:
#	liteflow-core/src/main/java/com/yomahub/liteflow/parser/helper/ParserHelper.java
This commit is contained in:
ousinka 2022-11-04 14:29:43 +08:00
commit 723ea21db0
22 changed files with 686 additions and 253 deletions

View File

@ -16,170 +16,171 @@ import java.util.function.Predicate;
/**
* 节点类型枚举
*
* @author Bryan.Zhang
* @since 2.6.0
*/
public enum NodeTypeEnum {
COMMON("common","普通", false, NodeComponent.class),
COMMON("common", "普通", false, NodeComponent.class),
SWITCH("switch", "选择", false, NodeSwitchComponent.class),
SWITCH("switch", "选择", false, NodeSwitchComponent.class),
IF("if", "条件", false, NodeIfComponent.class),
IF("if", "条件", false, NodeIfComponent.class),
FOR("for","循环次数", false, NodeForComponent.class),
FOR("for", "循环次数", false, NodeForComponent.class),
WHILE("while", "循环条件", false, NodeWhileComponent.class),
WHILE("while", "循环条件", false, NodeWhileComponent.class),
BREAK("break", "循环跳出", false, NodeBreakComponent.class),
SCRIPT("script","脚本", true, ScriptCommonComponent.class),
BREAK("break", "循环跳出", false, NodeBreakComponent.class),
SWITCH_SCRIPT("switch_script", "选择脚本", true, ScriptSwitchComponent.class),
SCRIPT("script", "脚本", true, ScriptCommonComponent.class),
IF_SCRIPT("if_script", "条件脚本", true, ScriptIfComponent.class),
SWITCH_SCRIPT("switch_script", "选择脚本", true, ScriptSwitchComponent.class),
FOR_SCRIPT("for_script", "循环次数脚本", true, ScriptForComponent.class),
IF_SCRIPT("if_script", "条件脚本", true, ScriptIfComponent.class),
WHILE_SCRIPT("while_script", "循环条件脚本", true, ScriptWhileComponent.class),
FOR_SCRIPT("for_script", "循环次数脚本", true, ScriptForComponent.class),
BREAK_SCRIPT("break_script", "循环跳出脚本", true, ScriptBreakComponent.class)
;
WHILE_SCRIPT("while_script", "循环条件脚本", true, ScriptWhileComponent.class),
private static final Logger LOG = LoggerFactory.getLogger(NodeTypeEnum.class);
BREAK_SCRIPT("break_script", "循环跳出脚本", true, ScriptBreakComponent.class);
private String code;
private String name;
private static final Logger LOG = LoggerFactory.getLogger(NodeTypeEnum.class);
private boolean isScript;
private String code;
private String name;
private Class<? extends NodeComponent> mappingClazz;
private boolean isScript;
NodeTypeEnum(String code, String name, boolean isScript, Class<? extends NodeComponent> mappingClazz) {
this.code = code;
this.name = name;
this.isScript = isScript;
this.mappingClazz = mappingClazz;
}
private Class<? extends NodeComponent> mappingClazz;
public String getCode() {
return code;
}
NodeTypeEnum(String code, String name, boolean isScript, Class<? extends NodeComponent> mappingClazz) {
this.code = code;
this.name = name;
this.isScript = isScript;
this.mappingClazz = mappingClazz;
}
public void setCode(String code) {
this.code = code;
}
public String getCode() {
return code;
}
public String getName() {
return name;
}
public void setCode(String code) {
this.code = code;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public boolean isScript() {
return isScript;
}
public void setName(String name) {
this.name = name;
}
public void setScript(boolean script) {
isScript = script;
}
public boolean isScript() {
return isScript;
}
public Class<? extends NodeComponent> getMappingClazz() {
return mappingClazz;
}
public void setScript(boolean script) {
isScript = script;
}
public void setMappingClazz(Class<? extends NodeComponent> mappingClazz) {
this.mappingClazz = mappingClazz;
}
public Class<? extends NodeComponent> getMappingClazz() {
return mappingClazz;
}
public static NodeTypeEnum getEnumByCode(String code) {
for (NodeTypeEnum e : NodeTypeEnum.values()) {
if (e.getCode().equals(code)) {
return e;
}
}
return null;
}
public void setMappingClazz(Class<? extends NodeComponent> mappingClazz) {
this.mappingClazz = mappingClazz;
}
public static NodeTypeEnum guessTypeBySuperClazz(Class<?> clazz){
Class<?> superClazz = clazz;
while(true){
superClazz = superClazz.getSuperclass();
if (superClazz.getPackage().getName().startsWith("com.yomahub.liteflow.core")){
break;
}
if(superClazz.equals(Object.class)){
return null;
}
}
public static NodeTypeEnum getEnumByCode(String code) {
for (NodeTypeEnum e : NodeTypeEnum.values()) {
if (e.getCode().equals(code)) {
return e;
}
}
return null;
}
for (NodeTypeEnum e : NodeTypeEnum.values()) {
if (e.getMappingClazz().equals(superClazz)) {
return e;
}
}
return null;
}
public static NodeTypeEnum guessTypeBySuperClazz(Class<?> clazz) {
Class<?> superClazz = clazz;
while (true) {
superClazz = superClazz.getSuperclass();
if (superClazz.getPackage().getName().startsWith("com.yomahub.liteflow.core")) {
break;
}
if (superClazz.equals(Object.class)) {
return null;
}
}
public static NodeTypeEnum guessType(Class<?> clazz){
NodeTypeEnum nodeType = guessTypeBySuperClazz(clazz);
if (nodeType == null){
//尝试从类声明处进行推断
LiteflowCmpDefine liteflowCmpDefine = clazz.getAnnotation(LiteflowCmpDefine.class);
if (liteflowCmpDefine != null){
//类声明方式中@LiteflowMethod是无需设置nodeId的
//但是如果设置了那么核心逻辑其实是取类上定义的id的
//这种可以运行但是理解起来不大好理解所以给出提示建议不要这么做
boolean mixDefined = Arrays.stream(clazz.getDeclaredMethods()).anyMatch(method -> {
LiteflowMethod liteflowMethod = AnnotationUtil.getAnnotation(method, LiteflowMethod.class);
if (liteflowMethod != null){
return StrUtil.isNotBlank(liteflowMethod.nodeId());
}else{
return false;
}
});
for (NodeTypeEnum e : NodeTypeEnum.values()) {
if (e.getMappingClazz().equals(superClazz)) {
return e;
}
}
return null;
}
if (mixDefined){
LOG.warn("[[[WARNING!!!]]]The @liteflowMethod in the class[{}] defined by @liteflowCmpDefine should not configure the nodeId again!",
clazz.getName());
}
public static NodeTypeEnum guessType(Class<?> clazz) {
NodeTypeEnum nodeType = guessTypeBySuperClazz(clazz);
if (nodeType == null) {
//尝试从类声明处进行推断
LiteflowCmpDefine liteflowCmpDefine = clazz.getAnnotation(LiteflowCmpDefine.class);
if (liteflowCmpDefine != null) {
//类声明方式中@LiteflowMethod是无需设置nodeId的
//但是如果设置了那么核心逻辑其实是取类上定义的id的
//这种可以运行但是理解起来不大好理解所以给出提示建议不要这么做
boolean mixDefined = Arrays.stream(clazz.getDeclaredMethods()).anyMatch(method -> {
LiteflowMethod liteflowMethod = AnnotationUtil.getAnnotation(method, LiteflowMethod.class);
if (liteflowMethod != null) {
return StrUtil.isNotBlank(liteflowMethod.nodeId());
} else {
return false;
}
});
if (mixDefined) {
LOG.warn("[[[WARNING!!!]]]The @liteflowMethod in the class[{}] defined by @liteflowCmpDefine should not configure the nodeId again!",
clazz.getName());
}
//在返回之前还要对方法级别的@LiteflowMethod进行检查如果存在方法上的类型与类上的不一致时给予警告信息
AtomicReference<Method> differenceTypeMethod = new AtomicReference<>();
boolean hasDifferenceNodeType = Arrays.stream(clazz.getDeclaredMethods()).anyMatch(method -> {
LiteflowMethod liteflowMethod = AnnotationUtil.getAnnotation(method, LiteflowMethod.class);
if (liteflowMethod != null){
if (!liteflowMethod.nodeType().equals(liteflowCmpDefine.value())){
differenceTypeMethod.set(method);
return true;
}else{
return false;
}
}else{
return false;
}
});
//在返回之前还要对方法级别的@LiteflowMethod进行检查如果存在方法上的类型与类上的不一致时给予警告信息
AtomicReference<Method> differenceTypeMethod = new AtomicReference<>();
boolean hasDifferenceNodeType = Arrays.stream(clazz.getDeclaredMethods()).anyMatch(method -> {
LiteflowMethod liteflowMethod = AnnotationUtil.getAnnotation(method, LiteflowMethod.class);
if (liteflowMethod != null) {
if (!liteflowMethod.nodeType().equals(liteflowCmpDefine.value())) {
differenceTypeMethod.set(method);
return true;
} else {
return false;
}
} else {
return false;
}
});
//表示存在不一样的类型
if (hasDifferenceNodeType){
LOG.warn("[[[WARNING!!!]]]The nodeType in @liteflowCmpDefine declared on the class[{}] does not match the nodeType in @liteflowMethod declared on the method[{}]!",
clazz.getName(), differenceTypeMethod.get().getName());
}
//表示存在不一样的类型
if (hasDifferenceNodeType) {
LOG.warn("[[[WARNING!!!]]]The nodeType in @liteflowCmpDefine declared on the class[{}] does not match the nodeType in @liteflowMethod declared on the method[{}]!",
clazz.getName(), differenceTypeMethod.get().getName());
}
return liteflowCmpDefine.value();
}
return liteflowCmpDefine.value();
}
//再尝试声明式组件这部分的推断
LiteflowMethod liteflowMethod = Arrays.stream(clazz.getDeclaredMethods()).map(
method -> AnnotationUtil.getAnnotation(method, LiteflowMethod.class)
).filter(Objects::nonNull).filter(lfMethod -> lfMethod.value().isMainMethod()).findFirst().orElse(null);
//再尝试声明式组件这部分的推断
LiteflowMethod liteflowMethod = Arrays.stream(clazz.getDeclaredMethods()).map(
method -> AnnotationUtil.getAnnotation(method, LiteflowMethod.class)
).filter(Objects::nonNull).filter(lfMethod -> lfMethod.value().isMainMethod()).findFirst().orElse(null);
if (liteflowMethod != null) {
nodeType = liteflowMethod.nodeType();
}
}
return nodeType;
}
if (liteflowMethod != null) {
nodeType = liteflowMethod.nodeType();
}
}
return nodeType;
}
}

View File

@ -35,7 +35,8 @@ public abstract class BaseJsonFlowParser implements FlowParser {
JsonNode flowJsonNode = JsonUtil.parseObject(content);
jsonObjectList.add(flowJsonNode);
}
ParserHelper.parseJsonNode(jsonObjectList, CHAIN_NAME_SET, this::parseOneChain);
ParserHelper.parseNodeJson(jsonObjectList);
ParserHelper.parseChainJson(jsonObjectList, CHAIN_NAME_SET, this::parseOneChain);
}
/**

View File

@ -36,8 +36,8 @@ public abstract class BaseXmlFlowParser implements FlowParser {
documentList.add(document);
}
Consumer<Element> parseOneChainConsumer = this::parseOneChain;
ParserHelper.parseDocument(documentList, CHAIN_NAME_SET, parseOneChainConsumer);
ParserHelper.parseNodeDocument(documentList);
ParserHelper.parseChainDocument(documentList, CHAIN_NAME_SET, this::parseOneChain);
}
/**

View File

@ -39,7 +39,8 @@ public abstract class BaseYmlFlowParser implements FlowParser {
}
Consumer<JsonNode> parseOneChainConsumer = this::parseOneChain;
ParserHelper.parseJsonNode(jsonObjectList, CHAIN_NAME_SET,parseOneChainConsumer);
ParserHelper.parseNodeJson(jsonObjectList);
ParserHelper.parseChainJson(jsonObjectList, CHAIN_NAME_SET, parseOneChainConsumer);
}
protected JsonNode convertToJson(String yamlString) {

View File

@ -1,11 +1,9 @@
package com.yomahub.liteflow.parser.helper;
import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.yomahub.liteflow.annotation.*;
import com.yomahub.liteflow.builder.LiteFlowNodeBuilder;
import com.yomahub.liteflow.builder.el.LiteFlowChainELBuilder;
import com.yomahub.liteflow.builder.prop.NodePropBean;
@ -87,35 +85,13 @@ public class ParserHelper {
/**
* xml 形式的主要解析过程
*
* @param documentList documentList
* @param chainNameSet 用于去重
* @param parseOneChainConsumer parseOneChain 函数
*/
public static void parseDocument(List<Document> documentList, Set<String> chainNameSet, Consumer<Element> parseOneChainConsumer) {
//先在元数据里放上chain
//先放有一个好处可以在parse的时候先映射到FlowBus的chainMap然后再去解析
//这样就不用去像之前的版本那样回归调用
//同时也解决了不能循环依赖的问题
documentList.forEach(document -> {
// 解析chain节点
List<Element> chainList = document.getRootElement().elements(CHAIN);
//先在元数据里放上chain
chainList.forEach(e -> {
//校验加载的 chainName 是否有重复的
//TODO 这里是否有个问题当混合格式加载的时候2个同名的Chain在不同的文件里就不行了
String chainName = Optional.ofNullable(e.attributeValue(ID)).orElse(e.attributeValue(NAME));
if (!chainNameSet.add(chainName)) {
throw new ChainDuplicateException(String.format("[chain name duplicate] chainName=%s", chainName));
}
FlowBus.addChain(chainName);
});
});
// 清空
chainNameSet.clear();
/**
* xml 形式的主要解析过程
* @param documentList documentList
*/
public static void parseNodeDocument(List<Document> documentList) {
for (Document document : documentList) {
Element rootElement = document.getRootElement();
Element nodesElement = rootElement.element(NODES);
@ -143,38 +119,67 @@ public class ParserHelper {
ParserHelper.buildNode(nodePropBean);
}
}
//解析每一个chain
List<Element> chainList = rootElement.elements(CHAIN);
chainList.forEach(parseOneChainConsumer);
}
}
public static void parseJsonNode(List<JsonNode> flowJsonObjectList, Set<String> chainNameSet, Consumer<JsonNode> parseOneChainConsumer) {
public static void parseDocument(List<Document> documentList, Set<String> chainNameSet, Consumer<Element> parseOneChainConsumer) {
//先在元数据里放上chain
//先放有一个好处可以在parse的时候先映射到FlowBus的chainMap然后再去解析
//这样就不用去像之前的版本那样回归调用
//同时也解决了不能循环依赖的问题
flowJsonObjectList.forEach(jsonObject -> {
documentList.forEach(document -> {
// 解析chain节点
Iterator<JsonNode> iterator = jsonObject.get(FLOW).get(CHAIN).elements();
List<Element> chainList = document.getRootElement().elements(CHAIN);
//先在元数据里放上chain
while (iterator.hasNext()) {
JsonNode innerJsonObject = iterator.next();
chainList.forEach(e -> {
//校验加载的 chainName 是否有重复的
// TODO 这里是否有个问题当混合格式加载的时候2个同名的Chain在不同的文件里就不行了
//String chainName = innerJsonObject.get(NAME).textValue();
String chainName = Optional.ofNullable(innerJsonObject.get(ID)).orElse(innerJsonObject.get(NAME)).textValue();
//TODO 这里是否有个问题当混合格式加载的时候2个同名的Chain在不同的文件里就不行了
String chainName = Optional.ofNullable(e.attributeValue(ID)).orElse(e.attributeValue(NAME));
if (!chainNameSet.add(chainName)) {
throw new ChainDuplicateException(String.format("[chain name duplicate] chainName=%s", chainName));
}
FlowBus.addChain(chainName);
}
});
});
// 清空
chainNameSet.clear();
}
public static void parseChainDocument(List<Document> documentList, Set<String> chainNameSet, Consumer<Element> parseOneChainConsumer){
//先在元数据里放上chain
//先放有一个好处可以在parse的时候先映射到FlowBus的chainMap然后再去解析
//这样就不用去像之前的版本那样回归调用
//同时也解决了不能循环依赖的问题
documentList.forEach(document -> {
// 解析chain节点
List<Element> chainList = document.getRootElement().elements(CHAIN);
//先在元数据里放上chain
chainList.forEach(e -> {
//校验加载的 chainName 是否有重复的
//TODO 这里是否有个问题当混合格式加载的时候2个同名的Chain在不同的文件里就不行了
String chainName = Optional.ofNullable(e.attributeValue(ID)).orElse(e.attributeValue(NAME));
if (!chainNameSet.add(chainName)) {
throw new ChainDuplicateException(String.format("[chain name duplicate] chainName=%s", chainName));
}
FlowBus.addChain(chainName);
});
});
// 清空
chainNameSet.clear();
//解析每一个chain
for (Document document : documentList) {
Element rootElement = document.getRootElement();
List<Element> chainList = rootElement.elements(CHAIN);
chainList.forEach(parseOneChainConsumer);
}
}
public static void parseNodeJson(List<JsonNode> flowJsonObjectList) {
for (JsonNode flowJsonNode : flowJsonObjectList) {
// 当存在<nodes>节点定义时解析node节点
if (flowJsonNode.get(FLOW).has(NODES)) {
@ -201,7 +206,61 @@ public class ParserHelper {
ParserHelper.buildNode(nodePropBean);
}
}
}
}
public static void parseJsonNode(List<JsonNode> flowJsonObjectList, Set<String> chainNameSet, Consumer<JsonNode> parseOneChainConsumer) {
//先在元数据里放上chain
//先放有一个好处可以在parse的时候先映射到FlowBus的chainMap然后再去解析
//这样就不用去像之前的版本那样回归调用
//同时也解决了不能循环依赖的问题
flowJsonObjectList.forEach(jsonObject -> {
// 解析chain节点
Iterator<JsonNode> iterator = jsonObject.get(FLOW).get(CHAIN).elements();
//先在元数据里放上chain
while (iterator.hasNext()) {
JsonNode innerJsonObject = iterator.next();
//校验加载的 chainName 是否有重复的
// TODO 这里是否有个问题当混合格式加载的时候2个同名的Chain在不同的文件里就不行了
//String chainName = innerJsonObject.get(NAME).textValue();
String chainName = Optional.ofNullable(innerJsonObject.get(ID)).orElse(innerJsonObject.get(NAME)).textValue();
if (!chainNameSet.add(chainName)) {
throw new ChainDuplicateException(String.format("[chain name duplicate] chainName=%s", chainName));
}
FlowBus.addChain(chainName);
}
});
// 清空
chainNameSet.clear();
}
public static void parseChainJson(List<JsonNode> flowJsonObjectList, Set<String> chainNameSet, Consumer<JsonNode> parseOneChainConsumer){
//先在元数据里放上chain
//先放有一个好处可以在parse的时候先映射到FlowBus的chainMap然后再去解析
//这样就不用去像之前的版本那样回归调用
//同时也解决了不能循环依赖的问题
flowJsonObjectList.forEach(jsonObject -> {
// 解析chain节点
Iterator<JsonNode> iterator = jsonObject.get(FLOW).get(CHAIN).elements();
//先在元数据里放上chain
while (iterator.hasNext()) {
JsonNode innerJsonObject = iterator.next();
//校验加载的 chainName 是否有重复的
// TODO 这里是否有个问题当混合格式加载的时候2个同名的Chain在不同的文件里就不行了
String chainName = Optional.ofNullable(innerJsonObject.get(ID)).orElse(innerJsonObject.get(NAME)).textValue();
if (!chainNameSet.add(chainName)) {
throw new ChainDuplicateException(String.format("[chain name duplicate] chainName=%s", chainName));
}
FlowBus.addChain(chainName);
}
});
// 清空
chainNameSet.clear();
for (JsonNode flowJsonNode : flowJsonObjectList) {
//解析每一个chain
Iterator<JsonNode> chainIterator = flowJsonNode.get(FLOW).get(CHAIN).elements();
while (chainIterator.hasNext()) {
@ -218,9 +277,9 @@ public class ParserHelper {
*/
public static void parseOneChainEl(JsonNode chainNode) {
//构建chainBuilder
String chainName = Optional.ofNullable(chainNode.get(ID)).orElse(chainNode.get(NAME)).textValue();
String chainId = Optional.ofNullable(chainNode.get(ID)).orElse(chainNode.get(NAME)).textValue();
String el = chainNode.get(VALUE).textValue();
LiteFlowChainELBuilder chainELBuilder = LiteFlowChainELBuilder.createChain().setChainName(chainName);
LiteFlowChainELBuilder chainELBuilder = LiteFlowChainELBuilder.createChain().setChainId(chainId);
chainELBuilder.setEL(el).build();
}
@ -231,10 +290,10 @@ public class ParserHelper {
*/
public static void parseOneChainEl(Element e) {
//构建chainBuilder
String chainName = Optional.ofNullable(e.attributeValue(ID)).orElse(e.attributeValue(NAME));
String chainId = Optional.ofNullable(e.attributeValue(ID)).orElse(e.attributeValue(NAME));
String text = e.getText();
String el = RegexUtil.removeComments(text);
LiteFlowChainELBuilder chainELBuilder = LiteFlowChainELBuilder.createChain().setChainName(chainName);
LiteFlowChainELBuilder chainELBuilder = LiteFlowChainELBuilder.createChain().setChainId(chainId);
chainELBuilder.setEL(el).build();
}

View File

@ -12,4 +12,4 @@
language (groovy|js) #IMPLIED
>
<!ATTLIST chain
name CDATA #REQUIRED>
name CDATA>

View File

@ -30,7 +30,9 @@ public class EtcdParserHelper {
this.etcdParserVO = etcdParserVO;
try{
this.etcdClient = ContextAwareHolder.loadContextAware().getBean(EtcdClient.class);
try{
this.etcdClient = ContextAwareHolder.loadContextAware().getBean(EtcdClient.class);
}catch (Exception ignored){}
if (this.etcdClient == null) {
Client client = Client.builder()
.endpoints(etcdParserVO.getConnectStr().split(","))

View File

@ -32,7 +32,9 @@ public class NacosParserHelper {
public NacosParserHelper(NacosParserVO nacosParserVO) {
this.nacosParserVO = nacosParserVO;
try{
this.configService = ContextAwareHolder.loadContextAware().getBean(NacosConfigService.class);
try{
this.configService = ContextAwareHolder.loadContextAware().getBean(NacosConfigService.class);
}catch (Exception ignored){}
if (this.configService == null){
Properties properties = new Properties();
properties.put(PropertyKeyConst.SERVER_ADDR, nacosParserVO.getServerAddr());

View File

@ -13,7 +13,6 @@ import com.yomahub.liteflow.property.LiteflowConfig;
import com.yomahub.liteflow.property.LiteflowConfigGetter;
import com.yomahub.liteflow.util.JsonUtil;
import java.util.Map;
import java.util.Objects;
/**
@ -35,9 +34,9 @@ public class SQLXmlELParser extends ClassXmlFlowELParser {
try {
SQLParserVO sqlParserVO = null;
if(MapUtil.isNotEmpty((liteflowConfig.getRuleSourceExtDataMap()))){
if (MapUtil.isNotEmpty((liteflowConfig.getRuleSourceExtDataMap()))) {
sqlParserVO = BeanUtil.toBean(liteflowConfig.getRuleSourceExtDataMap(), SQLParserVO.class, CopyOptions.create());
}else if (StrUtil.isNotBlank(liteflowConfig.getRuleSourceExtData())){
} else if (StrUtil.isNotBlank(liteflowConfig.getRuleSourceExtData())) {
sqlParserVO = JsonUtil.parseObject(liteflowConfig.getRuleSourceExtData(), SQLParserVO.class);
}
if (Objects.isNull(sqlParserVO)) {
@ -49,7 +48,6 @@ public class SQLXmlELParser extends ClassXmlFlowELParser {
// 初始化 JDBCHelper
JDBCHelper.init(sqlParserVO);
} catch (ELSQLException elsqlException) {
throw elsqlException;
} catch (Exception ex) {
@ -60,10 +58,9 @@ public class SQLXmlELParser extends ClassXmlFlowELParser {
@Override
public String parseCustom() {
return JDBCHelper.getInstance().getElDataContent();
return JDBCHelper.getInstance().getContent();
}
/**
* 检查配置文件并设置默认值
*

View File

@ -1,11 +1,10 @@
package com.yomahub.liteflow.parser.sql.util;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.StrFormatter;
import cn.hutool.core.util.StrUtil;
import com.yomahub.liteflow.enums.NodeTypeEnum;
import com.yomahub.liteflow.parser.sql.exception.ELSQLException;
import com.yomahub.liteflow.parser.sql.vo.SQLParserVO;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
@ -13,6 +12,7 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* jdbc 工具类
@ -22,10 +22,15 @@ import java.util.List;
*/
public class JDBCHelper {
private static final String SQL_PATTERN = "SELECT {},{} FROM {} ";
private static final String SQL_PATTERN = "SELECT {},{} FROM {} WHERE {}=?";
private static final String SCRIPT_SQL_CHECK_PATTERN = "SELECT 1 FROM {} WHERE {}=?";
private static final String SCRIPT_SQL_PATTERN = "SELECT {},{},{},{} FROM {} WHERE {}=?";
private static final String CHAIN_XML_PATTERN = "<chain name=\"{}\">{}</chain>";
private static final String XML_PATTERN = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><flow>{}</flow>";
private static final String NODE_XML_PATTERN = "<nodes><node id=\"{}\" name=\"{}\" type=\"{}\"><![CDATA[{}]]></node></nodes>";
private static final String XML_PATTERN = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><flow>{}{}</flow>";
private static final Integer FETCH_SIZE_MAX = 1000;
private SQLParserVO sqlParserVO;
@ -56,7 +61,7 @@ public class JDBCHelper {
* 获取链接
*/
public Connection getConn() {
Connection connection = null;
Connection connection;
try {
connection = DriverManager.getConnection(sqlParserVO.getUrl(), sqlParserVO.getUsername(), sqlParserVO.getPassword());
} catch (SQLException e) {
@ -68,15 +73,26 @@ public class JDBCHelper {
/**
* 获取 ElData 数据内容
*/
public String getElDataContent() {
public String getContent() {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
String chainTableName = sqlParserVO.getChainTableName();
String elDataField = sqlParserVO.getElDataField();
String chainNameField = sqlParserVO.getChainNameField();
String tableName = sqlParserVO.getTableName();
String sqlCmd = StrFormatter.format(SQL_PATTERN, chainNameField, elDataField, tableName);
String chainApplicationNameField = sqlParserVO.getChainApplicationNameField();
String applicationName = sqlParserVO.getApplicationName();
if(StrUtil.isBlank(chainTableName)){
throw new ELSQLException("You did not define the chainTableName property");
}
if(StrUtil.isBlank(applicationName) || StrUtil.isBlank(chainApplicationNameField)){
throw new ELSQLException("You did not define the applicationName or chainApplicationNameField property");
}
String sqlCmd = StrUtil.format(SQL_PATTERN, chainNameField, elDataField, chainTableName, chainApplicationNameField);
List<String> result = new ArrayList<>();
try {
@ -84,19 +100,14 @@ public class JDBCHelper {
stmt = conn.prepareStatement(sqlCmd, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
// 设置游标拉取数量
stmt.setFetchSize(FETCH_SIZE_MAX);
stmt.setString(1, applicationName);
rs = stmt.executeQuery();
while (rs.next()) {
String elData = rs.getString(elDataField);
if (StrUtil.isBlank(elData)) {
throw new ELSQLException(StrFormatter.format("{} table exist {} field value is empty", tableName, elDataField));
}
String chainName = rs.getString(chainNameField);
if (StrUtil.isBlank(elData)) {
throw new ELSQLException(StrFormatter.format("{} table exist {} field value is empty", tableName, elDataField));
}
String elData = getStringFromResultSet(rs, elDataField);
String chainName = getStringFromResultSet(rs, chainNameField);
result.add(StrFormatter.format(CHAIN_XML_PATTERN, chainName, elData));
result.add(StrUtil.format(CHAIN_XML_PATTERN, chainName, elData));
}
} catch (Exception e) {
throw new ELSQLException(e.getMessage());
@ -105,8 +116,77 @@ public class JDBCHelper {
close(conn, stmt, rs);
}
String chains = CollUtil.join(result, StrUtil.CRLF);
return StrFormatter.format(XML_PATTERN, chains);
String chainsContent = CollUtil.join(result, StrUtil.EMPTY);
String nodesContent;
if (hasScriptData()){
nodesContent = getScriptNodes();
}else{
nodesContent = StrUtil.EMPTY;
}
return StrUtil.format(XML_PATTERN, nodesContent, chainsContent);
}
public String getScriptNodes() {
List<String> result = new ArrayList<>();
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
String scriptTableName = sqlParserVO.getScriptTableName();
String scriptIdField = sqlParserVO.getScriptIdField();
String scriptDataField = sqlParserVO.getScriptDataField();
String scriptNameField = sqlParserVO.getScriptNameField();
String scriptTypeField = sqlParserVO.getScriptTypeField();
String scriptApplicationNameField = sqlParserVO.getScriptApplicationNameField();
String applicationName = sqlParserVO.getApplicationName();
if(StrUtil.isBlank(applicationName) || StrUtil.isBlank(scriptApplicationNameField)){
throw new ELSQLException("You did not define the applicationName or scriptApplicationNameField property");
}
String sqlCmd = StrUtil.format(
SCRIPT_SQL_PATTERN,
scriptIdField,
scriptDataField,
scriptNameField,
scriptTypeField,
scriptTableName,
scriptApplicationNameField
);
try {
conn = getConn();
stmt = conn.prepareStatement(sqlCmd, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
// 设置游标拉取数量
stmt.setFetchSize(FETCH_SIZE_MAX);
stmt.setString(1, applicationName);
rs = stmt.executeQuery();
while (rs.next()) {
String id = getStringFromResultSet(rs, scriptIdField);
String data = getStringFromResultSet(rs, scriptDataField);
String name = getStringFromResultSet(rs, scriptNameField);
String type = getStringFromResultSet(rs, scriptTypeField);
NodeTypeEnum nodeTypeEnum = NodeTypeEnum.getEnumByCode(type);
if (Objects.isNull(nodeTypeEnum)){
throw new ELSQLException(StrUtil.format("Invalid type value[{}]", type));
}
if (!nodeTypeEnum.isScript()) {
throw new ELSQLException(StrUtil.format("The type value[{}] is not a script type", type));
}
result.add(StrUtil.format(NODE_XML_PATTERN, id, name, type, data));
}
} catch (Exception e) {
throw new ELSQLException(e.getMessage());
} finally {
// 关闭连接
close(conn, stmt, rs);
}
return CollUtil.join(result, StrUtil.EMPTY);
}
/**
@ -143,8 +223,42 @@ public class JDBCHelper {
}
}
private boolean hasScriptData(){
if (StrUtil.isBlank(sqlParserVO.getScriptTableName())){
return false;
}
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
String sqlCmd = StrUtil.format(SCRIPT_SQL_CHECK_PATTERN,
sqlParserVO.getScriptTableName(),
sqlParserVO.getScriptApplicationNameField());
try {
conn = getConn();
stmt = conn.prepareStatement(sqlCmd, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
stmt.setFetchSize(1);
stmt.setString(1, sqlParserVO.getApplicationName());
rs = stmt.executeQuery();
return rs.next();
} catch (Exception e) {
return false;
} finally {
// 关闭连接
close(conn, stmt, rs);
}
}
//#region get set method
private String getStringFromResultSet(ResultSet rs, String field) throws SQLException {
String data = rs.getString(field);
if (StrUtil.isBlank(data)) {
throw new ELSQLException(StrUtil.format("exist {} field value is empty", field));
}
return data;
}
private SQLParserVO getSqlParserVO() {
return sqlParserVO;
}
@ -152,5 +266,4 @@ public class JDBCHelper {
private void setSqlParserVO(SQLParserVO sqlParserVO) {
this.sqlParserVO = sqlParserVO;
}
//#endregion
}

View File

@ -29,9 +29,19 @@ public class SQLParserVO {
private String password;
/**
*
* 应用
*/
private String tableName = "el_table";
private String applicationName;
/**
* chain表名
*/
private String chainTableName;
/**
* chain表里的应用名字段
*/
private String chainApplicationNameField = "application_name";
/**
* chainName
@ -43,6 +53,36 @@ public class SQLParserVO {
*/
private String elDataField = "el_data";
/**
* 脚本 node 表名
*/
private String scriptTableName;
/**
* script表里的应用名字段
*/
private String scriptApplicationNameField = "application_name";
/**
* 脚本 node id 字段
*/
private String scriptIdField = "script_id";
/**
* 脚本 node name 字段
*/
private String scriptNameField = "script_name";
/**
* 脚本 node data 字段
*/
private String scriptDataField = "script_data";
/**
* 脚本 node type 字段
*/
private String scriptTypeField = "script_type";
public String getUrl() {
return url;
}
@ -75,12 +115,28 @@ public class SQLParserVO {
this.password = password;
}
public String getTableName() {
return tableName;
public String getApplicationName() {
return applicationName;
}
public void setTableName(String tableName) {
this.tableName = tableName;
public void setApplicationName(String applicationName) {
this.applicationName = applicationName;
}
public String getChainTableName() {
return chainTableName;
}
public void setChainTableName(String chainTableName) {
this.chainTableName = chainTableName;
}
public String getChainApplicationNameField() {
return chainApplicationNameField;
}
public void setChainApplicationNameField(String chainApplicationNameField) {
this.chainApplicationNameField = chainApplicationNameField;
}
public String getChainNameField() {
@ -98,4 +154,52 @@ public class SQLParserVO {
public void setElDataField(String elDataField) {
this.elDataField = elDataField;
}
public String getScriptTableName() {
return scriptTableName;
}
public void setScriptTableName(String scriptTableName) {
this.scriptTableName = scriptTableName;
}
public String getScriptApplicationNameField() {
return scriptApplicationNameField;
}
public void setScriptApplicationNameField(String scriptApplicationNameField) {
this.scriptApplicationNameField = scriptApplicationNameField;
}
public String getScriptIdField() {
return scriptIdField;
}
public void setScriptIdField(String scriptIdField) {
this.scriptIdField = scriptIdField;
}
public String getScriptNameField() {
return scriptNameField;
}
public void setScriptNameField(String scriptNameField) {
this.scriptNameField = scriptNameField;
}
public String getScriptDataField() {
return scriptDataField;
}
public void setScriptDataField(String scriptDataField) {
this.scriptDataField = scriptDataField;
}
public String getScriptTypeField() {
return scriptTypeField;
}
public void setScriptTypeField(String scriptTypeField) {
this.scriptTypeField = scriptTypeField;
}
}

View File

@ -0,0 +1,42 @@
package com.yomahub.liteflow.test.script.groovy.scriptOrder;
import cn.hutool.core.io.resource.ResourceUtil;
import com.yomahub.liteflow.core.FlowExecutor;
import com.yomahub.liteflow.enums.FlowParserTypeEnum;
import com.yomahub.liteflow.flow.FlowBus;
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;
/**
* 测试springboot下的groovy脚本组件基于xml配置
* @author Bryan.Zhang
* @since 2.6.0
*/
@RunWith(SpringRunner.class)
@TestPropertySource(value = "classpath:/scriptOrder/application.properties")
@SpringBootTest(classes = LiteflowScriptOrderGroovyELTest.class)
@EnableAutoConfiguration
public class LiteflowScriptOrderGroovyELTest extends BaseTest {
@Resource
private FlowExecutor flowExecutor;
//测试普通脚本节点
@Test
public void testScript1() {
LiteflowResponse response = flowExecutor.execute2Resp("chain1", "arg");
Assert.assertTrue(response.isSuccess());
}
}

View File

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

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE flow PUBLIC "liteflow" "liteflow.dtd">
<flow>
<nodes>
<node id="s1" language="groovy" type="if_script">
<![CDATA[
System.out.println("Groovy 脚本1 被调用。")
return false
]]>
</node>
<node id="s2" language="groovy" type="script">
<![CDATA[
System.out.println("Groovy 脚本2 被调用。")
]]>
</node>
<node id="s3" language="groovy" type="script">
<![CDATA[
System.out.println("Groovy 脚本3 被调用。")
]]>
</node>
</nodes>
<chain name="chain1">
// IF(s1, s2, s3);
IF(s1, s2, s4);
</chain>
</flow>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE flow PUBLIC "liteflow" "liteflow.dtd">
<flow>
<nodes>
<node id="s4" language="groovy" type="script">
<![CDATA[
System.out.println("Groovy 脚本4 被调用。")
]]>
</node>
</nodes>
</flow>

View File

@ -27,11 +27,11 @@
<node id="s2" name="选择脚本" type="switch_script" language="groovy">
<![CDATA[
count = defaultContext.getData("count");
count = defaultContext.getData("count")
if(count > 100){
return "a";
return "a"
}else{
return "b";
return "b"
}
]]>
</node>

View File

@ -49,5 +49,13 @@
<version>${h2.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.yomahub</groupId>
<artifactId>liteflow-script-groovy</artifactId>
<version>${revision}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -33,35 +33,64 @@ import java.sql.Statement;
@EnableAutoConfiguration
@ComponentScan({"com.yomahub.liteflow.test.sql.cmp"})
public class SQLWithXmlELSpringbootTest extends BaseTest {
@Resource
private FlowExecutor flowExecutor;
@Resource
private FlowExecutor flowExecutor;
@Test
public void testSQLWithXml() {
LiteflowResponse response = flowExecutor.execute2Resp("chain1", "arg");
Assert.assertEquals("a==>b==>c", response.getExecuteStepStr());
@Test
public void testSQLWithXml() {
LiteflowResponse response = flowExecutor.execute2Resp("chain1", "arg");
Assert.assertEquals("a==>b==>c", response.getExecuteStepStr());
// 修改数据库
changeData();
// 修改数据库
changeData();
// 重新加载规则
flowExecutor.reloadRule();
Assert.assertEquals("a==>c==>b", flowExecutor.execute2Resp("chain1", "arg").getExecuteStepStr());
}
// 重新加载规则
flowExecutor.reloadRule();
Assert.assertEquals("a==>c==>b", flowExecutor.execute2Resp("chain1", "arg").getExecuteStepStr());
}
/**
* 修改数据库数据
*/
private void changeData() {
LiteflowConfig liteflowConfig = LiteflowConfigGetter.get();
SQLParserVO sqlParserVO = JsonUtil.parseObject(liteflowConfig.getRuleSourceExtData(), SQLParserVO.class);
Connection connection = null;
try {
connection = DriverManager.getConnection(sqlParserVO.getUrl(), sqlParserVO.getUsername(), sqlParserVO.getPassword());
Statement statement = connection.createStatement();
statement.executeUpdate("UPDATE EL_TABLE SET EL_DATA='THEN(a, c, b);' WHERE chain_name='chain1'");
} catch (SQLException e) {
throw new ELSQLException(e.getMessage());
}
}
@Test
public void testSQLWithScriptXml() {
LiteflowResponse response = flowExecutor.execute2Resp("chain3", "arg");
Assert.assertTrue(response.isSuccess());
Assert.assertEquals("x0[if 脚本]==>a==>b", response.getExecuteStepStrWithoutTime());
// 修改数据库
changeScriptData();
// 重新加载规则
flowExecutor.reloadRule();
Assert.assertEquals("x0[if 脚本]", flowExecutor.execute2Resp("chain3", "arg").getExecuteStepStr());
}
/**
* 修改数据库数据
*/
private void changeData() {
LiteflowConfig liteflowConfig = LiteflowConfigGetter.get();
SQLParserVO sqlParserVO = JsonUtil.parseObject(liteflowConfig.getRuleSourceExtData(), SQLParserVO.class);
Connection connection;
try {
connection = DriverManager.getConnection(sqlParserVO.getUrl(), sqlParserVO.getUsername(), sqlParserVO.getPassword());
Statement statement = connection.createStatement();
statement.executeUpdate("UPDATE EL_TABLE SET EL_DATA='THEN(a, c, b);' WHERE chain_name='chain1'");
} catch (SQLException e) {
throw new ELSQLException(e.getMessage());
}
}
/**
* 修改数据库数据
*/
private void changeScriptData() {
LiteflowConfig liteflowConfig = LiteflowConfigGetter.get();
SQLParserVO sqlParserVO = JsonUtil.parseObject(liteflowConfig.getRuleSourceExtData(), SQLParserVO.class);
Connection connection;
try {
connection = DriverManager.getConnection(sqlParserVO.getUrl(), sqlParserVO.getUsername(), sqlParserVO.getPassword());
Statement statement = connection.createStatement();
statement.executeUpdate("UPDATE SCRIPT_NODE_TABLE SET SCRIPT_NODE_DATA='return false;' WHERE SCRIPT_NODE_ID='x0'");
} catch (SQLException e) {
throw new ELSQLException(e.getMessage());
}
}
}

View File

@ -1,4 +1,20 @@
liteflow.rule-source-ext-data={"url":"jdbc:h2:mem:test_db;MODE=MySQL","driverClassName":"org.h2.Driver","username":"root","password":"123456","tableName":"EL_TABLE","chainNameField":"chain_name","elDataField":"EL_DATA"}
liteflow.rule-source-ext-data={\
"url":"jdbc:h2:mem:test_db;MODE=MySQL",\
"driverClassName":"org.h2.Driver",\
"username":"root",\
"password":"123456",\
"applicationName":"demo",\
"chainTableName":"EL_TABLE",\
"chainApplicationNameField":"application_name",\
"chainNameField":"chain_name",\
"elDataField":"EL_DATA",\
"scriptTableName":"script_node_table",\
"scriptApplicationNameField":"application_name",\
"scriptIdField":"script_node_id",\
"scriptNameField":"script_node_name",\
"scriptDataField":"script_node_data",\
"scriptTypeField":"script_node_type"\
}
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:test_db;MODE=MySQL

View File

@ -1,4 +1,10 @@
DELETE FROM EL_TABLE;
INSERT INTO EL_TABLE (CHAIN_NAME,EL_DATA) values ('chain1','THEN(a, b, c);');
INSERT INTO EL_TABLE (CHAIN_NAME,EL_DATA) values ('chain2','THEN(a, b, c);');
INSERT INTO EL_TABLE (APPLICATION_NAME,CHAIN_NAME,EL_DATA) values ('demo','chain1','THEN(a, b, c);');
INSERT INTO EL_TABLE (APPLICATION_NAME,CHAIN_NAME,EL_DATA) values ('demo','chain2','THEN(a, b, c);');
INSERT INTO EL_TABLE (APPLICATION_NAME,CHAIN_NAME,EL_DATA) values ('demo','chain3','IF(x0, THEN(a, b));');
DELETE FROM SCRIPT_NODE_TABLE;
INSERT INTO SCRIPT_NODE_TABLE (APPLICATION_NAME,SCRIPT_NODE_ID,SCRIPT_NODE_NAME,SCRIPT_NODE_TYPE,SCRIPT_NODE_DATA) values ('demo','x0','if 脚本','if_script','return true');
INSERT INTO SCRIPT_NODE_TABLE (APPLICATION_NAME,SCRIPT_NODE_ID,SCRIPT_NODE_NAME,SCRIPT_NODE_TYPE,SCRIPT_NODE_DATA) values ('demo','x1','if 脚本','if_script','return false');

View File

@ -1,6 +1,19 @@
create table `EL_TABLE` (
`id` bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
`chain_name` varchar(32) NOT NULL,
`el_data` varchar(1024) NOT NULL,
PRIMARY KEY (`id`)
) ;
create table `EL_TABLE`
(
`id` bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
`application_name` varchar(32) NOT NULL,
`chain_name` varchar(32) NOT NULL,
`el_data` varchar(1024) NOT NULL,
PRIMARY KEY (`id`)
);
create table `script_node_table`
(
`id` bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
`application_name` varchar(32) NOT NULL,
`script_node_id` varchar(32) NOT NULL,
`script_node_name` varchar(32) NOT NULL,
`script_node_type` varchar(32) NOT NULL,
`script_node_data` varchar(1024) NOT NULL,
PRIMARY KEY (`id`)
);

View File

@ -39,7 +39,7 @@
</scm>
<properties>
<revision>2.9.1</revision>
<revision>2.9.3</revision>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>8</maven.compiler.source>