diff --git a/repository/src/main/java/org/alfresco/repo/domain/permissions/FixedAclUpdater.java b/repository/src/main/java/org/alfresco/repo/domain/permissions/FixedAclUpdater.java index 5901108b44..a100f55b74 100644 --- a/repository/src/main/java/org/alfresco/repo/domain/permissions/FixedAclUpdater.java +++ b/repository/src/main/java/org/alfresco/repo/domain/permissions/FixedAclUpdater.java @@ -35,6 +35,8 @@ import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; +import com.google.common.collect.Sets; + import org.alfresco.model.ContentModel; import org.alfresco.repo.batch.BatchProcessWorkProvider; import org.alfresco.repo.batch.BatchProcessor; @@ -83,13 +85,16 @@ public class FixedAclUpdater extends TransactionListenerAdapter implements Appli 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"; + protected static final QName LOCK_Q_NAME = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "FixedAclUpdater"); + + /** A set of listeners to receive callback events whenever permissions are updated by this class. */ + private static Set listeners = Sets.newConcurrentHashSet(); 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; @@ -158,6 +163,12 @@ public class FixedAclUpdater extends TransactionListenerAdapter implements Appli this.policyIgnoreUtil = policyIgnoreUtil; } + /** Register a {@link FixedAclUpdaterListener} to be notified when a node is updated by an instance of this class. */ + public static void registerListener(FixedAclUpdaterListener listener) + { + listeners.add(listener); + } + public void init() { onInheritPermissionsDisabledDelegate = policyComponent @@ -241,7 +252,7 @@ public class FixedAclUpdater extends TransactionListenerAdapter implements Appli } } - private class AclWorker implements BatchProcessor.BatchProcessWorker + protected class AclWorker implements BatchProcessor.BatchProcessWorker { private Set aspects = new HashSet<>(1); @@ -315,6 +326,8 @@ public class FixedAclUpdater extends TransactionListenerAdapter implements Appli e.printStackTrace(); } + listeners.forEach(listener -> listener.permissionsUpdatedAsynchronously(nodeRef)); + if (log.isDebugEnabled()) { log.debug(String.format("Node processed %s", nodeRef)); @@ -330,7 +343,13 @@ public class FixedAclUpdater extends TransactionListenerAdapter implements Appli }; - private class GetNodesWithAspectCallback implements NodeRefQueryCallback + /** Create a new AclWorker. */ + protected AclWorker createAclWorker() + { + return new AclWorker(); + } + + class GetNodesWithAspectCallback implements NodeRefQueryCallback { private List nodes = new ArrayList<>(); private long minNodeId; @@ -421,11 +440,11 @@ public class FixedAclUpdater extends TransactionListenerAdapter implements Appli try { - lockToken = jobLockService.getLock(lockQName, lockTimeToLive, 0, 1); - jobLockService.refreshLock(lockToken, lockQName, lockRefreshTime, jobLockRefreshCallback); + lockToken = jobLockService.getLock(LOCK_Q_NAME, lockTimeToLive, 0, 1); + jobLockService.refreshLock(lockToken, LOCK_Q_NAME, lockRefreshTime, jobLockRefreshCallback); AclWorkProvider provider = new AclWorkProvider(); - AclWorker worker = new AclWorker(); + AclWorker worker = createAclWorker(); BatchProcessor bp = new BatchProcessor<>("FixedAclUpdater", transactionService.getRetryingTransactionHelper(), provider, numThreads, maxItemBatchSize, applicationContext, log, 100); @@ -442,7 +461,7 @@ public class FixedAclUpdater extends TransactionListenerAdapter implements Appli jobLockRefreshCallback.isActive.set(false); if (lockToken != null) { - jobLockService.releaseLock(lockToken, lockQName); + jobLockService.releaseLock(lockToken, LOCK_Q_NAME); } } } diff --git a/repository/src/main/java/org/alfresco/repo/domain/permissions/FixedAclUpdaterListener.java b/repository/src/main/java/org/alfresco/repo/domain/permissions/FixedAclUpdaterListener.java new file mode 100644 index 0000000000..ec986a15f6 --- /dev/null +++ b/repository/src/main/java/org/alfresco/repo/domain/permissions/FixedAclUpdaterListener.java @@ -0,0 +1,35 @@ +/* + * #%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 org.alfresco.service.cmr.repository.NodeRef; + +/** Listener to receive callback events when permissions are asynchronously updated with the {@link FixedAclUpdater}. */ +public interface FixedAclUpdaterListener +{ + /** Callback method for when permissions have been updated by the FixedAclUpdater. */ + void permissionsUpdatedAsynchronously(NodeRef nodeRef); +} diff --git a/repository/src/main/resources/alfresco/repository.properties b/repository/src/main/resources/alfresco/repository.properties index 7f5b92d9c6..3d48b03e6d 100644 --- a/repository/src/main/resources/alfresco/repository.properties +++ b/repository/src/main/resources/alfresco/repository.properties @@ -1206,7 +1206,7 @@ repo.event2.filter.nodeAspects=sys:* repo.event2.filter.childAssocTypes=rn:rendition # Comma separated list of users which should be excluded # Note: username's case-sensitivity depends on the {user.name.caseSensitive} setting -repo.event2.filter.users=System, null +repo.event2.filter.users= # Topic name repo.event2.topic.endpoint=amqp:topic:alfresco.repo.event2 diff --git a/repository/src/test/java/org/alfresco/AllUnitTestsSuite.java b/repository/src/test/java/org/alfresco/AllUnitTestsSuite.java index 7b66418b17..decef229e4 100644 --- a/repository/src/test/java/org/alfresco/AllUnitTestsSuite.java +++ b/repository/src/test/java/org/alfresco/AllUnitTestsSuite.java @@ -170,6 +170,7 @@ import org.junit.runners.Suite; org.alfresco.repo.content.caching.quota.UnlimitedQuotaStrategyTest.class, org.alfresco.repo.content.caching.CachingContentStoreTest.class, org.alfresco.repo.content.caching.ContentCacheImplTest.class, + org.alfresco.repo.domain.permissions.FixedAclUpdaterUnitTest.class, org.alfresco.repo.domain.propval.PropertyTypeConverterTest.class, org.alfresco.repo.search.MLAnaysisModeExpansionTest.class, org.alfresco.repo.search.DocumentNavigatorTest.class, diff --git a/repository/src/test/java/org/alfresco/repo/domain/permissions/FixedAclUpdaterUnitTest.java b/repository/src/test/java/org/alfresco/repo/domain/permissions/FixedAclUpdaterUnitTest.java new file mode 100644 index 0000000000..c249503fd4 --- /dev/null +++ b/repository/src/test/java/org/alfresco/repo/domain/permissions/FixedAclUpdaterUnitTest.java @@ -0,0 +1,108 @@ +/* + * #%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.alfresco.model.ContentModel.TYPE_BASE; +import static org.alfresco.service.cmr.repository.StoreRef.STORE_REF_ARCHIVE_SPACESSTORE; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.openMocks; + +import org.alfresco.repo.domain.node.NodeDAO; +import org.alfresco.repo.domain.permissions.FixedAclUpdater.AclWorker; +import org.alfresco.repo.policy.ClassPolicyDelegate; +import org.alfresco.repo.security.permissions.PermissionServicePolicies.OnInheritPermissionsDisabled; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.util.Pair; +import org.alfresco.util.PolicyIgnoreUtil; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +/** Mock-based unit tests for {@link FixedAclUpdater}. */ +public class FixedAclUpdaterUnitTest +{ + private static final NodeRef NODE_REF = new NodeRef("test://node/ref"); + private static final long NODE_ID = 123L; + private static final NodeRef ARCHIVED_NODE = new NodeRef(STORE_REF_ARCHIVE_SPACESSTORE, "archived"); + + @InjectMocks + private FixedAclUpdater fixedAclUpdater = new FixedAclUpdater(); + /** The inner class under test. */ + private AclWorker aclWorker = fixedAclUpdater.createAclWorker(); + @Mock + private NodeDAO nodeDAO; + @Mock + private AccessControlListDAO accessControlListDAO; + @Mock + private PolicyIgnoreUtil policyIgnoreUtil; + @Mock + private ClassPolicyDelegate onInheritPermissionsDisabledDelegate; + @Mock + private OnInheritPermissionsDisabled onInheritPermissionsDisabled; + /** A pair of mock listeners. */ + @Mock + private FixedAclUpdaterListener listenerA, listenerB; + + @Before + public void setUp() + { + openMocks(this); + + fixedAclUpdater.registerListener(listenerA); + fixedAclUpdater.registerListener(listenerB); + } + + /** Check that when the AclWorker successfully processes a node then the listeners are notified. */ + @Test + public void testListenersNotifiedAboutUpdate() throws Throwable + { + when(nodeDAO.getNodePair(NODE_REF)).thenReturn(new Pair<>(NODE_ID, NODE_REF)); + when(onInheritPermissionsDisabledDelegate.get(TYPE_BASE)).thenReturn(onInheritPermissionsDisabled); + + aclWorker.process(NODE_REF); + + verify(listenerA).permissionsUpdatedAsynchronously(NODE_REF); + verify(listenerB).permissionsUpdatedAsynchronously(NODE_REF); + } + + /** Check that archived nodes get the "Pending ACL" aspect removed without further updates, and the listeners are not notified. */ + @Test + public void testListenersNotNotifiedAboutArchivedNode() throws Throwable + { + when(nodeDAO.getNodePair(ARCHIVED_NODE)).thenReturn(new Pair<>(NODE_ID, ARCHIVED_NODE)); + when(onInheritPermissionsDisabledDelegate.get(TYPE_BASE)).thenReturn(onInheritPermissionsDisabled); + + aclWorker.process(ARCHIVED_NODE); + + verify(accessControlListDAO).removePendingAclAspect(NODE_ID); + verify(listenerA, never()).permissionsUpdatedAsynchronously(any(NodeRef.class)); + verify(listenerB, never()).permissionsUpdatedAsynchronously(any(NodeRef.class)); + } +}