刘光辉
119 分钟以前 06f314545ac3514d7ce28939920866ae8188d31c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
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));
    }
}