/* * #%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 . * #L% */ package org.alfresco.repo.domain.permissions; import static org.apache.commons.lang3.BooleanUtils.toBoolean; 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 java.util.concurrent.atomic.AtomicBoolean; import org.alfresco.model.ContentModel; import org.alfresco.repo.batch.BatchProcessWorkProvider; import org.alfresco.repo.batch.BatchProcessor; 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.JobLockService.JobLockRefreshCallback; import org.alfresco.repo.lock.LockAcquisitionException; 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.PermissionServicePolicies; import org.alfresco.repo.security.permissions.PermissionServicePolicies.OnInheritPermissionsDisabled; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; 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.PolicyIgnoreUtil; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; /** * Finds nodes with ASPECT_PENDING_FIX_ACL aspect and sets fixed ACLs for them * * @author Andreea Dragoi * @author sglover * @since 4.2.7 */ public class FixedAclUpdater extends TransactionListenerAdapter implements ApplicationContextAware { 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 ApplicationContext applicationContext; 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; private int numThreads = 4; private ClassPolicyDelegate onInheritPermissionsDisabledDelegate; private PolicyComponent policyComponent; private PolicyIgnoreUtil policyIgnoreUtil; public void setNumThreads(int numThreads) { this.numThreads = numThreads; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; }; 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; } public void setPolicyComponent(PolicyComponent policyComponent) { this.policyComponent = policyComponent; } public void setPolicyIgnoreUtil(PolicyIgnoreUtil policyIgnoreUtil) { this.policyIgnoreUtil = policyIgnoreUtil; } public void init() { onInheritPermissionsDisabledDelegate = policyComponent.registerClassPolicy(PermissionServicePolicies.OnInheritPermissionsDisabled.class); } private class GetNodesWithAspects { private Set aspects; private int workSize; private GetNodesWithAspectCallback getNodesCallback; GetNodesWithAspects(Set aspects) { this.aspects = aspects; this.getNodesCallback = new GetNodesWithAspectCallback(); this.workSize = countNodesWithAspects(); } int getWorkSize() { return workSize; } List getNodesWithAspects() { List nodes = transactionService.getRetryingTransactionHelper() .doInTransaction(new RetryingTransactionCallback>() { @Override public List execute() throws Throwable { getNodesCallback.init(); nodeDAO.getNodesWithAspects(aspects, getNodesCallback.getMinNodeId(), null, getNodesCallback); getNodesCallback.done(); return getNodesCallback.getNodes(); } }, false, true); return nodes; } int countNodesWithAspects() { final CountNodesWithAspectCallback countNodesCallback = new CountNodesWithAspectCallback(); int count = transactionService.getRetryingTransactionHelper() .doInTransaction(new RetryingTransactionCallback() { @Override public Integer execute() throws Throwable { nodeDAO.getNodesWithAspects(aspects, 0l, null, countNodesCallback); return countNodesCallback.getCount(); } }, false, true); return count; } } private class AclWorkProvider implements BatchProcessWorkProvider { private GetNodesWithAspects getNodesWithAspects; AclWorkProvider() { getNodesWithAspects = new GetNodesWithAspects(Collections.singleton(ContentModel.ASPECT_PENDING_FIX_ACL)); } @Override public int getTotalEstimatedWorkSize() { int workSize = getNodesWithAspects.getWorkSize(); return workSize; } @Override public Collection getNextWork() { return getNodesWithAspects.getNodesWithAspects(); } } private class AclWorker implements BatchProcessor.BatchProcessWorker { private Set aspects = new HashSet<>(1); AclWorker() { aspects.add(ContentModel.ASPECT_PENDING_FIX_ACL); } public String getIdentifier(NodeRef nodeRef) { return String.valueOf(nodeRef.toString()); } public void beforeProcess() throws Throwable { } public void afterProcess() throws Throwable { } public void process(final NodeRef nodeRef) throws Throwable { RunAsWork findAndUpdateAclRunAsWork = new RunAsWork() { @Override public Void doWork() throws Exception { if (log.isDebugEnabled()) { log.debug(String.format("Processing node %s", nodeRef)); } final Long nodeId = nodeDAO.getNodePair(nodeRef).getFirst(); // 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 (!policyIgnoreUtil.ignorePolicy(nodeRef)) { boolean transformedToAsyncOperation = toBoolean((Boolean) AlfrescoTransactionSupport.getResource(FixedAclUpdater.FIXED_ACL_ASYNC_REQUIRED_KEY)); OnInheritPermissionsDisabled onInheritPermissionsDisabledPolicy = onInheritPermissionsDisabledDelegate.get(ContentModel.TYPE_BASE); onInheritPermissionsDisabledPolicy.onInheritPermissionsDisabled(nodeRef, transformedToAsyncOperation); } if (log.isDebugEnabled()) { log.debug(String.format("Node processed %s", nodeRef)); } return null; } }; AuthenticationUtil.runAs(findAndUpdateAclRunAsWork, AuthenticationUtil.getSystemUserName()); } }; private class GetNodesWithAspectCallback implements NodeRefQueryCallback { private List nodes = new ArrayList<>(); private long minNodeId; private long maxNodeId; void init() { nodes.clear(); } void done() { this.minNodeId = maxNodeId + 1; } @Override public boolean handle(Pair nodePair) { if (nodes.size() < maxItemBatchSize) { nodes.add(nodePair.getSecond()); maxNodeId = nodePair.getFirst(); return true; } return false; } long getMinNodeId() { return minNodeId; } public List getNodes() { return nodes; } } private class CountNodesWithAspectCallback implements NodeRefQueryCallback { private int count = 0; @Override public boolean handle(Pair nodePair) { count++; return true; } public int getCount() { return count; } } 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); } } 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; } public int execute() { String lockToken = null; FixedAclUpdaterJobLockRefreshCallback jobLockRefreshCallback = new FixedAclUpdaterJobLockRefreshCallback(); try { lockToken = jobLockService.getLock(lockQName, lockTimeToLive, 0, 1); jobLockService.refreshLock(lockToken, lockQName, lockRefreshTime, jobLockRefreshCallback); AclWorkProvider provider = new AclWorkProvider(); AclWorker worker = new AclWorker(); BatchProcessor bp = new BatchProcessor<>( "FixedAclUpdater", transactionService.getRetryingTransactionHelper(), provider, numThreads, maxItemBatchSize, applicationContext, log, 100); int count = bp.process(worker, true); return count; } catch (LockAcquisitionException e) { // already running return 0; } finally { jobLockRefreshCallback.isActive.set(false); if(lockToken != null) { jobLockService.releaseLock(lockToken, lockQName); } } } @Override public void afterCommit() { execute(); } }