diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/alfresco-global.properties b/rm-server/config/alfresco/module/org_alfresco_module_rm/alfresco-global.properties
index d1b78c753a..6a22542489 100644
--- a/rm-server/config/alfresco/module/org_alfresco_module_rm/alfresco-global.properties
+++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/alfresco-global.properties
@@ -67,3 +67,9 @@ rm.classification.levelsFile=/alfresco/module/org_alfresco_module_rm/classificat
rm.classification.reasonsFile=/alfresco/module/org_alfresco_module_rm/classification/rm-classification-reasons.json
# The location of the exemption categories configuration file (relative to the classpath).
rm.classification.exemptionCategoriesFile=/alfresco/module/org_alfresco_module_rm/classification/rm-exemption-categories.json
+
+#
+# Content cleansing
+#
+rm.content.cleansing.enabled=false
+rm.content.cleaner=contentCleanser.522022M
diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/content-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/content-context.xml
new file mode 100644
index 0000000000..4e888f2697
--- /dev/null
+++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/content-context.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/rm-server/config/alfresco/module/org_alfresco_module_rm/module-context.xml b/rm-server/config/alfresco/module/org_alfresco_module_rm/module-context.xml
index 8df31092a8..9e2d78befd 100644
--- a/rm-server/config/alfresco/module/org_alfresco_module_rm/module-context.xml
+++ b/rm-server/config/alfresco/module/org_alfresco_module_rm/module-context.xml
@@ -258,5 +258,8 @@
+
+
+
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
new file mode 100644
index 0000000000..e840007005
--- /dev/null
+++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/ContentDestructionComponent.java
@@ -0,0 +1,210 @@
+/*
+ * 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.content;
+
+import java.util.Collection;
+
+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;
+import org.alfresco.repo.node.NodeServicePolicies;
+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.ContentData;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.namespace.QName;
+
+/**
+ * Content destruction component.
+ *
+ * Listens for the destruction of sensitive nodes (classified content and records) and schedules
+ * all their content for immediate destruction.
+ *
+ * If enabled, the content is also cleansed before destruction.
+ *
+ * @author Roy Wetherall
+ * @since 3.0.a
+ */
+@BehaviourBean
+public class ContentDestructionComponent implements NodeServicePolicies.BeforeDeleteNodePolicy
+{
+ /** authentication utils */
+ private AuthenticationUtil authenticationUtil;
+
+ /** content classification service */
+ private ContentClassificationService contentClassificationService;
+
+ /** record service */
+ private RecordService recordService;
+
+ /** eager content store cleaner */
+ private EagerContentStoreCleaner eagerContentStoreCleaner;
+
+ /** dictionary service */
+ private DictionaryService dictionaryService;
+
+ /** node service */
+ private NodeService nodeService;
+
+ /** indicates whether cleansing is enabled or not */
+ private boolean cleansingEnabled = false;
+
+ /**
+ * @param authenticationUtil authentication utils
+ */
+ public void setAuthenticationUtil(AuthenticationUtil authenticationUtil)
+ {
+ this.authenticationUtil = authenticationUtil;
+ }
+
+ /**
+ * @param contentClassificationService content classification service
+ */
+ public void setContentClassificationService(ContentClassificationService contentClassificationService)
+ {
+ this.contentClassificationService = contentClassificationService;
+ }
+
+ /**
+ * @param recordService record service
+ */
+ public void setRecordService(RecordService recordService)
+ {
+ this.recordService = recordService;
+ }
+
+ /**
+ * @param eagerContentStoreCleaner eager content store cleaner
+ */
+ public void setEagerContentStoreCleaner(EagerContentStoreCleaner eagerContentStoreCleaner)
+ {
+ this.eagerContentStoreCleaner = eagerContentStoreCleaner;
+ }
+
+ /**
+ * @param dictionaryService dictionary service
+ */
+ public void setDictionaryService(DictionaryService dictionaryService)
+ {
+ this.dictionaryService = dictionaryService;
+ }
+
+ /**
+ * @param nodeService node service
+ */
+ public void setNodeService(NodeService nodeService)
+ {
+ this.nodeService = nodeService;
+ }
+
+ /**
+ * @param cleansingEnabled true if cleansing enabled, false otherwise
+ */
+ public void setCleansingEnabled(boolean cleansingEnabled)
+ {
+ this.cleansingEnabled = cleansingEnabled;
+ }
+
+ /**
+ * @return true if cleansing is enabled, false otherwise
+ */
+ public boolean isCleansingEnabled()
+ {
+ return cleansingEnabled;
+ }
+
+ /**
+ * System behaviour implementation that listens for sensitive nodes
+ * and schedules them for immediate destruction.
+ *
+ * Note that the content destruction and cleansing takes place on transaction
+ * commit. If the transaction is rolled back after this behaviour is encountered
+ * then the content will not be destroyed or cleansed.
+ *
+ * @param nodeRef node reference about to be deleted
+ */
+ @Override
+ @Behaviour
+ (
+ isService = true,
+ kind = BehaviourKind.CLASS
+ )
+ public void beforeDeleteNode(final NodeRef nodeRef)
+ {
+ authenticationUtil.runAsSystem(new RunAsWork()
+ {
+ public Void doWork() throws Exception
+ {
+ // if enable and content is classified or a record
+ if (contentClassificationService.isClassified(nodeRef) ||
+ recordService.isRecord(nodeRef))
+ {
+ // then register all content for destruction
+ registerAllContentForDestruction(nodeRef);
+ }
+ return null;
+ }
+ });
+ }
+
+ /**
+ * Registers all content on the given node for destruction.
+ *
+ * @param nodeRef node reference
+ */
+ private void registerAllContentForDestruction(NodeRef nodeRef)
+ {
+ // get node type
+ QName nodeType = nodeService.getType(nodeRef);
+
+ // get type properties
+ Collection nodeProperties = dictionaryService.getAllProperties(nodeType);
+ for (QName nodeProperty : nodeProperties)
+ {
+ // get property definition
+ PropertyDefinition propertyDefinition = dictionaryService.getProperty(nodeProperty);
+
+ // if content property
+ if (propertyDefinition != null &&
+ DataTypeDefinition.CONTENT.equals(propertyDefinition.getDataType().getName()))
+ {
+ // get content data
+ ContentData dataContent = (ContentData)nodeService.getProperty(nodeRef, nodeProperty);
+
+ // if enabled cleanse content
+ if (isCleansingEnabled())
+ {
+ // register for cleanse then immediate destruction
+ eagerContentStoreCleaner.registerOrphanedContentUrlForCleansing(dataContent.getContentUrl());
+ }
+ else
+ {
+ // register for immediate destruction
+ eagerContentStoreCleaner.registerOrphanedContentUrl(dataContent.getContentUrl(), true);
+ }
+ }
+ }
+ }
+}
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
new file mode 100644
index 0000000000..d1c5648bb1
--- /dev/null
+++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/EagerContentStoreCleaner.java
@@ -0,0 +1,163 @@
+/*
+ * 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.content;
+
+import java.io.File;
+import java.util.Set;
+
+import org.alfresco.module.org_alfresco_module_rm.content.cleanser.ContentCleanser;
+import org.alfresco.module.org_alfresco_module_rm.util.TransactionalResourceHelper;
+import org.alfresco.repo.content.ContentStore;
+import org.alfresco.repo.content.filestore.FileContentReader;
+import org.alfresco.service.cmr.repository.ContentReader;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Eager content store cleaner that allows content to be registered for cleansing before
+ * destruction.
+ *
+ * @author Roy Wetherall
+ * @since 3.0.a
+ */
+public class EagerContentStoreCleaner extends org.alfresco.repo.content.cleanup.EagerContentStoreCleaner
+{
+ /** transaction resource key */
+ protected static final String KEY_POST_COMMIT_CLEANSING_URLS = "postCommitCleansingUrls";
+
+ /** logger */
+ private static Log logger = LogFactory.getLog(EagerContentStoreCleaner.class);
+
+ /** transactional resource helper */
+ private TransactionalResourceHelper transactionalResourceHelper;
+
+ /** content cleanser */
+ private ContentCleanser contentCleanser;
+
+ /**
+ * @param transactionResourceHelper transactional resource helper
+ */
+ public void setTransactionalResourceHelper(TransactionalResourceHelper transactionalResourceHelper)
+ {
+ this.transactionalResourceHelper = transactionalResourceHelper;
+ }
+
+ /**
+ * @param contentCleanser content cleanser
+ */
+ public void setContentCleanser(ContentCleanser contentCleanser)
+ {
+ this.contentCleanser = contentCleanser;
+ }
+
+ /**
+ * Registers orphaned content URLs for cleansing
+ *
+ * @param contentUrl content url
+ */
+ public void registerOrphanedContentUrlForCleansing(String contentUrl)
+ {
+ // make note of content that needs cleansing
+ Set cleansingUrls = transactionalResourceHelper.getSet(KEY_POST_COMMIT_CLEANSING_URLS);
+ cleansingUrls.add(contentUrl);
+
+ // register as usual
+ registerOrphanedContentUrl(contentUrl, true);
+ }
+
+ /**
+ * @see org.alfresco.repo.content.cleanup.EagerContentStoreCleaner#deleteFromStore(java.lang.String, org.alfresco.repo.content.ContentStore)
+ */
+ @Override
+ protected boolean deleteFromStore(String contentUrl, ContentStore store)
+ {
+ // determine if the content requires cleansing or not
+ Set cleansingUrls = transactionalResourceHelper.getSet(KEY_POST_COMMIT_CLEANSING_URLS);
+ if (cleansingUrls.contains(contentUrl))
+ {
+ // cleanse content before delete
+ cleanseContent(contentUrl, store);
+ }
+
+ // delete from store
+ return super.deleteFromStore(contentUrl, store);
+ }
+
+ /**
+ * Cleanse content
+ *
+ * @param contentUrl content url
+ * @param store content store
+ */
+ private void cleanseContent(String contentUrl, ContentStore store)
+ {
+ if (contentCleanser == null)
+ {
+ logger.error(
+ "No content cleanser specified. Unable to cleanse: \n" +
+ " URL: " + contentUrl + "\n" +
+ " Source: " + store);
+ }
+ else
+ {
+ // First check if the content is present at all
+ ContentReader reader = store.getReader(contentUrl);
+ if (reader != null && reader.exists())
+ {
+ // Call to implementation's shred
+ if (logger.isDebugEnabled())
+ {
+ logger.debug(
+ "About to cleanse: \n" +
+ " URL: " + contentUrl + "\n" +
+ " Source: " + store);
+ }
+ try
+ {
+ if (reader instanceof FileContentReader)
+ {
+ // get file content
+ FileContentReader fileReader = (FileContentReader) reader;
+ File file = fileReader.getFile();
+
+ // cleanse content
+ contentCleanser.cleanse(file);
+ }
+ }
+ catch (Throwable e)
+ {
+ logger.error(
+ "Content cleansing failed: \n" +
+ " URL: " + contentUrl + "\n" +
+ " Source: " + store + "\n" +
+ " Reader: " + reader,
+ e);
+ }
+ }
+ else
+ {
+ logger.error(
+ "Content no longer exists. Unable to cleanse: \n" +
+ " URL: " + contentUrl + "\n" +
+ " Source: " + store);
+ }
+ }
+ }
+
+}
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
new file mode 100644
index 0000000000..61750cf2d0
--- /dev/null
+++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/cleanser/ContentCleanser.java
@@ -0,0 +1,122 @@
+/*
+ * 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.content.cleanser;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Random;
+
+/**
+ * Content cleanser base implementation.
+ *
+ * @author Roy Wetherall
+ * @since 3.0.a
+ */
+public abstract class ContentCleanser
+{
+ /**
+ * Cleanse file
+ *
+ * @param file file to cleanse
+ */
+ public abstract void cleanse(File file);
+
+ /**
+ * Overwrite files bytes with provided overwrite operation
+ *
+ * @param file file
+ * @param overwriteOperation overwrite operation
+ */
+ protected void overwrite(File file, OverwriteOperation overwriteOperation)
+ {
+ // get the number of bytes
+ long bytes = file.length();
+ try
+ {
+ // get an output stream
+ OutputStream os = new BufferedOutputStream(new FileOutputStream(file));
+ try
+ {
+ for (int i = 0; i < bytes; i++)
+ {
+ // overwrite byte
+ overwriteOperation.operation(os);
+ }
+ }
+ finally
+ {
+ // close ouput stream
+ try {os.close(); } catch (Throwable e) {}
+ }
+ }
+ catch (IOException ioException)
+ {
+ // re-throw
+ throw new RuntimeException("Unable to overwrite file", ioException);
+ }
+ }
+
+ /**
+ * Overwrite operation
+ */
+ protected abstract class OverwriteOperation
+ {
+ public abstract void operation(OutputStream os) throws IOException;
+ }
+
+ /**
+ * Overwrite with zeros operation
+ */
+ protected OverwriteOperation overwriteZeros = new OverwriteOperation()
+ {
+ public void operation(OutputStream os) throws IOException
+ {
+ os.write(0);
+ }
+ };
+
+ /**
+ * Overwrite with ones operation
+ */
+ protected OverwriteOperation overwriteOnes = new OverwriteOperation()
+ {
+ public void operation(OutputStream os) throws IOException
+ {
+ os.write(1);
+ }
+ };
+
+ /**
+ * Overwrite with random operation
+ */
+ protected OverwriteOperation overwriteRandom = new OverwriteOperation()
+ {
+ private Random random = new Random();
+
+ public void operation(OutputStream os) throws IOException
+ {
+ byte[] randomByte = new byte[1];
+ random.nextBytes(randomByte);
+ os.write(randomByte[0]);
+ }
+ };
+}
diff --git a/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/cleanser/ContentCleanser522022M.java b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/cleanser/ContentCleanser522022M.java
new file mode 100644
index 0000000000..128ac507f0
--- /dev/null
+++ b/rm-server/source/java/org/alfresco/module/org_alfresco_module_rm/content/cleanser/ContentCleanser522022M.java
@@ -0,0 +1,50 @@
+/*
+ * 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.content.cleanser;
+
+import java.io.File;
+
+import org.alfresco.service.cmr.repository.ContentIOException;
+
+/**
+ * DoD 5220-22M data cleansing implementation.
+ *
+ * @author Roy Wetherall
+ * @since 3.0.a
+ */
+public class ContentCleanser522022M extends ContentCleanser
+{
+ /**
+ * @see org.alfresco.module.org_alfresco_module_rm.content.cleanser.ContentCleanser#cleanse(java.io.File)
+ */
+ @Override
+ public void cleanse(File file)
+ {
+ // Double check
+ if (!file.exists() || !file.canWrite())
+ {
+ throw new ContentIOException("Unable to write to file: " + file);
+ }
+
+ // Overwite file
+ overwrite(file, overwriteOnes);
+ overwrite(file, overwriteZeros);
+ overwrite(file, overwriteRandom);
+ }
+}
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
new file mode 100644
index 0000000000..2abe883419
--- /dev/null
+++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/content/ContentDestructionComponentUnitTest.java
@@ -0,0 +1,233 @@
+/*
+ * 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.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 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;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+
+/**
+ * Content destruction component unit test.
+ *
+ * @author Roy Wetherall
+ * @since 3.0.a
+ */
+public class ContentDestructionComponentUnitTest extends BaseUnitTest
+{
+ @InjectMocks private ContentDestructionComponent contentDestructionComponent;
+
+ @Mock private ContentClassificationService mockedContentClassificationService;
+ @Mock private ContentCleanser mockedContentCleanser;
+ @Mock private EagerContentStoreCleaner mockedEagerContentStoreCleaner;
+
+ /**
+ * Given a non-sensitive node
+ * When it is deleted
+ * Then nothing happens
+ */
+ @Test
+ public void deleteNonSensitiveNode()
+ {
+ NodeRef nodeRef = generateCmContent("myContent.txt");
+
+ when(mockedRecordService.isRecord(nodeRef))
+ .thenReturn(false);
+ when(mockedContentClassificationService.isClassified(nodeRef))
+ .thenReturn(false);
+
+ contentDestructionComponent.beforeDeleteNode(nodeRef);
+
+ verifyZeroInteractions(mockedEagerContentStoreCleaner, mockedDictionaryService);
+ }
+
+ /**
+ * Given a record
+ * When it is deleted
+ * Then it is sent for immediate destruction
+ */
+ @Test
+ public void deleteRecord()
+ {
+ String contentURL = AlfMock.generateText();
+ NodeRef nodeRef = generateDeletedNodeRef(contentURL);
+
+ when(mockedRecordService.isRecord(nodeRef))
+ .thenReturn(true);
+ when(mockedContentClassificationService.isClassified(nodeRef))
+ .thenReturn(false);
+
+ contentDestructionComponent.beforeDeleteNode(nodeRef);
+
+ verify(mockedEagerContentStoreCleaner).registerOrphanedContentUrl(contentURL, true);
+ }
+
+ /**
+ * Given classified content
+ * When it is deleted
+ * Then it is send for immediate destruction
+ */
+ @Test
+ public void deleteClassifiedContent()
+ {
+ String contentURL = AlfMock.generateText();
+ NodeRef nodeRef = generateDeletedNodeRef(contentURL);
+
+ when(mockedRecordService.isRecord(nodeRef))
+ .thenReturn(false);
+ when(mockedContentClassificationService.isClassified(nodeRef))
+ .thenReturn(true);
+
+ contentDestructionComponent.beforeDeleteNode(nodeRef);
+
+ verify(mockedEagerContentStoreCleaner).registerOrphanedContentUrl(contentURL, true);
+ }
+
+ /**
+ * Given that content cleansing is turned on
+ * When a sensitive node is deleted
+ * Then it is not scheduled for cleansing before destruction
+ */
+ @Test
+ public void contentCleansingOn()
+ {
+ String contentURL = AlfMock.generateText();
+ NodeRef nodeRef = generateDeletedNodeRef(contentURL);
+
+ when(mockedRecordService.isRecord(nodeRef))
+ .thenReturn(false);
+ when(mockedContentClassificationService.isClassified(nodeRef))
+ .thenReturn(true);
+
+ contentDestructionComponent.setCleansingEnabled(true);
+ contentDestructionComponent.beforeDeleteNode(nodeRef);
+
+ verify(mockedEagerContentStoreCleaner).registerOrphanedContentUrlForCleansing(contentURL);
+ }
+
+ /**
+ * Given that content cleansing is turned off
+ * When a sensitive node is deleted
+ * Then it is scheduled for cleansing before destruction
+ */
+ @Test
+ public void contentCleansingOff()
+ {
+ String contentURL = AlfMock.generateText();
+ NodeRef nodeRef = generateDeletedNodeRef(contentURL);
+
+ when(mockedRecordService.isRecord(nodeRef))
+ .thenReturn(false);
+ when(mockedContentClassificationService.isClassified(nodeRef))
+ .thenReturn(true);
+
+ contentDestructionComponent.setCleansingEnabled(false);
+ contentDestructionComponent.beforeDeleteNode(nodeRef);
+
+ verify(mockedEagerContentStoreCleaner).registerOrphanedContentUrl(contentURL, true);
+
+ }
+
+ /**
+ * Given that a sensitive node has more than one content property
+ * When is it deleted
+ * Then all the content properties are scheduled for destruction
+ */
+ @Test
+ public void moreThanOneContentProperty()
+ {
+ String contentURL = AlfMock.generateText();
+ NodeRef nodeRef = generateDeletedNodeRef(contentURL, 2);
+
+ when(mockedRecordService.isRecord(nodeRef))
+ .thenReturn(false);
+ when(mockedContentClassificationService.isClassified(nodeRef))
+ .thenReturn(true);
+
+ contentDestructionComponent.setCleansingEnabled(true);
+ contentDestructionComponent.beforeDeleteNode(nodeRef);
+
+ verify(mockedEagerContentStoreCleaner, times(2)).registerOrphanedContentUrlForCleansing(contentURL);
+
+ }
+
+ /**
+ * Helper method that creates deleted node reference
+ */
+ private NodeRef generateDeletedNodeRef(String contentURL)
+ {
+ return generateDeletedNodeRef(contentURL, 1);
+ }
+
+ /**
+ * Helper method that creates deleted node reference
+ */
+ private NodeRef generateDeletedNodeRef(String contentURL, int contentPropertiesCount)
+ {
+ 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())
+ .thenReturn(contentURL);
+ when(mockedNodeService.getProperty(eq(nodeRef), any(QName.class)))
+ .thenReturn(mockedDataContent);
+
+ return nodeRef;
+ }
+}
diff --git a/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/content/EagerContentStoreCleanerUnitTest.java b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/content/EagerContentStoreCleanerUnitTest.java
new file mode 100644
index 0000000000..4ed98fb865
--- /dev/null
+++ b/rm-server/unit-test/java/org/alfresco/module/org_alfresco_module_rm/content/EagerContentStoreCleanerUnitTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.content;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+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.repo.content.ContentStore;
+import org.alfresco.repo.content.filestore.FileContentReader;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+
+/**
+ * Eager content store cleaner unit test.
+ *
+ * @author Roy Wetherall
+ * @since 3.0.a
+ */
+public class EagerContentStoreCleanerUnitTest extends BaseUnitTest
+{
+ @InjectMocks private EagerContentStoreCleaner eagerContentStoreCleaner = new EagerContentStoreCleaner()
+ {
+ /** dummy implementation */
+ public boolean registerOrphanedContentUrl(String contentUrl, boolean force) {return true;};
+ };
+
+ @Mock private ContentCleanser mockedContentCleanser;
+
+ /**
+ * When content is registered for cleansing
+ * Then the content URL is recorded for use later
+ */
+ @SuppressWarnings("unchecked")
+ @Test
+ public void registerContentURL()
+ {
+ String contentURL = AlfMock.generateText();
+ Set