Alfresco – Implementing delete behavior

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.

Published by angelborroy

Understanding software.

One thought on “Alfresco – Implementing delete behavior

Leave a comment