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<String> activeNodes = new HashSet<>();
|
public Map<String, Set<String>> currentHandlers = new HashMap<>();
|
public Map<String, Set<String>> participants = new HashMap<>();
|
// 由执行轮次记录解析 KEEP/RELEASE 后给出,禁止仅凭历史节点曾出现过推断。
|
public Set<String> activeEnteredRules = new HashSet<>();
|
public Map<String, Map<Action, Boolean>> nativeUpperBounds = new LinkedHashMap<>();
|
public Map<String, Map<Action, Boolean>> inherited = new LinkedHashMap<>();
|
}
|
|
public static FilePermissionView resolve(List<Rule> 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<Rule> 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));
|
}
|
}
|