diff --git a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-model-context.xml b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-model-context.xml index a52089bd08..07adb5d9bf 100644 --- a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-model-context.xml +++ b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-model-context.xml @@ -168,7 +168,6 @@ - diff --git a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/hold/HoldServiceImpl.java b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/hold/HoldServiceImpl.java index 643ec24370..e0db4a7330 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/hold/HoldServiceImpl.java +++ b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/hold/HoldServiceImpl.java @@ -645,6 +645,8 @@ public class HoldServiceImpl extends ServiceBaseImpl { if (!nodeService.hasAspect(nodeRef, ASPECT_FROZEN)) { + //set in transaction cache in order not to trigger update policy when adding the aspect + transactionalResourceHelper.getSet(nodeRef).add("frozen"); // add freeze aspect nodeService.addAspect(nodeRef, ASPECT_FROZEN, props); diff --git a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/rma/aspect/FrozenAspect.java b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/rma/aspect/FrozenAspect.java index f5f174c73a..42ce032b8d 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/rma/aspect/FrozenAspect.java +++ b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/model/rma/aspect/FrozenAspect.java @@ -36,9 +36,10 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanService; import org.alfresco.module.org_alfresco_module_rm.freeze.FreezeService; import org.alfresco.module.org_alfresco_module_rm.model.BaseBehaviourBean; +import org.alfresco.repo.content.ContentServicePolicies; +import org.alfresco.repo.copy.CopyServicePolicies; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.Behaviour.NotificationFrequency; import org.alfresco.repo.policy.annotation.Behaviour; @@ -64,22 +65,15 @@ import org.alfresco.service.namespace.QName; public class FrozenAspect extends BaseBehaviourBean implements NodeServicePolicies.BeforeDeleteNodePolicy, NodeServicePolicies.OnAddAspectPolicy, - NodeServicePolicies.OnRemoveAspectPolicy + NodeServicePolicies.OnRemoveAspectPolicy, + NodeServicePolicies.OnUpdatePropertiesPolicy, + NodeServicePolicies.OnMoveNodePolicy, + ContentServicePolicies.OnContentUpdatePolicy, + CopyServicePolicies.BeforeCopyPolicy { - /** file plan service */ - protected FilePlanService filePlanService; - /** freeze service */ protected FreezeService freezeService; - /** - * @param filePlanService file plan service - */ - public void setFilePlanService(FilePlanService filePlanService) - { - this.filePlanService = filePlanService; - } - /** * @param freezeService freeze service */ @@ -101,25 +95,16 @@ public class FrozenAspect extends BaseBehaviourBean ) public void beforeDeleteNode(final NodeRef nodeRef) { - AuthenticationUtil.runAsSystem(new RunAsWork() - { - @Override - public Void doWork() + AuthenticationUtil.runAsSystem((RunAsWork) () -> { + if (nodeService.exists(nodeRef) && freezeService.isFrozen(nodeRef)) { - if (nodeService.exists(nodeRef) && - filePlanService.isFilePlanComponent(nodeRef)) - { - if (freezeService.isFrozen(nodeRef)) - { - // never allowed to delete a frozen node - throw new AccessDeniedException("Frozen nodes can not be deleted."); - } - - // check children - checkChildren(nodeService.getChildAssocs(nodeRef)); - } - return null; + // never allow to delete a frozen node + throw new AccessDeniedException("Frozen nodes can not be deleted."); } + + // check children + checkChildren(nodeService.getChildAssocs(nodeRef)); + return null; }); } @@ -139,7 +124,7 @@ public class FrozenAspect extends BaseBehaviourBean NodeRef nodeRef = assoc.getChildRef(); if (freezeService.isFrozen(nodeRef)) { - // never allowed to delete a node with a frozen child + // never allow to delete a node with a frozen child throw new AccessDeniedException("Can not delete node, because it contains a frozen child node."); } @@ -169,7 +154,8 @@ public class FrozenAspect extends BaseBehaviourBean int currentCount = (Integer) nodeService.getProperty(parentRef, PROP_HELD_CHILDREN_COUNT); currentCount = currentCount + 1; nodeService.setProperty(parentRef, PROP_HELD_CHILDREN_COUNT, currentCount); - } else + } + else { if (instanceOf(parentRef, TYPE_FOLDER) && !nodeService.hasAspect(parentRef, ASPECT_SITE_CONTAINER)) { @@ -193,17 +179,18 @@ public class FrozenAspect extends BaseBehaviourBean public void onRemoveAspect(final NodeRef nodeRef, QName aspectTypeQName) { AuthenticationUtil.runAsSystem((RunAsWork) () -> { + if (nodeService.exists(nodeRef) && (isRecord(nodeRef) || instanceOf(nodeRef, TYPE_CONTENT))) { // get the owning folder - NodeRef owningFolder = nodeService.getPrimaryParent(nodeRef).getParentRef(); + final NodeRef owningFolder = nodeService.getPrimaryParent(nodeRef).getParentRef(); // check that the aspect has been added if (nodeService.hasAspect(owningFolder, ASPECT_HELD_CHILDREN)) { // decrement current count - int currentCount = (Integer) nodeService.getProperty(owningFolder, PROP_HELD_CHILDREN_COUNT); + final int currentCount = (Integer) nodeService.getProperty(owningFolder, PROP_HELD_CHILDREN_COUNT); if (currentCount > 0) { nodeService.setProperty(owningFolder, PROP_HELD_CHILDREN_COUNT, currentCount - 1); @@ -214,4 +201,91 @@ public class FrozenAspect extends BaseBehaviourBean }); } + /** + * Behaviour associated with moving a frozen node + *

+ * Prevent frozen items being moved + */ + @Override + @Behaviour + ( + kind = BehaviourKind.ASSOCIATION, + notificationFrequency = NotificationFrequency.FIRST_EVENT + ) + public void onMoveNode(final ChildAssociationRef oldChildAssocRef, final ChildAssociationRef newChildAssocRef) + { + AuthenticationUtil.runAsSystem((RunAsWork) () -> { + if (nodeService.exists(newChildAssocRef.getParentRef()) && + nodeService.exists(newChildAssocRef.getChildRef())) + { + throw new AccessDeniedException("Frozen nodes can not be moved."); + } + return null; + }); + } + + /** + * Behaviour associated with updating properties + *

+ * Prevents frozen items being updated + */ + @Override + @Behaviour + ( + kind = BehaviourKind.CLASS, + notificationFrequency = NotificationFrequency.TRANSACTION_COMMIT + ) + public void onUpdateProperties(NodeRef nodeRef, Map before, Map after) + + { + AuthenticationUtil.runAsSystem((RunAsWork) () -> { + // check to not throw exception when the aspect is being added + if (nodeService.exists(nodeRef) && freezeService.isFrozen(nodeRef) && + !transactionalResourceHelper.getSet(nodeRef).contains("frozen") ) + { + throw new AccessDeniedException("Frozen nodes can not be updated."); + } + return null; + }); + } + + /** + * Behaviour associated with updating the content + *

+ * Ensures that the content of a frozen node can not be updated + */ + @Override + @Behaviour + ( + kind = BehaviourKind.CLASS, + notificationFrequency = NotificationFrequency.TRANSACTION_COMMIT + ) + public void onContentUpdate(NodeRef nodeRef, boolean newContent) + { + AuthenticationUtil.runAsSystem((RunAsWork) () -> { + if (nodeService.exists(nodeRef) && freezeService.isFrozen(nodeRef)) + { + // never allow to update the content of a frozen node + throw new AccessDeniedException("Frozen nodes content can not be updated."); + } + return null; + }); + } + + @Override + @Behaviour + ( + kind = BehaviourKind.CLASS + ) + public void beforeCopy(QName classRef, NodeRef sourceNodeRef, NodeRef targetNodeRef) + { + AuthenticationUtil.runAsSystem((RunAsWork) () -> { + if (nodeService.exists(sourceNodeRef) && freezeService.isFrozen(sourceNodeRef)) + { + throw new AccessDeniedException("Frozen nodes can not be copied."); + } + + return null; + }); + } } diff --git a/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/hold/RemoveActiveContentToHoldTest.java b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/hold/RemoveActiveContentFromHoldTest.java similarity index 99% rename from rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/hold/RemoveActiveContentToHoldTest.java rename to rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/hold/RemoveActiveContentFromHoldTest.java index 6c597f15d4..8a4181116e 100644 --- a/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/hold/RemoveActiveContentToHoldTest.java +++ b/rm-community/rm-community-repo/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/hold/RemoveActiveContentFromHoldTest.java @@ -42,7 +42,7 @@ import org.springframework.extensions.webscripts.GUID; * @author Ross Gale * @since 3.2 */ -public class RemoveActiveContentToHoldTest extends BaseRMTestCase +public class RemoveActiveContentFromHoldTest extends BaseRMTestCase { @Override protected boolean isCollaborationSiteTest() diff --git a/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/model/rma/aspect/FrozenAspectUnitTest.java b/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/model/rma/aspect/FrozenAspectUnitTest.java index a6ca309f0a..2a4010142a 100644 --- a/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/model/rma/aspect/FrozenAspectUnitTest.java +++ b/rm-community/rm-community-repo/unit-test/java/org/alfresco/module/org_alfresco_module_rm/model/rma/aspect/FrozenAspectUnitTest.java @@ -29,15 +29,25 @@ package org.alfresco.module.org_alfresco_module_rm.model.rma.aspect; import static org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel.ASPECT_HELD_CHILDREN; import static org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel.ASPECT_RECORD; import static org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel.PROP_HELD_CHILDREN_COUNT; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyMap; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + import org.alfresco.model.ContentModel; +import org.alfresco.module.org_alfresco_module_rm.freeze.FreezeService; import org.alfresco.module.org_alfresco_module_rm.util.NodeTypeUtility; +import org.alfresco.module.org_alfresco_module_rm.util.TransactionalResourceHelper; +import org.alfresco.repo.security.permissions.AccessDeniedException; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; import org.junit.Before; import org.junit.Test; import org.mockito.InjectMocks; @@ -64,12 +74,37 @@ public class FrozenAspectUnitTest @Mock private NodeTypeUtility mockedNodeTypeUtility; + @Mock + private FreezeService mockFreezeService; + + @Mock + private TransactionalResourceHelper mockResourceHelper; + + @Mock + private ChildAssociationRef mockChildRef; + + @Mock + private ChildAssociationRef mockParentRef; + + @Mock + private ChildAssociationRef mockOldRef; + + @Mock + private ChildAssociationRef mockNewRef; + + @Mock + private Set mockSet; + @InjectMocks private FrozenAspect frozenAspect; + private final List children = new ArrayList<>(); + private NodeRef record = new NodeRef("workspace://record/node"); private NodeRef folder = new NodeRef("workspace://folder/node"); private NodeRef content = new NodeRef("workspace://content/node"); + private NodeRef child = new NodeRef("workspace://content/child"); + private NodeRef parent = new NodeRef("workspace://content/parent"); @Before public void setUp() @@ -80,6 +115,11 @@ public class FrozenAspectUnitTest when(mockNodeService.hasAspect(folder, ASPECT_HELD_CHILDREN)).thenReturn(true); when(mockNodeService.getProperty(folder, PROP_HELD_CHILDREN_COUNT)).thenReturn(1); when(mockApplicationContext.getBean("dbNodeService")).thenReturn(mockNodeService); + when(mockNodeService.exists(content)).thenReturn(true); + when(mockFreezeService.isFrozen(content)).thenReturn(false); + children.add(mockChildRef); + when(mockNodeService.getChildAssocs(content)).thenReturn(children); + when(mockChildRef.isPrimary()).thenReturn(true); } /** @@ -122,4 +162,111 @@ public class FrozenAspectUnitTest frozenAspect.onRemoveAspect(content, null); verify(mockNodeService, times(0)).setProperty(folder, PROP_HELD_CHILDREN_COUNT, 0); } + + /** + * Test before delete throws an error if a node is frozen + */ + @Test(expected = AccessDeniedException.class) + public void testBeforeDeleteNodeThrowsExceptionIfNodeFrozen() + { + when(mockFreezeService.isFrozen(content)).thenReturn(true); + frozenAspect.beforeDeleteNode(content); + } + + /** + * Test before delete is fine for non-frozen nodes + */ + @Test + public void testBeforeDeleteForNonFrozenNodes() + { + frozenAspect.beforeDeleteNode(content); + verify(mockNodeService, times(1)).getChildAssocs(content); + verify(mockChildRef, times(1)).getChildRef(); + } + + /** + * Test before delete throws an error for a node with frozen children + */ + @Test (expected = AccessDeniedException.class) + public void testBeforeDeleteThrowsExceptionForFrozenChild() + { + when(mockChildRef.getChildRef()).thenReturn(child); + when(mockFreezeService.isFrozen(child)).thenReturn(true); + frozenAspect.beforeDeleteNode(content); + } + + /** + * Test on add aspect for a record node + */ + @Test + public void testOnAddAspectForRecord() + { + when(mockNodeService.getType(record)).thenReturn(ContentModel.TYPE_CONTENT); + when(mockNodeService.getPrimaryParent(record)).thenReturn(mockParentRef); + when(mockParentRef.getParentRef()).thenReturn(parent); + when(mockNodeService.hasAspect(parent, ASPECT_HELD_CHILDREN)).thenReturn(true); + when(mockNodeService.getProperty(parent, PROP_HELD_CHILDREN_COUNT)).thenReturn(0); + frozenAspect.onAddAspect(record,null); + verify(mockNodeService, times(1)).setProperty(parent, PROP_HELD_CHILDREN_COUNT,1); + } + + /** + * Test on add for a content node + */ + @Test + public void testOnAddAspectForContent() + { + when(mockNodeService.getType(record)).thenReturn(ContentModel.TYPE_CONTENT); + when(mockNodeService.getPrimaryParent(record)).thenReturn(mockParentRef); + when(mockParentRef.getParentRef()).thenReturn(parent); + when(mockNodeService.hasAspect(parent, ASPECT_HELD_CHILDREN)).thenReturn(false); + when(mockNodeService.getType(parent)).thenReturn(ContentModel.TYPE_FOLDER); + frozenAspect.onAddAspect(record, null); + verify(mockNodeService, times(1)).addAspect(any(NodeRef.class), any(QName.class), anyMap()); + } + + /** + * Test on move throws an error for a frozen node + */ + @Test(expected = AccessDeniedException.class) + public void testOnMoveThrowsExceptionForFrozenNode() + { + when(mockNewRef.getParentRef()).thenReturn(parent); + when(mockNewRef.getChildRef()).thenReturn(child); + when(mockNodeService.exists(parent)).thenReturn(true); + when(mockNodeService.exists(child)).thenReturn(true); + frozenAspect.onMoveNode(mockOldRef, mockNewRef); + } + + /** + * Test update properties throws an error for frozen nodes + */ + @Test(expected = AccessDeniedException.class) + public void testUpdatePropertiesThrowsExceptionForFrozenNode() + { + when(mockFreezeService.isFrozen(content)).thenReturn(true); + when(mockResourceHelper.getSet(content)).thenReturn(mockSet); + when(mockSet.contains("frozen")).thenReturn(false); + frozenAspect.onUpdateProperties(content, null, null); + } + + /** + * Test on content update throws an error for frozen nodes + */ + @Test(expected = AccessDeniedException.class) + public void testOnContentUpdateThrowsExceptionForFrozenNode() + { + when(mockFreezeService.isFrozen(content)).thenReturn(true); + frozenAspect.onContentUpdate(content, false); + } + + /** + * Test before copy throws an error for frozen node + */ + @Test(expected = AccessDeniedException.class) + public void testBeforeCopyThrowsExceptionForFrozenNode() + { + when(mockFreezeService.isFrozen(content)).thenReturn(true); + frozenAspect.beforeCopy(null, content, null); + } }