package jnpf.flowable.util.filepermission; import jnpf.flowable.model.filepermission.FilePermissionPolicy.*; import jnpf.flowable.model.filepermission.FilePermissionView; import java.util.*; /** * 接收服务端一致性读取的运行事实,解析人员/触发条件后统一裁决。 * Facts 不是 HTTP 请求模型;节点集合必须使用快照中的稳定编码。 */ public final class FilePermissionRuntime { private FilePermissionRuntime() { } public enum Terminal { NONE, COMPLETED, CANCELLED, TERMINATED } public static final class Facts { public String tenantId; public String taskId; public String formId; public String userId; public String creatorId; public String revision; // 只有一致性读取、稳定节点映射及原生准入均确认后才能为 true。 public boolean verified; public boolean transitioning; public Terminal terminal = Terminal.NONE; public Set activeNodes = new HashSet<>(); public Map> currentHandlers = new HashMap<>(); public Map> participants = new HashMap<>(); // 由执行轮次记录解析 KEEP/RELEASE 后给出,禁止仅凭历史节点曾出现过推断。 public Set activeEnteredRules = new HashSet<>(); public Map> nativeUpperBounds = new LinkedHashMap<>(); public Map> inherited = new LinkedHashMap<>(); } public static FilePermissionView resolve(List rules, Facts facts) { Objects.requireNonNull(rules, "rules"); Objects.requireNonNull(facts, "facts"); if (!facts.verified || facts.transitioning || facts.revision == null || facts.revision.isEmpty() || facts.terminal == null) { return FilePermissionView.unavailable(facts.taskId, facts.formId, facts.transitioning ? "TRANSITIONING" : "RUNTIME_UNAVAILABLE"); } List applicable = new ArrayList<>(); for (Rule rule : rules) { if (Objects.equals(facts.formId, rule.formId) && triggered(rule, facts) && matches(rule, facts)) applicable.add(rule); } FilePermissionView view = new FilePermissionView(); view.setTaskId(facts.taskId); view.setFormId(facts.formId); view.setRevision(facts.revision); view.setStatus("ready"); facts.nativeUpperBounds.forEach((field, upper) -> { FilePermissionEvaluator.Result result = FilePermissionEvaluator.evaluate(facts.formId, field, 0, false, upper, facts.inherited.getOrDefault(field, Collections.emptyMap()), applicable); FilePermissionView.FieldAccess access = new FilePermissionView.FieldAccess(); access.setStatus("ready"); access.setActions(result.actions); access.setReviewEnabled(rules.stream().anyMatch(rule -> Objects.equals(facts.formId, rule.formId) && Objects.equals(field, rule.field) && rule.actions.get(Action.REVIEW) == Effect.ALLOW)); view.getFields().put(field, access); }); return view; } private static boolean triggered(Rule rule, Facts f) { switch (rule.trigger) { case ACTIVE: return f.terminal == Terminal.NONE && f.activeNodes.contains(rule.triggerNodeId); case ENTERED: return f.activeEnteredRules.contains(rule.id); case COMPLETED: return f.terminal == Terminal.COMPLETED; case CANCELLED: return f.terminal == Terminal.CANCELLED; case TERMINATED: return f.terminal == Terminal.TERMINATED; default: return false; } } private static boolean matches(Rule rule, Facts f) { if (f.userId == null || f.userId.trim().isEmpty()) return false; switch (rule.subject) { case CREATOR: return f.userId.equals(f.creatorId); case CURRENT_HANDLERS: if (f.terminal != Terminal.NONE) return false; // ACTIVE 节点字段规则只约束该节点办理人,不能污染其他并行节点。 if (rule.trigger == Trigger.ACTIVE) { return f.currentHandlers.getOrDefault(rule.triggerNodeId, Collections.emptySet()).contains(f.userId); } return isActiveHandler(f); case NON_CURRENT_HANDLERS: return isParticipant(f) && !isActiveHandler(f); case PARTICIPANTS: return isParticipant(f); case NODE_PARTICIPANTS: return f.participants.getOrDefault(rule.subjectNodeId, Collections.emptySet()).contains(f.userId); default: return false; } } private static boolean isParticipant(Facts f) { return f.userId.equals(f.creatorId) || f.participants.values().stream().anyMatch(users -> users.contains(f.userId)); } private static boolean isActiveHandler(Facts f) { return f.terminal == Terminal.NONE && f.currentHandlers.entrySet().stream() .anyMatch(e -> f.activeNodes.contains(e.getKey()) && e.getValue().contains(f.userId)); } }