package jnpf.flowable.util.filepermission; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import jnpf.flowable.model.filepermission.FilePermissionPolicy; import java.util.*; /** 实例创建时的独立配置快照;不含当前授权,不读取最新模板,也不以引擎实例 ID 定位。 */ public final class FilePermissionSnapshot { private static final ObjectMapper JSON = new ObjectMapper() .enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION) .enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS); private final String json; private final String sourceFlowId; private final List rules; private final Set enteredNodes; private final Set activeReleaseRules; private FilePermissionSnapshot(String json, String sourceFlowId, List rules, Set enteredNodes, Set activeReleaseRules) { this.json = json; this.sourceFlowId = sourceFlowId; this.rules = rules; this.enteredNodes = Collections.unmodifiableSet(enteredNodes); this.activeReleaseRules = Collections.unmodifiableSet(activeReleaseRules); } public static String create(String tenantId, String taskId, String flowId, Map policy) { required(tenantId); required(taskId); required(flowId); if (FilePermissionPolicy.parse(policy).isEmpty()) return null; Map value = new LinkedHashMap<>(); value.put("version", 1); value.put("tenantId", tenantId); value.put("taskId", taskId); value.put("sourceFlowId", flowId); value.put("policy", policy); value.put("enteredNodes", Collections.emptyList()); value.put("activeReleaseRules", Collections.emptyList()); return encode(value); } /** 仅 SQL NULL 表示原有行为;空串/损坏配置/上下文不匹配均不得降级。 */ public static FilePermissionSnapshot read(String json, String tenantId, String taskId) { required(tenantId); required(taskId); if (json == null) return null; Map value = decode(json); if (!Integer.valueOf(1).equals(value.get("version")) || !tenantId.equals(value.get("tenantId")) || !taskId.equals(value.get("taskId"))) { throw new IllegalArgumentException("流程文件权限快照版本或实例归属不匹配"); } Object flowId = value.get("sourceFlowId"); if (!(flowId instanceof String)) throw new IllegalArgumentException("流程文件权限快照缺少来源版本"); required((String) flowId); if (!value.containsKey("policy") || value.get("policy") == null) { throw new IllegalArgumentException("流程文件权限快照缺少规则"); } List rules = FilePermissionPolicy.parse(value.get("policy")); if (rules.isEmpty()) throw new IllegalArgumentException("受控实例文件规则为空"); Set activeReleaseRules = ruleIds(value.get("activeReleaseRules"), "可解除规则状态"); Set releaseRuleIds = new HashSet<>(); for (FilePermissionPolicy.Rule rule : rules) { if (rule.trigger == FilePermissionPolicy.Trigger.ENTERED && rule.onReturn == FilePermissionPolicy.OnReturn.RELEASE) releaseRuleIds.add(rule.id); } if (!releaseRuleIds.containsAll(activeReleaseRules)) { throw new IllegalArgumentException("流程文件权限快照包含未知的可解除规则"); } return new FilePermissionSnapshot(json, (String) flowId, rules, enteredNodes(value.get("enteredNodes")), activeReleaseRules); } /** 撤销申请派生任务沿用来源实例配置,绑定自己的任务 ID,不读取后来修改的模板。 */ public String forDerivedTask(String newTaskId) { required(newTaskId); Map value = decode(json); value.put("taskId", newTaskId); value.put("enteredNodes", Collections.emptyList()); value.put("activeReleaseRules", Collections.emptyList()); return encode(value); } /** 节点进入事实只增不减;规则策略仍保持创建实例时的冻结值。 */ public String withEnteredNodes(Collection nodeIds) { if (nodeIds == null || nodeIds.isEmpty()) return json; LinkedHashSet merged = new LinkedHashSet<>(enteredNodes); LinkedHashSet active = new LinkedHashSet<>(activeReleaseRules); for (String nodeId : nodeIds) { required(nodeId); merged.add(nodeId); } for (FilePermissionPolicy.Rule rule : rules) { if (rule.trigger == FilePermissionPolicy.Trigger.ENTERED && rule.onReturn == FilePermissionPolicy.OnReturn.RELEASE && nodeIds.contains(rule.triggerNodeId)) active.add(rule.id); } if (merged.equals(enteredNodes) && active.equals(activeReleaseRules)) return json; Map value = decode(json); value.put("enteredNodes", new ArrayList<>(merged)); value.put("activeReleaseRules", new ArrayList<>(active)); return encode(value); } /** 退回时,引擎判定为目标节点之后并作废的触发节点,其可解除规则退出当前执行轮次。 */ public String withReturnedPastNodes(Collection invalidatedNodeIds) { if (invalidatedNodeIds == null || invalidatedNodeIds.isEmpty() || activeReleaseRules.isEmpty()) return json; Set invalidated = new HashSet<>(); for (String nodeId : invalidatedNodeIds) { required(nodeId); invalidated.add(nodeId); } LinkedHashSet active = new LinkedHashSet<>(activeReleaseRules); for (FilePermissionPolicy.Rule rule : rules) { if (rule.trigger == FilePermissionPolicy.Trigger.ENTERED && rule.onReturn == FilePermissionPolicy.OnReturn.RELEASE && invalidated.contains(rule.triggerNodeId)) active.remove(rule.id); } if (active.equals(activeReleaseRules)) return json; Map value = decode(json); value.put("activeReleaseRules", new ArrayList<>(active)); return encode(value); } public String getSourceFlowId() { return sourceFlowId; } public List getRules() { return rules; } public Set getEnteredNodes() { return enteredNodes; } public Set getActiveReleaseRules() { return activeReleaseRules; } private static Set enteredNodes(Object value) { if (value == null) return new LinkedHashSet<>(); // 兼容功能启用前已冻结的实例快照。 return ruleIds(value, "节点历史"); } private static Set ruleIds(Object value, String label) { if (value == null) return new LinkedHashSet<>(); if (!(value instanceof Collection)) throw new IllegalArgumentException("流程文件权限快照的" + label + "无效"); LinkedHashSet result = new LinkedHashSet<>(); for (Object item : (Collection) value) { if (!(item instanceof String)) throw new IllegalArgumentException("流程文件权限快照的" + label + "无效"); required((String) item); result.add((String) item); } return result; } private static Map decode(String json) { try { Map value = JSON.readValue(json, new TypeReference>() { }); if (value == null) throw new IllegalArgumentException("流程文件权限快照无效"); return value; } catch (java.io.IOException e) { throw new IllegalArgumentException("流程文件权限快照损坏", e); } } private static String encode(Map value) { try { return JSON.writeValueAsString(value); } catch (java.io.IOException e) { throw new IllegalArgumentException("无法生成流程文件权限快照", e); } } private static void required(String value) { if (value == null || value.trim().isEmpty()) throw new IllegalArgumentException("流程文件权限缺少实例上下文"); } }