Alfresco behaviors mechanism allow us to inject custom logic in repository nodes lifecycle. As you know, a detailed tutorial on this is available from Jeff Potts.
However, we need to know some extra information in order to develop a succesful behavior. In this article, we are going to extend node delete policy by deleting all existing copies of a deleted node.
Basic implementation
Following existing instructions, we can write a simple implementation.
public class SimpleBasicDeleteBehaviour implements NodeServicePolicies.BeforeDeleteNodePolicy { private PolicyComponent policyComponent; private NodeService nodeService; public void init() { policyComponent.bindClassBehaviour( NodeServicePolicies.BeforeDeleteNodePolicy.QNAME, ContentModel.TYPE_CONTENT, new JavaBehaviour(this, "beforeDeleteNode", Behaviour.NotificationFrequency.FIRST_EVENT)); } @Override public void beforeDeleteNode(NodeRef nodeRef) { // Find copied nodes and delete them for (AssociationRef copyAssoc : nodeService.getSourceAssocs(nodeRef,ContentModel.ASSOC_ORIGINAL)) { nodeService.deleteNode(copyAssoc.getSourceRef()); } } }
By using this code, every node with copies can’t be deleted. And no log trace will be produced. Let me show you why.
Extra information
- Any Alfresco transaction involving a node deletion, will not allow to perform any updating or deleting operation on custom policies
- Our custom policy will be executed inside the same deletion transaction from the original node and this transaction is managed by thread
Transactional implementation
We can write a new code avoiding to use the same transaction and the same thread from original deletion operation.
public class TransactionalDeleteBehaviour implements NodeServicePolicies.BeforeDeleteNodePolicy { // Key to identify resources associated to transaction private static final String KEY_RELATED_NODES = TransactionalDeleteBehaviour.class.getName() + ".relatedNodes"; private PolicyComponent policyComponent; private NodeService nodeService; private TransactionService transactionService; private TransactionListener transactionListener; private ThreadPoolExecutor threadPoolExecutor; // Bind behaviour and initialize transaction listener public void init() { policyComponent.bindClassBehaviour( NodeServicePolicies.BeforeDeleteNodePolicy.QNAME, ContentModel.TYPE_CONTENT, new JavaBehaviour(this, "beforeDeleteNode", Behaviour.NotificationFrequency.FIRST_EVENT)); this.transactionListener = new RelatedNodesTransactionListener(); } @Override public void beforeDeleteNode(NodeRef nodeRef) { // Bind listener to current transaction AlfrescoTransactionSupport.bindListener(transactionListener); // Get some related nodes to work with List<NodeRef> relatedNodes = new ArrayList<NodeRef>(); for (AssociationRef copyAssoc : nodeService.getSourceAssocs(nodeRef, ContentModel.ASSOC_ORIGINAL)) { relatedNodes.add(copyAssoc.getSourceRef()); } // Transactions involving several nodes need resource updating List<NodeRef> currentRelatedNodes = AlfrescoTransactionSupport.getResource(KEY_RELATED_NODES); if (currentRelatedNodes == null) { currentRelatedNodes = relatedNodes; } else { currentRelatedNodes.addAll(relatedNodes); } // Put resources to be used in transaction listener AlfrescoTransactionSupport.bindResource(KEY_RELATED_NODES, currentRelatedNodes); } // Listening "afterCommit" transaction event private class RelatedNodesTransactionListener extends TransactionListenerAdapter implements TransactionListener { @Override public void afterCommit() { @SuppressWarnings("unchecked") List<NodeRef> nodesToBeReviewed = (List<NodeRef>) AlfrescoTransactionSupport.getResource(KEY_RELATED_NODES); if (nodesToBeReviewed != null) { for (NodeRef nodeToBeReviewed : nodesToBeReviewed) { // Launch every node work in a different thread Runnable runnable = new RelatedNodeDeletion(nodeToBeReviewed); threadPoolExecutor.execute(runnable); } } } @Override public void flush() { } } // Thread to work with an individual node private class RelatedNodeDeletion implements Runnable { private NodeRef nodeToBeReviewed; private RelatedNodeDeletion(NodeRef nodeToBeReviewed) { this.nodeToBeReviewed = nodeToBeReviewed; } @Override public void run() { AuthenticationUtil.runAsSystem(new RunAsWork<Void>() { public Void doWork() throws Exception { RetryingTransactionCallback<Void> callback = new RetryingTransactionCallback<Void>() { @Override public Void execute() throws Throwable { nodeService.deleteNode(nodeToBeReviewed); return null; } }; try { RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper(); txnHelper.doInTransaction(callback, false, true); } catch (Throwable e) { e.printStackTrace(); } return null; } }); } }
So, node deletion will be performed asynchronously once the original deletion operation is finished. And any transaction conflict with Alfresco default behavior will be avoided.
Hello,
I have a doubt, when threadPoolExecutor is initialized? Thanks!