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
This commit is contained in:
Roy Wetherall
2015-08-07 02:04:51 +00:00
parent 7e61856833
commit 8f2f5258d1
12 changed files with 748 additions and 157 deletions

View File

@@ -259,7 +259,7 @@
<bean id="destroy" class="org.alfresco.module.org_alfresco_module_rm.action.impl.DestroyAction" parent="rmAction"
depends-on="rmDestroyRecordsScheduledForDestructionCapability">
<property name="capabilityService" ref="CapabilityService" />
<property name="eagerContentStoreCleaner" ref="eagerContentStoreCleaner"/>
<property name="contentDestructionComponent" ref="contentDestructionComponent"/>
<property name="ghostingEnabled">
<value>${rm.ghosting.enabled}</value>
</property>

View File

@@ -452,6 +452,12 @@
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.alfresco.test</groupId>
<artifactId>alfresco-testng</artifactId>
<version>1.1</version>
<scope>test</scope>
</dependency>
<!-- Database drivers -->
<dependency>
<groupId>postgresql</groupId>

View File

@@ -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.<QName, Serializable> 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<QName> 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<QName> 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

View File

@@ -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<QName> 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<QName, Serializable> properties = nodeService.getProperties(nodeRef);
// get type properties
Collection<QName> nodeProperties = dictionaryService.getAllProperties(nodeType);
for (QName nodeProperty : nodeProperties)
for (Map.Entry<QName, Serializable> 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());
}
}
}
}

View File

@@ -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" +

View File

@@ -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);
}
};

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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());
}
});
}
}

View File

@@ -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<String, Serializable> params = new HashMap<String, Serializable>(1);
params.put(CompleteEventAction.PARAM_EVENT_NAME, eventName);
// complete event
actionService.executeRecordsManagementAction(disposableItem, CompleteEventAction.NAME, params);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@@ -234,4 +234,9 @@
</list>
</property>
</bean>
<!-- Test content cleanser -->
<bean id="contentCleanser.test" class="org.alfresco.module.org_alfresco_module_rm.test.util.TestContentCleanser"/>
</beans>

View File

@@ -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<QName> contentProperties = new ArrayList<QName>(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<QName, Serializable> propertiesMap = new HashMap<QName, Serializable>(contentPropertiesCount);
for(int i = 0; i < contentPropertiesCount; i++)
{
propertiesMap.put(AlfMock.generateQName(), mockedContentData);
}
when(mockedNodeService.getProperties(nodeRef))
.thenReturn(propertiesMap);
return nodeRef;
}

View File

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