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);
}
/**