org.alfresco.repo.node.archive.NodeArchiveServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.alfresco.repo.node.archive.NodeArchiveServiceImpl.java

Source

/*
 * #%L
 * Alfresco Repository
 * %%
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */
package org.alfresco.repo.node.archive;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.query.CannedQuery;
import org.alfresco.query.CannedQueryFactory;
import org.alfresco.query.CannedQueryResults;
import org.alfresco.query.PagingRequest;
import org.alfresco.query.PagingResults;
import org.alfresco.repo.batch.BatchProcessWorkProvider;
import org.alfresco.repo.batch.BatchProcessor;
import org.alfresco.repo.batch.BatchProcessor.BatchProcessWorker;
import org.alfresco.repo.lock.JobLockService;
import org.alfresco.repo.lock.LockAcquisitionException;
import org.alfresco.repo.node.NodeArchiveServicePolicies;
import org.alfresco.repo.node.NodeArchiveServicePolicies.BeforePurgeNodePolicy;
import org.alfresco.repo.node.archive.RestoreNodeReport.RestoreStatus;
import org.alfresco.repo.policy.ClassPolicyDelegate;
import org.alfresco.repo.policy.PolicyComponent;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.repo.tenant.TenantService;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException;
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.security.AccessStatus;
import org.alfresco.service.cmr.security.AuthorityService;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.EqualsHelper;
import org.alfresco.util.Pair;
import org.alfresco.util.ParameterCheck;
import org.alfresco.util.VmShutdownListener;
import org.alfresco.util.registry.NamedObjectRegistry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Implementation of the node archive abstraction.
 * 
 * @author Derek Hulley, Jamal Kaabi-Mofrad
 */
public class NodeArchiveServiceImpl implements NodeArchiveService {
    private static final QName LOCK_QNAME = QName.createQName(NamespaceService.ALFRESCO_URI, "NodeArchive");
    private static final long LOCK_TTL = 60000;

    private static final String MSG_BUSY = "node.archive.msg.busy";
    private static final String CANNED_QUERY_ARCHIVED_NODES_LIST = "archivedNodesCannedQueryFactory";

    private static Log logger = LogFactory.getLog(NodeArchiveServiceImpl.class);

    protected NodeService nodeService;
    private PermissionService permissionService;
    private TransactionService transactionService;
    private JobLockService jobLockService;
    private AuthorityService authorityService;
    private NamedObjectRegistry<CannedQueryFactory<ArchivedNodeEntity>> cannedQueryRegistry;
    private TenantService tenantService;
    private boolean userNamesAreCaseSensitive = false;

    /** controls policy delegates */
    private PolicyComponent policyComponent;
    private ClassPolicyDelegate<BeforePurgeNodePolicy> beforePurgeNodeDelegate;

    public void setPolicyComponent(PolicyComponent policyComponent) {
        this.policyComponent = policyComponent;
    }

    public void setNodeService(NodeService nodeService) {
        this.nodeService = nodeService;
    }

    public void setPermissionService(PermissionService permissionService) {
        this.permissionService = permissionService;
    }

    public void setTransactionService(TransactionService transactionService) {
        this.transactionService = transactionService;
    }

    public NodeRef getStoreArchiveNode(StoreRef originalStoreRef) {
        return nodeService.getStoreArchiveNode(originalStoreRef);
    }

    public void setJobLockService(JobLockService jobLockService) {
        this.jobLockService = jobLockService;
    }

    public void init() {
        // Register the various policies
        beforePurgeNodeDelegate = policyComponent
                .registerClassPolicy(NodeArchiveServicePolicies.BeforePurgeNodePolicy.class);
    }

    public void setAuthorityService(AuthorityService authorityService) {
        this.authorityService = authorityService;
    }

    public void setCannedQueryRegistry(
            NamedObjectRegistry<CannedQueryFactory<ArchivedNodeEntity>> cannedQueryRegistry) {
        this.cannedQueryRegistry = cannedQueryRegistry;
    }

    public void setTenantService(TenantService tenantService) {
        this.tenantService = tenantService;
    }

    public void setUserNamesAreCaseSensitive(boolean userNamesAreCaseSensitive) {
        this.userNamesAreCaseSensitive = userNamesAreCaseSensitive;
    }

    public NodeRef getArchivedNode(NodeRef originalNodeRef) {
        StoreRef orginalStoreRef = originalNodeRef.getStoreRef();
        NodeRef archiveRootNodeRef = nodeService.getStoreArchiveNode(orginalStoreRef);
        // create the likely location of the archived node
        NodeRef archivedNodeRef = new NodeRef(archiveRootNodeRef.getStoreRef(), originalNodeRef.getId());
        return archivedNodeRef;
    }

    /**
     * Get all the nodes that were archived <b>from</b> the given store.
     * 
     * @param originalStoreRef      the original store to process
     */
    private List<NodeRef> getArchivedNodes(StoreRef originalStoreRef) {
        // Get the archive location
        final NodeRef archiveParentNodeRef = nodeService.getStoreArchiveNode(originalStoreRef);
        RunAsWork<List<ChildAssociationRef>> runAsWork = new RunAsWork<List<ChildAssociationRef>>() {
            @Override
            public List<ChildAssociationRef> doWork() throws Exception {
                String currentUser = AuthenticationUtil.getFullyAuthenticatedUser();
                if (currentUser == null) {
                    throw new AccessDeniedException("No authenticated user; cannot get archived nodes.");
                }
                return nodeService.getChildAssocs(archiveParentNodeRef, ContentModel.ASSOC_CHILDREN,
                        NodeArchiveService.QNAME_ARCHIVED_ITEM);
            }
        };
        // Fetch all children as 'system' user to bypass permission checks
        List<ChildAssociationRef> archivedAssocs = AuthenticationUtil.runAs(runAsWork,
                AuthenticationUtil.getSystemUserName());
        // Iterate and pull out NodeRefs with a permission check
        List<NodeRef> nodeRefs = new ArrayList<NodeRef>(archivedAssocs.size());
        for (ChildAssociationRef childAssociationRef : archivedAssocs) {
            NodeRef nodeRef = childAssociationRef.getChildRef();
            // Eliminate if the current user doesn't have permission to delete
            if (permissionService.hasPermission(nodeRef, PermissionService.DELETE) == AccessStatus.ALLOWED) {
                nodeRefs.add(nodeRef);
            }
        }
        return nodeRefs;
    }

    /**
     * @return                      Returns a work provider for batch processing
     * 
     * @since 3.3.4
     */
    private BatchProcessWorkProvider<NodeRef> getArchivedNodesWorkProvider(final StoreRef originalStoreRef,
            final String lockToken) {
        return new BatchProcessWorkProvider<NodeRef>() {
            private VmShutdownListener vmShutdownLister = new VmShutdownListener("getArchivedNodesWorkProvider");
            private List<NodeRef> nodeRefs;
            private boolean done;

            private synchronized List<NodeRef> getNodeRefs() {
                if (nodeRefs == null) {
                    nodeRefs = getArchivedNodes(originalStoreRef);
                }
                return nodeRefs;
            }

            /**
             * @return              Returns 0, always
             */
            public synchronized int getTotalEstimatedWorkSize() {
                return 0;
            }

            public synchronized Collection<NodeRef> getNextWork() {
                if (vmShutdownLister.isVmShuttingDown()) {
                    return Collections.emptyList();
                }
                // Make sure we still have the lock
                try {
                    // TODO: Replace with joblock callback mechanism that provides shutdown hints
                    jobLockService.refreshLock(lockToken, LOCK_QNAME, LOCK_TTL);
                } catch (LockAcquisitionException e) {
                    // This is OK.  We don't have the lock so just quit
                    return Collections.emptyList();
                }

                if (done) {
                    return Collections.emptyList();
                } else {
                    done = true;
                    return getNodeRefs();
                }
            }
        };
    }

    /**
     * This is the primary restore method that all <code>restore</code> methods fall back on.
     * It executes the restore for the node in a separate transaction and attempts to catch
     * the known conditions that can be reported back to the client.
     */
    public RestoreNodeReport restoreArchivedNode(final NodeRef archivedNodeRef, final NodeRef destinationNodeRef,
            final QName assocTypeQName, final QName assocQName) {
        RestoreNodeReport report = new RestoreNodeReport(archivedNodeRef);
        report.setTargetParentNodeRef(destinationNodeRef);
        try {
            // Transactional wrapper to attempt the restore
            RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
            RetryingTransactionCallback<NodeRef> restoreCallback = new RetryingTransactionCallback<NodeRef>() {
                public NodeRef execute() throws Exception {
                    return nodeService.restoreNode(archivedNodeRef, destinationNodeRef, assocTypeQName, assocQName);
                }
            };
            NodeRef newNodeRef = txnHelper.doInTransaction(restoreCallback, false, true);
            // success
            report.setRestoredNodeRef(newNodeRef);
            report.setStatus(RestoreStatus.SUCCESS);
        } catch (InvalidNodeRefException e) {
            report.setCause(e);
            NodeRef invalidNodeRef = e.getNodeRef();
            if (archivedNodeRef.equals(invalidNodeRef)) {
                // not too serious, but the node to archive is missing
                report.setStatus(RestoreStatus.FAILURE_INVALID_ARCHIVE_NODE);
            } else if (EqualsHelper.nullSafeEquals(destinationNodeRef, invalidNodeRef)) {
                report.setStatus(RestoreStatus.FAILURE_INVALID_PARENT);
            } else if (destinationNodeRef == null) {
                // get the original parent of the archived node
                ChildAssociationRef originalParentAssocRef = (ChildAssociationRef) nodeService
                        .getProperty(archivedNodeRef, ContentModel.PROP_ARCHIVED_ORIGINAL_PARENT_ASSOC);
                NodeRef originalParentNodeRef = originalParentAssocRef.getParentRef();
                if (EqualsHelper.nullSafeEquals(originalParentNodeRef, invalidNodeRef)) {
                    report.setStatus(RestoreStatus.FAILURE_INVALID_PARENT);
                } else {
                    // some other invalid node was detected
                    report.setStatus(RestoreStatus.FAILURE_OTHER);
                }
            } else {
                // some other invalid node was detected
                report.setStatus(RestoreStatus.FAILURE_OTHER);
            }
        } catch (DuplicateChildNodeNameException e) {
            report.setCause(e);
            report.setStatus(RestoreStatus.FAILURE_DUPLICATE_CHILD_NODE_NAME);
            logger.error(e);
        } catch (AccessDeniedException e) {
            report.setCause(e);
            report.setStatus(RestoreStatus.FAILURE_PERMISSION);
        } catch (Throwable e) {
            report.setCause(e);
            report.setStatus(RestoreStatus.FAILURE_OTHER);
            logger.error("An unhandled exception stopped the restore", e);
        }
        // done
        if (logger.isDebugEnabled()) {
            logger.debug("Attempted node restore: " + report);
        }
        return report;
    }

    /**
     * @see #restoreArchivedNode(NodeRef, NodeRef, QName, QName)
     */
    public RestoreNodeReport restoreArchivedNode(NodeRef archivedNodeRef) {
        return restoreArchivedNode(archivedNodeRef, null, null, null);
    }

    /**
     * @see #restoreArchivedNodes(List, NodeRef, QName, QName)
     */
    public List<RestoreNodeReport> restoreArchivedNodes(List<NodeRef> archivedNodeRefs) {
        return restoreArchivedNodes(archivedNodeRefs, null, null, null);
    }

    /**
     * @see #restoreArchivedNode(NodeRef, NodeRef, QName, QName)
     */
    public List<RestoreNodeReport> restoreArchivedNodes(List<NodeRef> archivedNodeRefs, NodeRef destinationNodeRef,
            QName assocTypeQName, QName assocQName) {
        List<RestoreNodeReport> results = new ArrayList<RestoreNodeReport>(archivedNodeRefs.size());
        for (NodeRef nodeRef : archivedNodeRefs) {
            RestoreNodeReport result = restoreArchivedNode(nodeRef, destinationNodeRef, assocTypeQName, assocQName);
            results.add(result);
        }
        return results;
    }

    /**
     * Uses batch processing and job locking to purge all archived nodes
     * 
     * @deprecated              In 3.4: to be removed
     */
    public List<RestoreNodeReport> restoreAllArchivedNodes(StoreRef originalStoreRef) {
        final String user = AuthenticationUtil.getFullyAuthenticatedUser();
        if (user == null) {
            throw new IllegalStateException("Cannot restore as there is no authenticated user.");
        }

        final List<RestoreNodeReport> results = Collections
                .synchronizedList(new ArrayList<RestoreNodeReport>(1000));
        /**
         * Worker that restores each node
         */
        BatchProcessWorker<NodeRef> worker = new BatchProcessor.BatchProcessWorkerAdaptor<NodeRef>() {
            @Override
            public void beforeProcess() throws Throwable {
                AuthenticationUtil.pushAuthentication();
            }

            public void process(NodeRef entry) throws Throwable {
                AuthenticationUtil.setFullyAuthenticatedUser(user);
                if (nodeService.exists(entry)) {
                    RestoreNodeReport report = restoreArchivedNode(entry);
                    // Append the results (it is synchronized)
                    results.add(report);
                }
            }

            @Override
            public void afterProcess() throws Throwable {
                AuthenticationUtil.popAuthentication();
            }
        };
        doBulkOperation(user, originalStoreRef, worker);
        return results;
    }

    /**
     * Finds the archive location for nodes that were deleted from the given store
     * and attempt to restore each node.
     * 
     * @see NodeService#getStoreArchiveNode(StoreRef)
     * @see #restoreArchivedNode(NodeRef, NodeRef, QName, QName)
     * 
     * @deprecated              In 3.4: to be removed
     */
    public List<RestoreNodeReport> restoreAllArchivedNodes(final StoreRef originalStoreRef,
            final NodeRef destinationNodeRef, final QName assocTypeQName, final QName assocQName) {
        final String user = AuthenticationUtil.getFullyAuthenticatedUser();
        if (user == null) {
            throw new IllegalStateException("Cannot restore as there is no authenticated user.");
        }

        final List<RestoreNodeReport> results = Collections
                .synchronizedList(new ArrayList<RestoreNodeReport>(1000));
        /**
         * Worker that restores each node
         */
        BatchProcessWorker<NodeRef> worker = new BatchProcessor.BatchProcessWorkerAdaptor<NodeRef>() {
            @Override
            public void beforeProcess() throws Throwable {
                AuthenticationUtil.pushAuthentication();
            }

            public void process(NodeRef nodeRef) throws Throwable {
                AuthenticationUtil.setFullyAuthenticatedUser(user);
                if (nodeService.exists(nodeRef)) {
                    RestoreNodeReport report = restoreArchivedNode(nodeRef, destinationNodeRef, assocTypeQName,
                            assocQName);
                    // Append the results (it is synchronized)
                    results.add(report);
                }
            }

            @Override
            public void afterProcess() throws Throwable {
                AuthenticationUtil.popAuthentication();
            }
        };
        doBulkOperation(user, originalStoreRef, worker);
        return results;
    }

    /**
     * This is the primary purge methd that all purge methods fall back on.  It isolates the delete
     * work in a new transaction.
     */
    public void purgeArchivedNode(final NodeRef archivedNodeRef) {
        RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
        RetryingTransactionCallback<Void> deleteCallback = new RetryingTransactionCallback<Void>() {
            public Void execute() throws Exception {
                if (!nodeService.exists(archivedNodeRef)) {
                    // Node has disappeared
                    return null;
                }
                invokeBeforePurgeNode(archivedNodeRef);
                nodeService.deleteNode(archivedNodeRef);
                return null;
            }
        };
        txnHelper.doInTransaction(deleteCallback, false, true);
    }

    /**
     * @see #purgeArchivedNode(NodeRef)
     */
    public void purgeArchivedNodes(List<NodeRef> archivedNodes) {
        for (NodeRef archivedNodeRef : archivedNodes) {
            purgeArchivedNode(archivedNodeRef);
        }
        // done
    }

    /**
     * Uses batch processing and job locking to purge all archived nodes
     */
    public void purgeAllArchivedNodes(StoreRef originalStoreRef) {
        final String user = AuthenticationUtil.getFullyAuthenticatedUser();
        if (user == null) {
            throw new IllegalStateException("Cannot purge as there is no authenticated user.");
        }

        /**
         * Worker that purges each node
         */
        BatchProcessWorker<NodeRef> worker = new BatchProcessor.BatchProcessWorkerAdaptor<NodeRef>() {
            @Override
            public void beforeProcess() throws Throwable {
                AuthenticationUtil.pushAuthentication();
            }

            public void process(NodeRef nodeRef) throws Throwable {
                AuthenticationUtil.setFullyAuthenticatedUser(user);
                if (nodeService.exists(nodeRef)) {
                    invokeBeforePurgeNode(nodeRef);
                    nodeService.deleteNode(nodeRef);
                }
            }

            @Override
            public void afterProcess() throws Throwable {
                AuthenticationUtil.popAuthentication();
            }
        };
        doBulkOperation(user, originalStoreRef, worker);
    }

    /**
     * Do batch-controlled work
     */
    private void doBulkOperation(final String user, StoreRef originalStoreRef, BatchProcessWorker<NodeRef> worker) {
        String lockToken = null;
        try {
            // Get a lock to keep refreshing
            lockToken = jobLockService.getLock(LOCK_QNAME, LOCK_TTL);
            // TODO: Should merely trigger a background job i.e. perhaps it should not be
            //       triggered by a user-based thread
            BatchProcessor<NodeRef> batchProcessor = new BatchProcessor<NodeRef>("ArchiveBulkPurgeOrRestore",
                    transactionService.getRetryingTransactionHelper(),
                    getArchivedNodesWorkProvider(originalStoreRef, lockToken), 2, 20, null, null, 1000);
            batchProcessor.process(worker, true);
        } catch (LockAcquisitionException e) {
            throw new AlfrescoRuntimeException(MSG_BUSY);
        } finally {
            try {
                if (lockToken != null) {
                    jobLockService.releaseLock(lockToken, LOCK_QNAME);
                }
            } catch (LockAcquisitionException e) {
                // Ignore
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public PagingResults<NodeRef> listArchivedNodes(ArchivedNodesCannedQueryBuilder cannedQueryBuilder) {
        ParameterCheck.mandatory("cannedQueryBuilder", cannedQueryBuilder);

        Long start = (logger.isDebugEnabled() ? System.currentTimeMillis() : null);

        // get canned query
        GetArchivedNodesCannedQueryFactory getArchivedNodesCannedQueryFactory = (GetArchivedNodesCannedQueryFactory) cannedQueryRegistry
                .getNamedObject(CANNED_QUERY_ARCHIVED_NODES_LIST);

        Pair<NodeRef, QName> archiveNodeRefAssocTypePair = getArchiveNodeRefAssocTypePair(
                cannedQueryBuilder.getArchiveRootNodeRef());
        GetArchivedNodesCannedQuery cq = (GetArchivedNodesCannedQuery) getArchivedNodesCannedQueryFactory
                .getCannedQuery(archiveNodeRefAssocTypePair.getFirst(), archiveNodeRefAssocTypePair.getSecond(),
                        cannedQueryBuilder.getFilter(), cannedQueryBuilder.isFilterIgnoreCase(),
                        cannedQueryBuilder.getPagingRequest(), cannedQueryBuilder.getSortOrderAscending());

        // execute canned query
        final CannedQueryResults<ArchivedNodeEntity> results = ((CannedQuery<ArchivedNodeEntity>) cq).execute();

        final List<ArchivedNodeEntity> page;
        if (results.getPageCount() > 0) {
            page = results.getPages().get(0);
        } else {
            page = Collections.emptyList();
        }

        // set total count
        final Pair<Integer, Integer> totalCount;
        PagingRequest pagingRequest = cannedQueryBuilder.getPagingRequest();
        if (pagingRequest.getRequestTotalCountMax() > 0) {
            totalCount = results.getTotalResultCount();
        } else {
            totalCount = null;
        }

        if (start != null) {
            int skipCount = pagingRequest.getSkipCount();
            int maxItems = pagingRequest.getMaxItems();
            int pageNum = (skipCount / maxItems) + 1;

            if (logger.isDebugEnabled()) {
                StringBuilder sb = new StringBuilder(300);
                sb.append("listArchivedNodes: ").append(page.size()).append(" items in ")
                        .append((System.currentTimeMillis() - start)).append("ms ").append("[pageNum=")
                        .append(pageNum).append(", skip=").append(skipCount).append(", max=").append(maxItems)
                        .append(", hasMorePages=").append(results.hasMoreItems()).append(", totalCount=")
                        .append(totalCount).append(", filter=").append(cannedQueryBuilder.getFilter())
                        .append(", sortOrderAscending=").append(cannedQueryBuilder.getSortOrderAscending())
                        .append("]");

                logger.debug(sb.toString());
            }
        }
        return new PagingResults<NodeRef>() {
            @Override
            public String getQueryExecutionId() {
                return results.getQueryExecutionId();
            }

            @Override
            public List<NodeRef> getPage() {
                List<NodeRef> nodeRefs = new ArrayList<NodeRef>(page.size());
                for (ArchivedNodeEntity entity : page) {
                    nodeRefs.add(entity.getNodeRef());
                }
                return nodeRefs;
            }

            @Override
            public boolean hasMoreItems() {
                return results.hasMoreItems();
            }

            @Override
            public Pair<Integer, Integer> getTotalResultCount() {
                return totalCount;
            }
        };
    }

    /**
     * {@inheritDoc}
     */
    public boolean hasFullAccess(NodeRef nodeRef) {
        ParameterCheck.mandatory("nodeRef", nodeRef);

        String currentUser = getCurrentUser();
        if (hasAdminAccess(currentUser)) {
            return true;
        } else {
            String archivedBy = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_ARCHIVED_BY);
            if (!userNamesAreCaseSensitive && archivedBy != null) {
                archivedBy = archivedBy.toLowerCase();
            }
            return currentUser.equals(archivedBy);
        }
    }

    protected boolean hasAdminAccess(String userID) {
        return authorityService.isAdminAuthority(userID);
    }

    private Pair<NodeRef, QName> getArchiveNodeRefAssocTypePair(final NodeRef archiveStoreRootNodeRef) {
        final String currentUser = getCurrentUser();

        if (archiveStoreRootNodeRef == null || !nodeService.exists(archiveStoreRootNodeRef)) {
            throw new InvalidNodeRefException("Invalid archive store root node Ref.", archiveStoreRootNodeRef);
        }

        if (hasAdminAccess(currentUser)) {
            return new Pair<NodeRef, QName>(archiveStoreRootNodeRef, ContentModel.ASSOC_CHILDREN);
        } else {
            List<ChildAssociationRef> list = AuthenticationUtil.runAs(new RunAsWork<List<ChildAssociationRef>>() {
                @Override
                public List<ChildAssociationRef> doWork() throws Exception {
                    return nodeService.getChildrenByName(archiveStoreRootNodeRef,
                            ContentModel.ASSOC_ARCHIVE_USER_LINK, Collections.singletonList(currentUser));
                }
            }, AuthenticationUtil.getAdminUserName());

            // Empty list means that the current user hasn't deleted anything yet.
            if (list == null || list.isEmpty()) {
                return new Pair<NodeRef, QName>(null, null);
            }
            NodeRef userArchive = list.get(0).getChildRef();
            return new Pair<NodeRef, QName>(userArchive, ContentModel.ASSOC_ARCHIVED_LINK);
        }
    }

    private String getCurrentUser() {
        String currentUser = AuthenticationUtil.getFullyAuthenticatedUser();
        if (currentUser == null) {
            throw new AccessDeniedException("No authenticated user; cannot get archived nodes.");
        }

        if (!userNamesAreCaseSensitive
                && !AuthenticationUtil.getSystemUserName().equals(tenantService.getBaseNameUser(currentUser))) {
            // user names are not case-sensitive
            currentUser = currentUser.toLowerCase();
        }
        return currentUser;
    }

    protected void invokeBeforePurgeNode(NodeRef nodeRef) {
        if (ignorePolicy(nodeRef)) {
            return;
        }

        // get qnames to invoke against
        Set<QName> qnames = getTypeAndAspectQNames(nodeRef);
        // execute policy for node type and aspects
        NodeArchiveServicePolicies.BeforePurgeNodePolicy policy = beforePurgeNodeDelegate.get(nodeRef, qnames);
        policy.beforePurgeNode(nodeRef);
    }

    /**
     * Get all aspect and node type qualified names
     * 
     * @param nodeRef
     *            the node we are interested in
     * @return Returns a set of qualified names containing the node type and all
     *         the node aspects, or null if the node no longer exists
     */
    protected Set<QName> getTypeAndAspectQNames(NodeRef nodeRef) {
        Set<QName> qnames = null;
        try {
            Set<QName> aspectQNames = nodeService.getAspects(nodeRef);

            QName typeQName = nodeService.getType(nodeRef);

            qnames = new HashSet<QName>(aspectQNames.size() + 1);
            qnames.addAll(aspectQNames);
            qnames.add(typeQName);
        } catch (InvalidNodeRefException e) {
            qnames = Collections.emptySet();
        }
        // done
        return qnames;
    }

    private boolean ignorePolicy(NodeRef nodeRef) {
        return false;
    }
}