-
Notifications
You must be signed in to change notification settings - Fork 6.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
329 additions
and
341 deletions.
There are no files selected for viewing
309 changes: 309 additions & 0 deletions
309
...-flowable/src/main/java/cn/iocoder/yudao/framework/flowable/core/util/BpmnModelUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,309 @@ | ||
package cn.iocoder.yudao.framework.flowable.core.util; | ||
|
||
import cn.hutool.core.collection.CollUtil; | ||
import org.flowable.bpmn.converter.BpmnXMLConverter; | ||
import org.flowable.bpmn.model.Process; | ||
import org.flowable.bpmn.model.*; | ||
|
||
import java.util.*; | ||
|
||
/** | ||
* 流程模型转操作工具类 | ||
*/ | ||
public class BpmnModelUtils { | ||
|
||
/** | ||
* 根据节点,获取入口连线 | ||
* | ||
* @param source 起始节点 | ||
* @return 入口连线列表 | ||
*/ | ||
public static List<SequenceFlow> getElementIncomingFlows(FlowElement source) { | ||
if (source instanceof FlowNode) { | ||
return ((FlowNode) source).getIncomingFlows(); | ||
} | ||
return new ArrayList<>(); | ||
} | ||
|
||
/** | ||
* 根据节点,获取出口连线 | ||
* | ||
* @param source 起始节点 | ||
* @return 出口连线列表 | ||
*/ | ||
public static List<SequenceFlow> getElementOutgoingFlows(FlowElement source) { | ||
if (source instanceof FlowNode) { | ||
return ((FlowNode) source).getOutgoingFlows(); | ||
} | ||
return new ArrayList<>(); | ||
} | ||
|
||
/** | ||
* 获取流程元素信息 | ||
* | ||
* @param model bpmnModel 对象 | ||
* @param flowElementId 元素 ID | ||
* @return 元素信息 | ||
*/ | ||
public static FlowElement getFlowElementById(BpmnModel model, String flowElementId) { | ||
Process process = model.getMainProcess(); | ||
return process.getFlowElement(flowElementId); | ||
} | ||
|
||
/** | ||
* 获得 BPMN 流程中,指定的元素们 | ||
* | ||
* @param model | ||
* @param clazz 指定元素。例如说,{@link UserTask}、{@link Gateway} 等等 | ||
* @return 元素们 | ||
*/ | ||
public static <T extends FlowElement> List<T> getBpmnModelElements(BpmnModel model, Class<T> clazz) { | ||
List<T> result = new ArrayList<>(); | ||
model.getProcesses().forEach(process -> { | ||
process.getFlowElements().forEach(flowElement -> { | ||
if (flowElement.getClass().isAssignableFrom(clazz)) { | ||
result.add((T) flowElement); | ||
} | ||
}); | ||
}); | ||
return result; | ||
} | ||
|
||
/** | ||
* 比较 两个bpmnModel 是否相同 | ||
* @param oldModel 老的bpmn model | ||
* @param newModel 新的bpmn model | ||
*/ | ||
public static boolean equals(BpmnModel oldModel, BpmnModel newModel) { | ||
// 由于 BpmnModel 未提供 equals 方法,所以只能转成字节数组,进行比较 | ||
return Arrays.equals(getBpmnBytes(oldModel), getBpmnBytes(newModel)); | ||
} | ||
|
||
/** | ||
* 把 bpmnModel 转换成 byte[] | ||
* @param model bpmnModel | ||
*/ | ||
public static byte[] getBpmnBytes(BpmnModel model) { | ||
if (model == null) { | ||
return new byte[0]; | ||
} | ||
BpmnXMLConverter converter = new BpmnXMLConverter(); | ||
return converter.convertToXML(model); | ||
} | ||
|
||
// ========== 遍历相关的方法 ========== | ||
|
||
/** | ||
* 找到 source 节点之前的所有用户任务节点 | ||
* | ||
* @param source 起始节点 | ||
* @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 | ||
* @param userTaskList 已找到的用户任务节点 | ||
* @return 用户任务节点 数组 | ||
*/ | ||
public static List<UserTask> getPreviousUserTaskList(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) { | ||
userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList; | ||
hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; | ||
// 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 | ||
if (source instanceof StartEvent && source.getSubProcess() != null) { | ||
userTaskList = getPreviousUserTaskList(source.getSubProcess(), hasSequenceFlow, userTaskList); | ||
} | ||
|
||
// 根据类型,获取入口连线 | ||
List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source); | ||
if (sequenceFlows == null) { | ||
return userTaskList; | ||
} | ||
// 循环找到目标元素 | ||
for (SequenceFlow sequenceFlow : sequenceFlows) { | ||
// 如果发现连线重复,说明循环了,跳过这个循环 | ||
if (hasSequenceFlow.contains(sequenceFlow.getId())) { | ||
continue; | ||
} | ||
// 添加已经走过的连线 | ||
hasSequenceFlow.add(sequenceFlow.getId()); | ||
// 类型为用户节点,则新增父级节点 | ||
if (sequenceFlow.getSourceFlowElement() instanceof UserTask) { | ||
userTaskList.add((UserTask) sequenceFlow.getSourceFlowElement()); | ||
} | ||
// 类型为子流程,则添加子流程开始节点出口处相连的节点 | ||
if (sequenceFlow.getSourceFlowElement() instanceof SubProcess) { | ||
// 获取子流程用户任务节点 | ||
List<UserTask> childUserTaskList = findChildProcessUserTaskList((StartEvent) ((SubProcess) sequenceFlow.getSourceFlowElement()).getFlowElements().toArray()[0], null, null); | ||
// 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续 | ||
if (CollUtil.isNotEmpty(childUserTaskList)) { | ||
userTaskList.addAll(childUserTaskList); | ||
} | ||
} | ||
// 继续迭代 | ||
userTaskList = getPreviousUserTaskList(sequenceFlow.getSourceFlowElement(), hasSequenceFlow, userTaskList); | ||
} | ||
return userTaskList; | ||
} | ||
|
||
/** | ||
* 迭代获取子流程用户任务节点 | ||
* | ||
* @param source 起始节点 | ||
* @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 | ||
* @param userTaskList 需要撤回的用户任务列表 | ||
* @return 用户任务节点 | ||
*/ | ||
public static List<UserTask> findChildProcessUserTaskList(FlowElement source, Set<String> hasSequenceFlow, List<UserTask> userTaskList) { | ||
hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; | ||
userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList; | ||
|
||
// 根据类型,获取出口连线 | ||
List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source); | ||
if (sequenceFlows == null) { | ||
return userTaskList; | ||
} | ||
// 循环找到目标元素 | ||
for (SequenceFlow sequenceFlow : sequenceFlows) { | ||
// 如果发现连线重复,说明循环了,跳过这个循环 | ||
if (hasSequenceFlow.contains(sequenceFlow.getId())) { | ||
continue; | ||
} | ||
// 添加已经走过的连线 | ||
hasSequenceFlow.add(sequenceFlow.getId()); | ||
// 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加 | ||
if (sequenceFlow.getTargetFlowElement() instanceof UserTask) { | ||
userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement()); | ||
continue; | ||
} | ||
// 如果节点为子流程节点情况,则从节点中的第一个节点开始获取 | ||
if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) { | ||
List<UserTask> childUserTaskList = findChildProcessUserTaskList((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), hasSequenceFlow, null); | ||
// 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续 | ||
if (CollUtil.isNotEmpty(childUserTaskList)) { | ||
userTaskList.addAll(childUserTaskList); | ||
continue; | ||
} | ||
} | ||
// 继续迭代 | ||
userTaskList = findChildProcessUserTaskList(sequenceFlow.getTargetFlowElement(), hasSequenceFlow, userTaskList); | ||
} | ||
return userTaskList; | ||
} | ||
|
||
|
||
/** | ||
* 迭代从后向前扫描,判断目标节点相对于当前节点是否是串行 | ||
* 不存在直接回退到子流程中的情况,但存在从子流程出去到父流程情况 | ||
* | ||
* @param source 起始节点 | ||
* @param target 目标节点 | ||
* @param visitedElements 已经经过的连线的 ID,用于判断线路是否重复 | ||
* @return 结果 | ||
*/ | ||
public static boolean isSequentialReachable(FlowElement source, FlowElement target, Set<String> visitedElements) { | ||
visitedElements = visitedElements == null ? new HashSet<>() : visitedElements; | ||
// 不能是开始事件和子流程 | ||
if (source instanceof StartEvent && isInEventSubprocess(source)) { | ||
return false; | ||
} | ||
|
||
// 根据类型,获取入口连线 | ||
List<SequenceFlow> sequenceFlows = getElementIncomingFlows(source); | ||
if (CollUtil.isEmpty(sequenceFlows)) { | ||
return true; | ||
} | ||
// 循环找到目标元素 | ||
for (SequenceFlow sequenceFlow : sequenceFlows) { | ||
// 如果发现连线重复,说明循环了,跳过这个循环 | ||
if (visitedElements.contains(sequenceFlow.getId())) { | ||
continue; | ||
} | ||
// 添加已经走过的连线 | ||
visitedElements.add(sequenceFlow.getId()); | ||
// 这条线路存在目标节点,这条线路完成,进入下个线路 | ||
FlowElement sourceFlowElement = sequenceFlow.getSourceFlowElement(); | ||
if (target.getId().equals(sourceFlowElement.getId())) { | ||
continue; | ||
} | ||
// 如果目标节点为并行网关,则不继续 | ||
if (sourceFlowElement instanceof ParallelGateway) { | ||
return false; | ||
} | ||
// 否则就继续迭代 | ||
if (!isSequentialReachable(sourceFlowElement, target, visitedElements)) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
|
||
/** | ||
* 判断当前节点是否属于不同的子流程 | ||
* | ||
* @param flowElement 被判断的节点 | ||
* @return true 表示属于子流程 | ||
*/ | ||
private static boolean isInEventSubprocess(FlowElement flowElement) { | ||
FlowElementsContainer flowElementsContainer = flowElement.getParentContainer(); | ||
while (flowElementsContainer != null) { | ||
if (flowElementsContainer instanceof EventSubProcess) { | ||
return true; | ||
} | ||
|
||
if (flowElementsContainer instanceof FlowElement) { | ||
flowElementsContainer = ((FlowElement) flowElementsContainer).getParentContainer(); | ||
} else { | ||
flowElementsContainer = null; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
/** | ||
* 根据正在运行的任务节点,迭代获取子级任务节点列表,向后找 | ||
* | ||
* @param source 起始节点 | ||
* @param runTaskKeyList 正在运行的任务 Key,用于校验任务节点是否是正在运行的节点 | ||
* @param hasSequenceFlow 已经经过的连线的 ID,用于判断线路是否重复 | ||
* @param userTaskList 需要撤回的用户任务列表 | ||
* @return 子级任务节点列表 | ||
*/ | ||
public static List<UserTask> iteratorFindChildUserTasks(FlowElement source, List<String> runTaskKeyList, | ||
Set<String> hasSequenceFlow, List<UserTask> userTaskList) { | ||
hasSequenceFlow = hasSequenceFlow == null ? new HashSet<>() : hasSequenceFlow; | ||
userTaskList = userTaskList == null ? new ArrayList<>() : userTaskList; | ||
// 如果该节点为开始节点,且存在上级子节点,则顺着上级子节点继续迭代 | ||
if (source instanceof StartEvent && source.getSubProcess() != null) { | ||
userTaskList = iteratorFindChildUserTasks(source.getSubProcess(), runTaskKeyList, hasSequenceFlow, userTaskList); | ||
} | ||
|
||
// 根据类型,获取出口连线 | ||
List<SequenceFlow> sequenceFlows = getElementOutgoingFlows(source); | ||
if (sequenceFlows == null) { | ||
return userTaskList; | ||
} | ||
// 循环找到目标元素 | ||
for (SequenceFlow sequenceFlow : sequenceFlows) { | ||
// 如果发现连线重复,说明循环了,跳过这个循环 | ||
if (hasSequenceFlow.contains(sequenceFlow.getId())) { | ||
continue; | ||
} | ||
// 添加已经走过的连线 | ||
hasSequenceFlow.add(sequenceFlow.getId()); | ||
// 如果为用户任务类型,且任务节点的 Key 正在运行的任务中存在,添加 | ||
if (sequenceFlow.getTargetFlowElement() instanceof UserTask && runTaskKeyList.contains((sequenceFlow.getTargetFlowElement()).getId())) { | ||
userTaskList.add((UserTask) sequenceFlow.getTargetFlowElement()); | ||
continue; | ||
} | ||
// 如果节点为子流程节点情况,则从节点中的第一个节点开始获取 | ||
if (sequenceFlow.getTargetFlowElement() instanceof SubProcess) { | ||
List<UserTask> childUserTaskList = iteratorFindChildUserTasks((FlowElement) (((SubProcess) sequenceFlow.getTargetFlowElement()).getFlowElements().toArray()[0]), runTaskKeyList, hasSequenceFlow, null); | ||
// 如果找到节点,则说明该线路找到节点,不继续向下找,反之继续 | ||
if (CollUtil.isNotEmpty(childUserTaskList)) { | ||
userTaskList.addAll(childUserTaskList); | ||
continue; | ||
} | ||
} | ||
// 继续迭代 | ||
userTaskList = iteratorFindChildUserTasks(sequenceFlow.getTargetFlowElement(), runTaskKeyList, hasSequenceFlow, userTaskList); | ||
} | ||
return userTaskList; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.