From 8f2f5258d1fa086720912e88faec386b62cf9ebc Mon Sep 17 00:00:00 2001 From: Roy Wetherall Date: Fri, 7 Aug 2015 02:04:51 +0000 Subject: [PATCH] Acceptance Criteria Automation for RM-1997: Content store data cleansing * added @AlfrescoTest annotation dependency to help track AC's back to JIRA * feedback from previous review * see RM-2460, RM-2461, RM-2462, RM-2505, RM-2506, RM-2507 +review RM git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/modules/recordsmanagement/HEAD@109733 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../rm-action-context.xml | 2 +- rm-server/pom.xml | 6 + .../action/impl/DestroyAction.java | 107 +--- .../content/ContentDestructionComponent.java | 82 ++- .../content/EagerContentStoreCleaner.java | 2 +- .../content/cleanser/ContentCleanser.java | 10 +- .../destroy/DestroyContentTest.java | 552 ++++++++++++++++++ .../test/util/CommonRMTestUtils.java | 17 + .../test/util/TestContentCleanser.java | 56 ++ rm-server/test/resources/test-context.xml | 5 + .../ContentDestructionComponentUnitTest.java | 53 +- .../ContentCleanser522022MUnitTest.java | 13 +- 12 files changed, 748 insertions(+), 157 deletions(-) create mode 100644 rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/destroy/DestroyContentTest.java create mode 100644 rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/TestContentCleanser.java diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-action-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-action-context.xml index f3d6f08996..614cd7ad0c 100644 --- a/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-action-context.xml +++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/rm-action-context.xml @@ -259,7 +259,7 @@ - + ${rm.ghosting.enabled} diff --git a/rm-server/pom.xml b/rm-server/pom.xml index a34d915b1b..5fe24bde67 100644 --- a/rm-server/pom.xml +++ b/rm-server/pom.xml @@ -452,6 +452,12 @@ tests test + + org.alfresco.test + alfresco-testng + 1.1 + test + postgresql diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/DestroyAction.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/DestroyAction.java index 27b0805122..aa82b65efa 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/DestroyAction.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/action/impl/DestroyAction.java @@ -21,19 +21,13 @@ package org.alfresco.module.org_alfresco_module_rm.action.impl; import java.io.Serializable; import java.util.Collections; import java.util.List; -import java.util.Set; -import org.alfresco.model.ContentModel; -import org.alfresco.model.RenditionModel; import org.alfresco.module.org_alfresco_module_rm.action.RMDispositionActionExecuterAbstractBase; import org.alfresco.module.org_alfresco_module_rm.capability.CapabilityService; +import org.alfresco.module.org_alfresco_module_rm.content.ContentDestructionComponent; import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionActionDefinition; import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionSchedule; -import org.alfresco.repo.content.cleanup.EagerContentStoreCleaner; import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.security.AccessStatus; import org.alfresco.service.namespace.QName; @@ -49,9 +43,9 @@ public class DestroyAction extends RMDispositionActionExecuterAbstractBase /** Action name */ public static final String NAME = "destroy"; - /** Eager content store cleaner */ - private EagerContentStoreCleaner eagerContentStoreCleaner; - + /** content destruction component */ + private ContentDestructionComponent contentDestructionComponent; + /** Capability service */ private CapabilityService capabilityService; @@ -59,13 +53,13 @@ public class DestroyAction extends RMDispositionActionExecuterAbstractBase private boolean ghostingEnabled = true; /** - * @param eagerContentStoreCleaner eager content store cleaner + * @param contentDestructionComponent content destruction component */ - public void setEagerContentStoreCleaner(EagerContentStoreCleaner eagerContentStoreCleaner) + public void setContentDestructionComponent(ContentDestructionComponent contentDestructionComponent) { - this.eagerContentStoreCleaner = eagerContentStoreCleaner; + this.contentDestructionComponent = contentDestructionComponent; } - + /** * @param capabilityService capability service */ @@ -128,10 +122,12 @@ public class DestroyAction extends RMDispositionActionExecuterAbstractBase } if (isGhostOnDestroySetForAction(action, recordFolder)) { + // add aspect getNodeService().addAspect(recordFolder, ASPECT_GHOSTED, Collections. emptyMap()); } else { + // just delete the node getNodeService().deleteNode(recordFolder); } } @@ -141,95 +137,22 @@ public class DestroyAction extends RMDispositionActionExecuterAbstractBase */ @Override protected void executeRecordLevelDisposition(Action action, NodeRef record) - { - // Clear the content - clearAllContent(record); - - // Clear thumbnail content - clearThumbnails(record); - + { if (isGhostOnDestroySetForAction(action, record)) { // Add the ghosted aspect getNodeService().addAspect(record, ASPECT_GHOSTED, null); + + // destroy content + contentDestructionComponent.destroyContent(record); } else { - // If ghosting is not enabled, delete the node + // just delete the node getNodeService().deleteNode(record); } } - /** - * Clear all the content properties - * - * @param nodeRef - */ - private void clearAllContent(NodeRef nodeRef) - { - Set props = this.getNodeService().getProperties(nodeRef).keySet(); - props.retainAll(this.getDictionaryService().getAllProperties(DataTypeDefinition.CONTENT)); - for (QName prop : props) - { - // Clear the content - clearContent(nodeRef, prop); - - // Remove the property - this.getNodeService().removeProperty(nodeRef, prop); - } - } - - /** - * Clear all the thumbnail information - * - * @param nodeRef - */ - @SuppressWarnings("deprecation") - private void clearThumbnails(NodeRef nodeRef) - { - // Remove the renditioned aspect (and its properties and associations) if it is present. - // - // From Alfresco 3.3 it is the rn:renditioned aspect which defines the - // child-association being considered in this method. - // Note also that the cm:thumbnailed aspect extends the rn:renditioned aspect. - // - // We want to remove the rn:renditioned aspect, but due to the possibility - // that there is Alfresco 3.2-era data with the cm:thumbnailed aspect - // applied, we must consider removing it too. - if (getNodeService().hasAspect(nodeRef, RenditionModel.ASPECT_RENDITIONED) || - getNodeService().hasAspect(nodeRef, ContentModel.ASPECT_THUMBNAILED)) - { - // Add the ghosted aspect to all the renditioned children, so that they will not be archived when the - // renditioned aspect is removed - Set childAssocTypes = getDictionaryService().getAspect(RenditionModel.ASPECT_RENDITIONED).getChildAssociations().keySet(); - for (ChildAssociationRef child : getNodeService().getChildAssocs(nodeRef)) - { - if (childAssocTypes.contains(child.getTypeQName())) - { - // Clear the content and delete the rendition - clearAllContent(child.getChildRef()); - getNodeService().deleteNode(child.getChildRef()); - } - } - } - } - - /** - * Clear a content property - * - * @param nodeRef - * @param contentProperty - */ - private void clearContent(NodeRef nodeRef, QName contentProperty) - { - // Ensure the content is cleaned at the end of the transaction - ContentData contentData = (ContentData)getNodeService().getProperty(nodeRef, contentProperty); - if (contentData != null && contentData.getContentUrl() != null) - { - eagerContentStoreCleaner.registerOrphanedContentUrl(contentData.getContentUrl(), true); - } - } - /** * Return true if the ghost on destroy property is set against the * definition for the passed action on the specified node diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/ContentDestructionComponent.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/ContentDestructionComponent.java index e840007005..7abc184357 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/ContentDestructionComponent.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/ContentDestructionComponent.java @@ -18,8 +18,12 @@ */ package org.alfresco.module.org_alfresco_module_rm.content; -import java.util.Collection; +import java.io.Serializable; +import java.util.Map; +import java.util.Set; +import org.alfresco.model.ContentModel; +import org.alfresco.model.RenditionModel; import org.alfresco.module.org_alfresco_module_rm.classification.ContentClassificationService; import org.alfresco.module.org_alfresco_module_rm.record.RecordService; import org.alfresco.module.org_alfresco_module_rm.util.AuthenticationUtil; @@ -28,9 +32,8 @@ import org.alfresco.repo.policy.annotation.Behaviour; import org.alfresco.repo.policy.annotation.BehaviourBean; import org.alfresco.repo.policy.annotation.BehaviourKind; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; @@ -162,36 +165,75 @@ public class ContentDestructionComponent implements NodeServicePolicies.BeforeDe recordService.isRecord(nodeRef)) { // then register all content for destruction - registerAllContentForDestruction(nodeRef); + registerAllContentForDestruction(nodeRef, false); } return null; } }); } + /** + * Destroy content + * + * @param nodeRef + */ + public void destroyContent(NodeRef nodeRef) + { + destroyContent(nodeRef, true); + } + + /** + * Destroy content + * + * @param nodeRef + * @param includeRenditions + */ + @SuppressWarnings("deprecation") + public void destroyContent(NodeRef nodeRef, boolean includeRenditions) + { + // destroy the nodes content properties + registerAllContentForDestruction(nodeRef, true); + + // Remove the renditioned aspect (and its properties and associations) if it is present. + // + // From Alfresco 3.3 it is the rn:renditioned aspect which defines the + // child-association being considered in this method. + // Note also that the cm:thumbnailed aspect extends the rn:renditioned aspect. + // + // We want to remove the rn:renditioned aspect, but due to the possibility + // that there is Alfresco 3.2-era data with the cm:thumbnailed aspect + // applied, we must consider removing it too. + if (nodeService.hasAspect(nodeRef, RenditionModel.ASPECT_RENDITIONED) || + nodeService.hasAspect(nodeRef, ContentModel.ASPECT_THUMBNAILED)) + { + // get the rendition assoc types + Set childAssocTypes = dictionaryService.getAspect(RenditionModel.ASPECT_RENDITIONED).getChildAssociations().keySet(); + for (ChildAssociationRef child : nodeService.getChildAssocs(nodeRef)) + { + if (childAssocTypes.contains(child.getTypeQName())) + { + // destroy renditions content + destroyContent(nodeRef, false); + } + } + } + } + /** * Registers all content on the given node for destruction. * * @param nodeRef node reference */ - private void registerAllContentForDestruction(NodeRef nodeRef) + private void registerAllContentForDestruction(NodeRef nodeRef, boolean clearContentProperty) { - // get node type - QName nodeType = nodeService.getType(nodeRef); + Map properties = nodeService.getProperties(nodeRef); - // get type properties - Collection nodeProperties = dictionaryService.getAllProperties(nodeType); - for (QName nodeProperty : nodeProperties) + for (Map.Entry entry : properties.entrySet()) { - // get property definition - PropertyDefinition propertyDefinition = dictionaryService.getProperty(nodeProperty); - - // if content property - if (propertyDefinition != null && - DataTypeDefinition.CONTENT.equals(propertyDefinition.getDataType().getName())) + if (entry.getValue() instanceof ContentData) { // get content data - ContentData dataContent = (ContentData)nodeService.getProperty(nodeRef, nodeProperty); + ContentData dataContent = (ContentData)entry.getValue(); // if enabled cleanse content if (isCleansingEnabled()) @@ -204,6 +246,12 @@ public class ContentDestructionComponent implements NodeServicePolicies.BeforeDe // register for immediate destruction eagerContentStoreCleaner.registerOrphanedContentUrl(dataContent.getContentUrl(), true); } + + // clear the property + if (clearContentProperty) + { + nodeService.removeProperty(nodeRef, entry.getKey()); + } } } } diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/EagerContentStoreCleaner.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/EagerContentStoreCleaner.java index d1c5648bb1..6f160a3bb5 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/EagerContentStoreCleaner.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/EagerContentStoreCleaner.java @@ -140,7 +140,7 @@ public class EagerContentStoreCleaner extends org.alfresco.repo.content.cleanup. contentCleanser.cleanse(file); } } - catch (Throwable e) + catch (Exception e) { logger.error( "Content cleansing failed: \n" + diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/cleanser/ContentCleanser.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/cleanser/ContentCleanser.java index 61750cf2d0..89381f1e97 100644 --- a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/cleanser/ContentCleanser.java +++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/cleanser/ContentCleanser.java @@ -53,8 +53,7 @@ public abstract class ContentCleanser try { // get an output stream - OutputStream os = new BufferedOutputStream(new FileOutputStream(file)); - try + try (OutputStream os = new BufferedOutputStream(new FileOutputStream(file))) { for (int i = 0; i < bytes; i++) { @@ -62,11 +61,6 @@ public abstract class ContentCleanser overwriteOperation.operation(os); } } - finally - { - // close ouput stream - try {os.close(); } catch (Throwable e) {} - } } catch (IOException ioException) { @@ -101,7 +95,7 @@ public abstract class ContentCleanser { public void operation(OutputStream os) throws IOException { - os.write(1); + os.write(0xff); } }; diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/destroy/DestroyContentTest.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/destroy/DestroyContentTest.java new file mode 100644 index 0000000000..29808b932a --- /dev/null +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/integration/destroy/DestroyContentTest.java @@ -0,0 +1,552 @@ +/* + * Copyright (C) 2005-2015 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.module.org_alfresco_module_rm.test.integration.destroy; + +import java.util.Collections; + +import org.alfresco.module.org_alfresco_module_rm.action.impl.CutOffAction; +import org.alfresco.module.org_alfresco_module_rm.action.impl.DestroyAction; +import org.alfresco.module.org_alfresco_module_rm.classification.ClassificationAspectProperties; +import org.alfresco.module.org_alfresco_module_rm.content.ContentDestructionComponent; +import org.alfresco.module.org_alfresco_module_rm.content.EagerContentStoreCleaner; +import org.alfresco.module.org_alfresco_module_rm.test.util.BaseRMTestCase; +import org.alfresco.module.org_alfresco_module_rm.test.util.CommonRMTestUtils; +import org.alfresco.module.org_alfresco_module_rm.test.util.TestContentCleanser; +import org.alfresco.repo.content.ContentStore; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.test.AlfrescoTest; +import org.alfresco.util.GUID; + +/** + * Acceptance criteria for content destruction and content cleansing. + * + * @author Roy Wetherall + * @Author 3.0.a + */ +public class DestroyContentTest extends BaseRMTestCase +{ + private static final String BEAN_NAME_CONTENT_CLEANSER = "contentCleanser.test"; + + private ContentStore contentStore; + private TestContentCleanser contentCleanser; + private EagerContentStoreCleaner eagerContentStoreCleaner; + private ContentDestructionComponent contentDestructionComponent; + + @Override + protected void initServices() + { + super.initServices(); + contentStore = (ContentStore)applicationContext.getBean("fileContentStore"); + contentCleanser = (TestContentCleanser)applicationContext.getBean(BEAN_NAME_CONTENT_CLEANSER); + eagerContentStoreCleaner = (EagerContentStoreCleaner)applicationContext.getBean("eagerContentStoreCleaner"); + contentDestructionComponent = (ContentDestructionComponent)applicationContext.getBean("contentDestructionComponent"); + + // set the test content store cleaner + eagerContentStoreCleaner.setContentCleanser(contentCleanser); + } + + /** + * Given that a record folder is eligible for destruction + * And record ghosting is applied + * When the record folder is destroyed + * Then the record folder and records are ghosted + * And the content is destroyed + */ + @AlfrescoTest (jira="RM-2506") + public void testRecordFolderDestroy() throws Exception + { + doBehaviourDrivenTest(new BehaviourDrivenTest() + { + private NodeRef recordCategoryFolderLevel; + private NodeRef destroyableFolder; + private NodeRef subRecord; + + public void given() throws Exception + { + // create destroyable record folder that contains a record + recordCategoryFolderLevel = filePlanService.createRecordCategory(filePlan, GUID.generate()); + utils.createBasicDispositionSchedule( + recordCategoryFolderLevel, + CommonRMTestUtils.DEFAULT_DISPOSITION_INSTRUCTIONS, + CommonRMTestUtils.DEFAULT_DISPOSITION_AUTHORITY, + false, + true); + destroyableFolder = recordFolderService.createRecordFolder(recordCategoryFolderLevel, GUID.generate()); + subRecord = utils.createRecord(destroyableFolder, GUID.generate(), GUID.generate()); + utils.completeRecord(subRecord); + utils.completeEvent(destroyableFolder, CommonRMTestUtils.DEFAULT_EVENT_NAME); + rmActionService.executeRecordsManagementAction(destroyableFolder, CutOffAction.NAME); + + // assert things are as we expect + assertEquals(DestroyAction.NAME, dispositionService.getNextDispositionAction(destroyableFolder).getName()); + assertTrue(dispositionService.isNextDispositionActionEligible(destroyableFolder)); + + // reset test content cleanser + contentCleanser.reset(); + assertFalse(contentDestructionComponent.isCleansingEnabled()); + } + + public void when() throws Exception + { + // destroy the folder + rmActionService.executeRecordsManagementAction(destroyableFolder, DestroyAction.NAME); + } + + public void then() throws Exception + { + // folder and record exist and are ghosted + assertTrue(nodeService.exists(destroyableFolder)); + assertTrue(nodeService.hasAspect(destroyableFolder, ASPECT_GHOSTED)); + assertTrue(nodeService.exists(subRecord)); + assertTrue(nodeService.hasAspect(subRecord, ASPECT_GHOSTED)); + + // record content is destroyed + ContentReader reader = contentService.getReader(subRecord, PROP_CONTENT); + assertNull(reader); + + // content cleansing hasn't taken place + assertFalse(contentCleanser.hasCleansed()); + } + }); + } + + /** + * Given that a record is eligible for destruction + * And record ghosting is applied + * When the record is destroyed + * Then the record is ghosted + * And the content is destroyed + */ + @AlfrescoTest (jira="RM-2506") + public void testRecordDestroy() throws Exception + { + doBehaviourDrivenTest(new BehaviourDrivenTest() + { + private NodeRef recordCategoryRecordLevel; + private NodeRef recordFolder; + private NodeRef destroyableRecord; + + public void given() throws Exception + { + // create destroyable record + recordCategoryRecordLevel = filePlanService.createRecordCategory(filePlan, GUID.generate()); + utils.createBasicDispositionSchedule( + recordCategoryRecordLevel, + CommonRMTestUtils.DEFAULT_DISPOSITION_INSTRUCTIONS, + CommonRMTestUtils.DEFAULT_DISPOSITION_AUTHORITY, + true, + true); + recordFolder = recordFolderService.createRecordFolder(recordCategoryRecordLevel, GUID.generate()); + destroyableRecord = utils.createRecord(recordFolder, GUID.generate(), GUID.generate()); + utils.completeRecord(destroyableRecord); + utils.completeEvent(destroyableRecord, CommonRMTestUtils.DEFAULT_EVENT_NAME); + rmActionService.executeRecordsManagementAction(destroyableRecord, CutOffAction.NAME); + + // assert things are as we expect + assertEquals(DestroyAction.NAME, dispositionService.getNextDispositionAction(destroyableRecord).getName()); + assertTrue(dispositionService.isNextDispositionActionEligible(destroyableRecord)); + + // reset test content cleanser + contentCleanser.reset(); + assertFalse(contentDestructionComponent.isCleansingEnabled()); + } + + public void when() throws Exception + { + // destroy the folder + rmActionService.executeRecordsManagementAction(destroyableRecord, DestroyAction.NAME); + } + + public void then() throws Exception + { + // show that record still exists and has the ghosted aspect applied + assertTrue(nodeService.exists(destroyableRecord)); + assertTrue(nodeService.hasAspect(destroyableRecord, ASPECT_GHOSTED)); + + // record content is destroyed + ContentReader reader = contentService.getReader(destroyableRecord, PROP_CONTENT); + assertNull(reader); + + // content cleansing hasn't taken place + assertFalse(contentCleanser.hasCleansed()); + } + }); + } + + /** + * Given that a record is eligible for destruction + * And record ghosting is applied + * And cleansing is configured on + * When the record is destroyed + * Then the record is ghosted + * And the content is cleansed + * And then content is destroyed + */ + @AlfrescoTest (jira="RM-2505") + public void testRecordDestroyAndCleanse() throws Exception + { + doBehaviourDrivenTest(new BehaviourDrivenTest() + { + private NodeRef recordCategoryRecordLevel; + private NodeRef recordFolder; + private NodeRef destroyableRecord; + + public void given() throws Exception + { + // create destroyable record + recordCategoryRecordLevel = filePlanService.createRecordCategory(filePlan, GUID.generate()); + utils.createBasicDispositionSchedule( + recordCategoryRecordLevel, + CommonRMTestUtils.DEFAULT_DISPOSITION_INSTRUCTIONS, + CommonRMTestUtils.DEFAULT_DISPOSITION_AUTHORITY, + true, + true); + recordFolder = recordFolderService.createRecordFolder(recordCategoryRecordLevel, GUID.generate()); + destroyableRecord = utils.createRecord(recordFolder, GUID.generate(), GUID.generate()); + utils.completeRecord(destroyableRecord); + utils.completeEvent(destroyableRecord, CommonRMTestUtils.DEFAULT_EVENT_NAME); + rmActionService.executeRecordsManagementAction(destroyableRecord, CutOffAction.NAME); + + // assert things are as we expect + assertEquals(DestroyAction.NAME, dispositionService.getNextDispositionAction(destroyableRecord).getName()); + assertTrue(dispositionService.isNextDispositionActionEligible(destroyableRecord)); + + // reset test content cleanser and configure on + contentCleanser.reset(); + contentDestructionComponent.setCleansingEnabled(true); + assertTrue(contentDestructionComponent.isCleansingEnabled()); + } + + public void when() throws Exception + { + // destroy the folder + rmActionService.executeRecordsManagementAction(destroyableRecord, DestroyAction.NAME); + } + + public void then() throws Exception + { + // show that record still exists and has the ghosted aspect applied + assertTrue(nodeService.exists(destroyableRecord)); + assertTrue(nodeService.hasAspect(destroyableRecord, ASPECT_GHOSTED)); + + // record content is destroyed + ContentReader reader = contentService.getReader(destroyableRecord, PROP_CONTENT); + assertNull(reader); + + // content cleansing has taken place + assertTrue(contentCleanser.hasCleansed()); + } + + public void after() throws Exception + { + // reset cleansing to default + contentDestructionComponent.setCleansingEnabled(false); + } + }); + } + + /** + * When the a record is deleted + * Then the content is destroyed + */ + @AlfrescoTest (jira="RM-2461") + public void testRecordDelete() throws Exception + { + doBehaviourDrivenTest(new BehaviourDrivenTest() + { + private NodeRef recordCategoryRecordLevel; + private NodeRef recordFolder; + private NodeRef deleteableRecord; + private ContentData contentData; + + public void given() throws Exception + { + // create destroyable record + recordCategoryRecordLevel = filePlanService.createRecordCategory(filePlan, GUID.generate()); + recordFolder = recordFolderService.createRecordFolder(recordCategoryRecordLevel, GUID.generate()); + deleteableRecord = utils.createRecord(recordFolder, GUID.generate(), GUID.generate()); + contentData = (ContentData)nodeService.getProperty(deleteableRecord, PROP_CONTENT); + + // assert things are as we expect + assertNotNull(contentData); + assertTrue(contentStore.exists(contentData.getContentUrl())); + + // reset test content cleanser + contentCleanser.reset(); + assertFalse(contentDestructionComponent.isCleansingEnabled()); + } + + public void when() throws Exception + { + // delete the record + nodeService.deleteNode(deleteableRecord); + } + + public void then() throws Exception + { + // record destroyed + assertFalse(nodeService.exists(deleteableRecord)); + assertFalse(contentStore.exists(contentData.getContentUrl())); + + // content cleansing hasn't taken place + assertFalse(contentCleanser.hasCleansed()); + } + }); + } + + /** + * Given cleansing is configured on + * When the a record is deleted + * Then the content is cleansed + * And then the content is destroyed + */ + @AlfrescoTest (jira="RM-2460") + public void testRecordDeleteAndCleanse() throws Exception + { + doBehaviourDrivenTest(new BehaviourDrivenTest() + { + private NodeRef recordCategoryRecordLevel; + private NodeRef recordFolder; + private NodeRef deleteableRecord; + private ContentData contentData; + + public void given() throws Exception + { + // create destroyable record + recordCategoryRecordLevel = filePlanService.createRecordCategory(filePlan, GUID.generate()); + recordFolder = recordFolderService.createRecordFolder(recordCategoryRecordLevel, GUID.generate()); + deleteableRecord = utils.createRecord(recordFolder, GUID.generate(), GUID.generate()); + contentData = (ContentData)nodeService.getProperty(deleteableRecord, PROP_CONTENT); + + // assert things are as we expect + assertNotNull(contentData); + assertTrue(contentStore.exists(contentData.getContentUrl())); + + // reset test content cleanser and configure on + contentCleanser.reset(); + contentDestructionComponent.setCleansingEnabled(true); + assertTrue(contentDestructionComponent.isCleansingEnabled()); + } + + public void when() throws Exception + { + // delete the record + nodeService.deleteNode(deleteableRecord); + } + + public void then() throws Exception + { + // record destroyed + assertFalse(nodeService.exists(deleteableRecord)); + assertFalse(contentStore.exists(contentData.getContentUrl())); + + // content cleansing has taken place + assertTrue(contentCleanser.hasCleansed()); + } + + public void after() throws Exception + { + // reset cleansing to default + contentDestructionComponent.setCleansingEnabled(false); + } + }); + } + + /** + * When classified content (non-record) is deleted + * Then it is destroyed + */ + @AlfrescoTest (jira="RM-2461") + public void testClassifiedContentDelete() throws Exception + { + doBehaviourDrivenTest(new BehaviourDrivenTest() + { + private NodeRef deleteableContent; + private ContentData contentData; + + public void given() throws Exception + { + // create deletable classified content + assertTrue(nodeService.exists(folder)); + deleteableContent = fileFolderService.create(folder, "myDocument.txt", TYPE_CONTENT).getNodeRef(); + ContentWriter writer = fileFolderService.getWriter(deleteableContent); + writer.setEncoding("UTF-8"); + writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + writer.putContent(GUID.generate()); + + // classify the content + ClassificationAspectProperties properties = new ClassificationAspectProperties(); + properties.setClassificationLevelId("level1"); + properties.setClassifiedBy("me"); + properties.setClassificationReasonIds(Collections.singleton("Test Reason 1")); + contentClassificationService.classifyContent(properties, deleteableContent); + + // grab the content data + contentData = (ContentData)nodeService.getProperty(deleteableContent, PROP_CONTENT); + + // assert things are as we expect + assertNotNull(contentData); + assertTrue(contentStore.exists(contentData.getContentUrl())); + + // reset test content cleanser + contentCleanser.reset(); + assertFalse(contentDestructionComponent.isCleansingEnabled()); + } + + public void when() throws Exception + { + // delete the content + nodeService.deleteNode(deleteableContent); + } + + public void then() throws Exception + { + // content destroyed + assertFalse(nodeService.exists(deleteableContent)); + assertFalse(contentStore.exists(contentData.getContentUrl())); + + // content cleansing hasn't taken place + assertFalse(contentCleanser.hasCleansed()); + } + }); + } + + /** + * Given data cleansing is configured on + * When classified content (non-record) is deleted + * Then it is cleansed + * And then it is destroyed + */ + @AlfrescoTest (jira="RM-2460") + public void testClassifiedContentDeleteAndCleanse() throws Exception + { + doBehaviourDrivenTest(new BehaviourDrivenTest() + { + private NodeRef deleteableContent; + private ContentData contentData; + + public void given() throws Exception + { + // create deletable classified content + assertTrue(nodeService.exists(folder)); + deleteableContent = fileFolderService.create(folder, "myDocument.txt", TYPE_CONTENT).getNodeRef(); + ContentWriter writer = fileFolderService.getWriter(deleteableContent); + writer.setEncoding("UTF-8"); + writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + writer.putContent(GUID.generate()); + + // classify the content + ClassificationAspectProperties properties = new ClassificationAspectProperties(); + properties.setClassificationLevelId("level1"); + properties.setClassifiedBy("me"); + properties.setClassificationReasonIds(Collections.singleton("Test Reason 1")); + contentClassificationService.classifyContent(properties, deleteableContent); + + // grab the content data + contentData = (ContentData)nodeService.getProperty(deleteableContent, PROP_CONTENT); + + // assert things are as we expect + assertNotNull(contentData); + assertTrue(contentStore.exists(contentData.getContentUrl())); + + // reset test content cleanser and configure on + contentCleanser.reset(); + contentDestructionComponent.setCleansingEnabled(true); + assertTrue(contentDestructionComponent.isCleansingEnabled()); + } + + public void when() throws Exception + { + // delete the content + nodeService.deleteNode(deleteableContent); + } + + public void then() throws Exception + { + // content destroyed + assertFalse(nodeService.exists(deleteableContent)); + assertFalse(contentStore.exists(contentData.getContentUrl())); + + // content cleansing has taken place + assertTrue(contentCleanser.hasCleansed()); + } + + public void after() throws Exception + { + // reset cleansing to default + contentDestructionComponent.setCleansingEnabled(false); + } + }); + } + + /** + * When a unclassified document (non-record) is deleted + * Then it is deleted but the the content is not immediately destroyed + */ + @AlfrescoTest (jira="RM-2507") + public void testContentDelete() throws Exception + { + doBehaviourDrivenTest(new BehaviourDrivenTest() + { + private NodeRef deleteableContent; + private ContentData contentData; + + public void given() throws Exception + { + // create deletable content + assertTrue(nodeService.exists(folder)); + deleteableContent = fileFolderService.create(folder, "myDocument.txt", TYPE_CONTENT).getNodeRef(); + ContentWriter writer = fileFolderService.getWriter(deleteableContent); + writer.setEncoding("UTF-8"); + writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + writer.putContent(GUID.generate()); + + contentData = (ContentData)nodeService.getProperty(deleteableContent, PROP_CONTENT); + + // assert things are as we expect + assertNotNull(contentData); + assertTrue(contentStore.exists(contentData.getContentUrl())); + + // reset test content cleanser + contentCleanser.reset(); + assertFalse(contentDestructionComponent.isCleansingEnabled()); + } + + public void when() throws Exception + { + // delete the content + nodeService.deleteNode(deleteableContent); + } + + public void then() throws Exception + { + // content deleted but not destroyed + assertFalse(nodeService.exists(deleteableContent)); + assertTrue(contentStore.exists(contentData.getContentUrl())); + + // content cleansing hasn't taken place + assertFalse(contentCleanser.hasCleansed()); + } + }); + } +} diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/CommonRMTestUtils.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/CommonRMTestUtils.java index 51d8f9dfa8..c9abd8935e 100644 --- a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/CommonRMTestUtils.java +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/CommonRMTestUtils.java @@ -30,6 +30,7 @@ import java.util.Set; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.module.org_alfresco_module_rm.action.RecordsManagementActionService; +import org.alfresco.module.org_alfresco_module_rm.action.impl.CompleteEventAction; import org.alfresco.module.org_alfresco_module_rm.action.impl.CutOffAction; import org.alfresco.module.org_alfresco_module_rm.action.impl.DestroyAction; import org.alfresco.module.org_alfresco_module_rm.action.impl.TransferAction; @@ -292,4 +293,20 @@ public class CommonRMTestUtils implements RecordsManagementModel return filePlanRoleService.createRole(filePlan, roleName, roleName, capabilities); } + + /** + * Helper method to complete event on disposable item + * + * @param disposableItem disposable item (record or record folder) + * @param eventName event name + */ + public void completeEvent(NodeRef disposableItem, String eventName) + { + // build action properties + Map params = new HashMap(1); + params.put(CompleteEventAction.PARAM_EVENT_NAME, eventName); + + // complete event + actionService.executeRecordsManagementAction(disposableItem, CompleteEventAction.NAME, params); + } } diff --git a/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/TestContentCleanser.java b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/TestContentCleanser.java new file mode 100644 index 0000000000..988af69a3e --- /dev/null +++ b/rm-server/test/java/org/alfresco/module/org_alfresco_module_rm/test/util/TestContentCleanser.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2005-2015 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.module.org_alfresco_module_rm.test.util; + +import java.io.File; + +import org.alfresco.module.org_alfresco_module_rm.content.cleanser.ContentCleanser522022M; + +/** + * Test Content Cleanser + * + * @author Roy Wetherall + * @since 3.0.a + */ +public class TestContentCleanser extends ContentCleanser522022M +{ + private boolean hasCleansed = false; + + public void reset() + { + hasCleansed = false; + } + + public boolean hasCleansed() + { + return hasCleansed; + } + + /** + * @see org.alfresco.module.org_alfresco_module_rm.content.cleanser.ContentCleanser#cleanse(java.io.File) + */ + @Override + public void cleanse(File file) + { + hasCleansed = false; + super.cleanse(file); + hasCleansed = true; + } + +} diff --git a/rm-server/test/resources/test-context.xml b/rm-server/test/resources/test-context.xml index f5e35faf58..638686efda 100644 --- a/rm-server/test/resources/test-context.xml +++ b/rm-server/test/resources/test-context.xml @@ -234,4 +234,9 @@ + + + + + \ No newline at end of file diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/content/ContentDestructionComponentUnitTest.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/content/ContentDestructionComponentUnitTest.java index 2abe883419..96ca9fe424 100644 --- a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/content/ContentDestructionComponentUnitTest.java +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/content/ContentDestructionComponentUnitTest.java @@ -18,24 +18,20 @@ */ package org.alfresco.module.org_alfresco_module_rm.content; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; -import java.util.ArrayList; -import java.util.List; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; -import org.alfresco.model.ContentModel; import org.alfresco.module.org_alfresco_module_rm.classification.ContentClassificationService; import org.alfresco.module.org_alfresco_module_rm.content.cleanser.ContentCleanser; import org.alfresco.module.org_alfresco_module_rm.test.util.AlfMock; import org.alfresco.module.org_alfresco_module_rm.test.util.BaseUnitTest; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.namespace.QName; @@ -79,8 +75,10 @@ public class ContentDestructionComponentUnitTest extends BaseUnitTest /** * Given a record + * And that by default cleansing is off * When it is deleted * Then it is sent for immediate destruction + * And not cleansed */ @Test public void deleteRecord() @@ -100,8 +98,10 @@ public class ContentDestructionComponentUnitTest extends BaseUnitTest /** * Given classified content + * And that by default cleansing is off * When it is deleted * Then it is send for immediate destruction + * And not cleansed */ @Test public void deleteClassifiedContent() @@ -122,7 +122,7 @@ public class ContentDestructionComponentUnitTest extends BaseUnitTest /** * Given that content cleansing is turned on * When a sensitive node is deleted - * Then it is not scheduled for cleansing before destruction + * Then it is scheduled for cleansing before destruction */ @Test public void contentCleansingOn() @@ -144,7 +144,7 @@ public class ContentDestructionComponentUnitTest extends BaseUnitTest /** * Given that content cleansing is turned off * When a sensitive node is deleted - * Then it is scheduled for cleansing before destruction + * Then it is not scheduled for cleansing before destruction */ @Test public void contentCleansingOff() @@ -202,31 +202,18 @@ public class ContentDestructionComponentUnitTest extends BaseUnitTest { NodeRef nodeRef = generateCmContent("myContent.txt"); - List contentProperties = new ArrayList(contentPropertiesCount); - for (int i = 0; i < contentPropertiesCount; i++) - { - contentProperties.add(AlfMock.generateQName()); - } - - when(mockedDictionaryService.getAllProperties(ContentModel.TYPE_CONTENT)) - .thenReturn(contentProperties); - - DataTypeDefinition mockedDataTypeDefinition = mock(DataTypeDefinition.class); - when(mockedDataTypeDefinition.getName()) - .thenReturn(DataTypeDefinition.CONTENT); - - PropertyDefinition mockedPropertyDefinition = mock(PropertyDefinition.class); - when(mockedPropertyDefinition.getDataType()) - .thenReturn(mockedDataTypeDefinition); - - when(mockedDictionaryService.getProperty(any(QName.class))) - .thenReturn(mockedPropertyDefinition); - - ContentData mockedDataContent = mock(ContentData.class); - when(mockedDataContent.getContentUrl()) + ContentData mockedContentData = mock(ContentData.class); + when(mockedContentData.getContentUrl()) .thenReturn(contentURL); - when(mockedNodeService.getProperty(eq(nodeRef), any(QName.class))) - .thenReturn(mockedDataContent); + + Map propertiesMap = new HashMap(contentPropertiesCount); + for(int i = 0; i < contentPropertiesCount; i++) + { + propertiesMap.put(AlfMock.generateQName(), mockedContentData); + } + + when(mockedNodeService.getProperties(nodeRef)) + .thenReturn(propertiesMap); return nodeRef; } diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/content/cleanser/ContentCleanser522022MUnitTest.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/content/cleanser/ContentCleanser522022MUnitTest.java index e8098475c5..b661b01703 100644 --- a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/content/cleanser/ContentCleanser522022MUnitTest.java +++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/content/cleanser/ContentCleanser522022MUnitTest.java @@ -18,7 +18,7 @@ */ package org.alfresco.module.org_alfresco_module_rm.content.cleanser; -import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.when; import java.io.File; @@ -26,6 +26,7 @@ import java.io.File; import org.alfresco.module.org_alfresco_module_rm.test.util.BaseUnitTest; import org.alfresco.service.cmr.repository.ContentIOException; import org.junit.Test; +import org.mockito.InOrder; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Spy; @@ -61,12 +62,14 @@ public class ContentCleanser522022MUnitTest extends BaseUnitTest contentCleanser522022M.cleanse(mockedFile); - verify(contentCleanser522022M) + InOrder inOrder = inOrder(contentCleanser522022M); + + inOrder.verify(contentCleanser522022M) .overwrite(mockedFile, contentCleanser522022M.overwriteOnes); - verify(contentCleanser522022M) + inOrder.verify(contentCleanser522022M) .overwrite(mockedFile, contentCleanser522022M.overwriteZeros); - verify(contentCleanser522022M) - .overwrite(mockedFile, contentCleanser522022M.overwriteOnes); + inOrder.verify(contentCleanser522022M) + .overwrite(mockedFile, contentCleanser522022M.overwriteRandom); } /**