package jnpf.flowable.model.filepermission; import java.util.*; /** * 流程版本内的文件规则契约。此类不读取登录态、不分配全局角色,也不替代原生准入。 * 配置引用必须在发布时与该版本的表单、字段及节点校验。 */ public final class FilePermissionPolicy { private FilePermissionPolicy() { } public enum Action { VIEW, DOWNLOAD, EDIT, REVIEW, ADD, REPLACE, REMOVE } public enum Effect { INHERIT, ALLOW, DENY } public enum Subject { CREATOR, CURRENT_HANDLERS, NON_CURRENT_HANDLERS, PARTICIPANTS, NODE_PARTICIPANTS } public enum Trigger { ACTIVE, ENTERED, COMPLETED, CANCELLED, TERMINATED } public enum OnReturn { KEEP, RELEASE } public static final class Rule { public final String id; public final String formId; /** 沿用原生字段标识;子表为 parentField-field,行 ID 属于运行态资源。 */ public final String field; public final Subject subject; public final String subjectNodeId; public final Trigger trigger; public final String triggerNodeId; public final OnReturn onReturn; public final Map actions; private Rule(Map source) { id = text(source, "id"); formId = text(source, "formId"); field = text(source, "field"); subject = enumeration(Subject.class, source.get("subject")); trigger = enumeration(Trigger.class, source.get("trigger")); subjectNodeId = subject == Subject.NODE_PARTICIPANTS ? text(source, "subjectNodeId") : null; triggerNodeId = trigger == Trigger.ACTIVE || trigger == Trigger.ENTERED ? text(source, "triggerNodeId") : null; // 持续规则必须显式选择退回行为,不能默认恢复旧权限快照。 onReturn = trigger == Trigger.ENTERED ? enumeration(OnReturn.class, source.get("onReturn")) : null; actions = parseActions(source.get("actions")); if (actions.isEmpty()) throw invalid("规则没有配置文件动作"); } } /** null 代表旧流程未启用;存在但格式错误不能退回旧授权。 */ public static List parse(Object config) { if (config == null) return Collections.emptyList(); if (!(config instanceof Map)) throw invalid("文件权限配置必须为对象"); Map map = (Map) config; if (!(map.get("version") instanceof Number) || ((Number) map.get("version")).doubleValue() != 1) { throw invalid("不支持的文件权限配置版本"); } if (!(map.get("rules") instanceof List)) throw invalid("文件规则必须为数组"); List result = new ArrayList<>(); Set ids = new HashSet<>(); for (Object value : (List) map.get("rules")) { if (!(value instanceof Map)) throw invalid("文件规则必须为对象"); Rule rule = new Rule((Map) value); if (!ids.add(rule.id)) throw invalid("文件规则 ID 重复"); result.add(rule); } return Collections.unmodifiableList(result); } public static Map parseActions(Object value) { if (!(value instanceof Map)) throw invalid("文件动作必须为对象"); Map result = new EnumMap<>(Action.class); for (Map.Entry entry : ((Map) value).entrySet()) { result.put(enumeration(Action.class, entry.getKey()), enumeration(Effect.class, entry.getValue())); } return Collections.unmodifiableMap(result); } /** availableFields 必须来自该版本的真实 uploadFile 字段,不使用客户端候选列表。 */ public static void validateReferences(List rules, Map> availableFields, Set nodeIds) { Objects.requireNonNull(availableFields, "availableFields"); Objects.requireNonNull(nodeIds, "nodeIds"); for (Rule rule : rules) { if (!availableFields.getOrDefault(rule.formId, Collections.emptySet()).contains(rule.field)) { throw invalid("文件规则引用的附件字段不存在: " + rule.id); } if (rule.triggerNodeId != null && !nodeIds.contains(rule.triggerNodeId) || rule.subjectNodeId != null && !nodeIds.contains(rule.subjectNodeId)) { throw invalid("文件规则引用的节点不存在: " + rule.id); } } } private static String text(Map map, String key) { Object value = map.get(key); if (!(value instanceof String) || ((String) value).trim().isEmpty()) throw invalid("缺少规则属性: " + key); return (String) value; } private static > T enumeration(Class type, Object value) { if (!(value instanceof String)) throw invalid("无效的 " + type.getSimpleName()); try { return Enum.valueOf(type, (String) value); } catch (IllegalArgumentException e) { throw invalid("无效的 " + type.getSimpleName() + ": " + value); } } private static IllegalArgumentException invalid(String message) { return new IllegalArgumentException(message); } }