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.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s