如何在AEM OTB删除工作流删除JCR Node前读取其属性?
我完全理解你遇到的痛点——默认的EventListener和ResourceChangeListener大多是在节点已经被持久化删除后才触发,导致你拿不到节点数据,竞态条件更是雪上加霜。下面给你两个最可靠的解决方案,直接命中你的需求:
方案1:关联现有OTB删除工作流(最可控的方式)
AEM的原生删除工作流(比如页面删除、资产删除的默认工作流)都是由一系列顺序执行的步骤组成的,我们可以直接在删除动作前插入自定义审核步骤,从根源上避免删除后才触发的问题:
定位目标OTB工作流模型
找到你要修改的原生工作流,比如:- 页面删除工作流:
/etc/workflow/models/delete-page - 资产删除工作流:
/etc/workflow/models/dam-delete
- 页面删除工作流:
添加自定义工作流步骤
复制原工作流模型(避免直接修改原生配置),在Delete核心步骤之前添加一个自定义的工作流进程(WorkflowProcess):- 写一个实现
com.adobe.granite.workflow.exec.WorkflowProcess的类,在execute方法中获取要删除的节点路径:@Component(service = WorkflowProcess.class, property = { "process.label=Pre-Delete Attribute Audit" }) public class PreDeleteAuditProcess implements WorkflowProcess { @Reference private ResourceResolverFactory resolverFactory; @Override public void execute(WorkItem workItem, WorkflowSession workflowSession, MetaDataMap metaDataMap) throws WorkflowException { // 从工作流负载中获取要删除的节点路径 String payloadPath = workItem.getWorkflowData().getPayload().toString(); // 使用system用户获取资源(确保权限足够) try (ResourceResolver resolver = resolverFactory.getServiceResourceResolver(Map.of(ResourceResolverFactory.SUBSERVICE, "audit-service"))) { Resource targetResource = resolver.getResource(payloadPath); if (targetResource != null) { // 读取节点属性做审核逻辑 ValueMap properties = targetResource.getValueMap(); String auditAttr = properties.get("your-audit-property", String.class); // 示例:如果属性不符合要求,终止工作流 if (!isValid(auditAttr)) { workflowSession.terminateWorkflow(workItem.getWorkflow()); return; } } } catch (LoginException e) { // 处理登录异常 } } private boolean isValid(String attrValue) { // 你的审核逻辑 return attrValue != null && !attrValue.isEmpty(); } } - 在工作流模型编辑器中,把这个自定义步骤拖到
Delete步骤之前,设置为必选执行。
- 写一个实现
设置为默认工作流
将修改后的工作流模型设置为对应删除操作的默认工作流(比如页面删除的默认工作流),这样所有OTB删除操作都会先执行你的审核步骤。
方案2:使用JCR BEFORE_NODE_REMOVED事件(覆盖非工作流删除场景)
如果需要覆盖直接通过节点删除(比如CRXDE中删除、代码删除)的场景,JCR提供了BEFORE类型的事件,可以在节点物理删除前触发:
注册JCR BEFORE事件监听器
不要用默认的ResourceChangeListener,而是注册一个监听Event.BEFORE_NODE_REMOVED的JCR监听器:@Component(service = EventListener.class) public class PreDeleteNodeListener implements javax.jcr.observation.EventListener { @Reference private SlingRepository repository; @Activate protected void activate() throws RepositoryException { Session session = repository.loginService("audit-service", null); try { ObservationManager obsManager = session.getWorkspace().getObservationManager(); // 监听BEFORE_NODE_REMOVED事件,指定路径范围 obsManager.addEventListener( this, Event.BEFORE_NODE_REMOVED, "/content/your-target-path", // 你要监听的根路径 true, // isDeep null, // uuidFilter null, // nodeTypeNameFilter false // noLocal ); } finally { session.logout(); } } @Override public void onEvent(EventIterator events) { while (events.hasNext()) { Event event = events.nextEvent(); String nodePath = event.getPath(); // 使用system会话获取节点(此时节点尚未删除) try (Session session = repository.loginService("audit-service", null)) { Node targetNode = session.getNode(nodePath); // 读取属性做审核 String auditAttr = targetNode.getProperty("your-audit-property").getString(); // 这里注意:BEFORE事件是在会话提交阶段触发的,不能修改当前会话,但可以记录日志、发送通知等 if (!isValid(auditAttr)) { // 可以抛出异常中断删除操作(注意:需要确保异常能传递到会话提交环节) throw new RepositoryException("Attribute audit failed for node: " + nodePath); } } catch (RepositoryException e) { // 处理异常 } } } private boolean isValid(String attrValue) { // 你的审核逻辑 return attrValue != null && !attrValue.isEmpty(); } }关键注意事项
BEFORE_NODE_REMOVED事件是同步触发的,在会话提交过程中、节点删除前执行,完全不会有竞态条件。- 监听器的运行用户必须有足够的权限读取目标节点的属性,建议使用service用户。
为什么之前的方案有竞态条件?
你之前用的EventListener或ResourceChangeListener默认监听的是AFTER类型的事件(比如NODE_REMOVED或Resource/REMOVE),这些事件是在节点删除后异步触发的,所以会出现有时能拿到节点、有时拿不到的竞态情况。而上面的两种方案都是在删除动作执行前触发,从根本上解决了这个问题。
内容的提问来源于stack exchange,提问作者Dee

