/* * Copyright (C) 2005-2016 Alfresco Software Limited. * * This file is part of Alfresco * * 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 . */ package org.alfresco.repo.domain.permissions; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import org.alfresco.model.ContentModel; import org.alfresco.repo.domain.node.NodeDAO; import org.alfresco.repo.domain.node.NodeDAO.NodeRefQueryCallback; import org.alfresco.repo.lock.JobLockService; import org.alfresco.repo.lock.LockAcquisitionException; import org.alfresco.repo.lock.JobLockService.JobLockRefreshCallback; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.transaction.TransactionListenerAdapter; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.Pair; import org.alfresco.util.VmShutdownListener.VmShutdownException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Finds nodes with ASPECT_PENDING_FIX_ACL aspect and sets fixed ACLs for them * * @author Andreea Dragoi * @since 4.2.7 */ public class FixedAclUpdater extends TransactionListenerAdapter { private static final Log log = LogFactory.getLog(FixedAclUpdater.class); private static final Set PENDING_FIX_ACL_ASPECT_PROPS = pendingFixAclAspectProps(); public static final String FIXED_ACL_ASYNC_REQUIRED_KEY = "FIXED_ACL_ASYNC_REQUIRED"; public static final String FIXED_ACL_ASYNC_CALL_KEY = "FIXED_ACL_ASYNC_CALL"; private JobLockService jobLockService; private TransactionService transactionService; private AccessControlListDAO accessControlListDAO; private NodeDAO nodeDAO; private QName lockQName = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "FixedAclUpdater"); private long lockTimeToLive = 10000; private long lockRefreshTime = lockTimeToLive / 2; private int maxItemBatchSize = 100; public void setJobLockService(JobLockService jobLockService) { this.jobLockService = jobLockService; } public void setNodeDAO(NodeDAO nodeDAO) { this.nodeDAO = nodeDAO; } public void setTransactionService(TransactionService transactionService) { this.transactionService = transactionService; } public void setAccessControlListDAO(AccessControlListDAO accessControlListDAO) { this.accessControlListDAO = accessControlListDAO; } public void setMaxItemBatchSize(int maxItemBatchSize) { this.maxItemBatchSize = maxItemBatchSize; } public void setLockTimeToLive(long lockTimeToLive) { this.lockTimeToLive = lockTimeToLive; this.lockRefreshTime = lockTimeToLive / 2; } private static Set pendingFixAclAspectProps() { Set props = new HashSet<>(); props.add(ContentModel.PROP_SHARED_ACL_TO_REPLACE); props.add(ContentModel.PROP_INHERIT_FROM_ACL); return props; } private int findAndUpdateAcl(FixedAclUpdaterJobLockRefreshCallback jobCallback) { final Set aspects = new HashSet<>(1); aspects.add(ContentModel.ASPECT_PENDING_FIX_ACL); List nodesToUpdate = getNodesWithAspects(aspects); int processedNodes = 0; // loop over results for (final NodeRef nodeRef : nodesToUpdate) { // Check if we have been terminated if (!jobCallback.isActive.get()) { if (log.isDebugEnabled()) { log.debug(String.format("Processing node failed %s. Job not active", nodeRef)); } // terminate break; } if (log.isDebugEnabled()) { log.debug(String.format("Processing node %s", nodeRef)); } final Long nodeId = nodeDAO.getNodePair(nodeRef).getFirst(); try { transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { public Void execute() throws Throwable { // retrieve acl properties from node Long inheritFrom = (Long) nodeDAO.getNodeProperty(nodeId, ContentModel.PROP_INHERIT_FROM_ACL); Long sharedAclToReplace = (Long) nodeDAO.getNodeProperty(nodeId, ContentModel.PROP_SHARED_ACL_TO_REPLACE); // set inheritance using retrieved prop accessControlListDAO.setInheritanceForChildren(nodeRef, inheritFrom, sharedAclToReplace, true); nodeDAO.removeNodeAspects(nodeId, aspects); nodeDAO.removeNodeProperties(nodeId, PENDING_FIX_ACL_ASPECT_PROPS); if (log.isDebugEnabled()) { log.debug(String.format("Node processed ", nodeRef)); } return null; } }, false, true); } catch (Exception e) { if (log.isDebugEnabled()) { log.debug(String.format("Could not process node ", nodeRef), e); } } processedNodes++; } if (log.isDebugEnabled()) { log.debug(String.format("Nodes found %s; nodes processed %s", nodesToUpdate.size(), processedNodes)); } return processedNodes; } public void execute() { String lockToken = null; FixedAclUpdaterJobLockRefreshCallback callback = new FixedAclUpdaterJobLockRefreshCallback(); try { RunAsWork findAndUpdateAclRunAsWork = findAndUpdateAclRunAsWork(callback); lockToken = jobLockService.getLock(lockQName, lockTimeToLive, 0, 1); while (true) { jobLockService.refreshLock(lockToken, lockQName, lockRefreshTime, callback); Integer processed = AuthenticationUtil.runAs(findAndUpdateAclRunAsWork, AuthenticationUtil.getSystemUserName()); if (processed.intValue() == 0) { // There is no more to process break; } // There is still more to process, so continue } } catch (LockAcquisitionException e) { // already running } catch (VmShutdownException e) { if (log.isDebugEnabled()) { log.debug("FixedAclUpdater aborted"); } } finally { callback.isActive.set(false); jobLockService.releaseLock(lockToken, lockQName); } } private RunAsWork findAndUpdateAclRunAsWork(final FixedAclUpdaterJobLockRefreshCallback callback) { final RetryingTransactionCallback findAndUpdateAclWork = new RetryingTransactionCallback() { public Integer execute() throws Exception { return findAndUpdateAcl(callback); } }; // execute as system user to ensure fast, accurate results RunAsWork findAndUpdateAclRunAsWork = new RunAsWork() { @Override public Integer doWork() throws Exception { return transactionService.getRetryingTransactionHelper().doInTransaction(findAndUpdateAclWork, false, true); } }; return findAndUpdateAclRunAsWork; } private List getNodesWithAspects(final Set aspects) { return transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback>() { @Override public List execute() throws Throwable { GetNodesWithAspectCallback callback = new GetNodesWithAspectCallback(); nodeDAO.getNodesWithAspects(aspects, 1L, null, callback); return callback.getNodes(); } }, false, true); } private class GetNodesWithAspectCallback implements NodeRefQueryCallback { private List nodes = new ArrayList<>(); @Override public boolean handle(Pair nodePair) { if (nodes.size() < maxItemBatchSize) { nodes.add(nodePair.getSecond()); return true; } return false; } public List getNodes() { return nodes; } } private static class FixedAclUpdaterJobLockRefreshCallback implements JobLockRefreshCallback { public AtomicBoolean isActive = new AtomicBoolean(true); @Override public boolean isActive() { return isActive.get(); } @Override public void lockReleased() { isActive.set(false); } } @Override public void afterCommit() { Thread t = new Thread(new Runnable() { @Override public void run() { execute(); } }); t.start(); } }