Data destruction and cleansing

* added content destruction component which ensures all records and classified content are immediately destroyed and optionally cleansed
 * extension to eager content cleaner to allow cleansing to take place just before the content is deleted from the content store
 * base content cleanser
 * simple implementation of DoD 5220-22M cleansing algoritm
 * data cleansing enabled global configuration
 * data cleansing bean configuration
 * unit tests
 * see RM-2463 and RM-2464

+review RM 



git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/modules/recordsmanagement/HEAD@109121 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Roy Wetherall
2015-07-29 01:26:49 +00:00
parent bc18dac0bb
commit c2cb2e11ef
10 changed files with 1055 additions and 0 deletions

View File

@@ -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 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). # 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 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

View File

@@ -0,0 +1,38 @@
<?xml version='1.0' encoding='UTF-8'?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
<!-- content destruction component -->
<bean name="contentDestructionComponent" class="org.alfresco.module.org_alfresco_module_rm.content.ContentDestructionComponent">
<property name="authenticationUtil" ref="rm.authenticationUtil" />
<property name="recordService" ref="recordService" />
<property name="contentClassificationService" ref="contentClassificationService" />
<property name="eagerContentStoreCleaner" ref="eagerContentStoreCleaner" />
<property name="dictionaryService" ref="dictionaryService" />
<property name="nodeService" ref="nodeService" />
<property name="cleansingEnabled" value="${rm.content.cleansing.enabled}" />
</bean>
<!-- extended eager content store cleaner -->
<bean name="rm.eagerContentStoreCleaner" class="org.alfresco.module.org_alfresco_module_rm.content.EagerContentStoreCleaner">
<property name="transactionalResourceHelper" ref="rm.transactionalResourceHelper" />
<property name="contentCleanser" ref="${rm.content.cleaner}" />
</bean>
<bean class="org.alfresco.util.BeanExtender">
<property name="beanName" value="eagerContentStoreCleaner" />
<property name="extendingBeanName" value="rm.eagerContentStoreCleaner" />
</bean>
<!-- content cleanser -->
<bean id="contentCleanser.522022M" class="org.alfresco.module.org_alfresco_module_rm.content.cleanser.ContentCleanser522022M"/>
</beans>

View File

@@ -259,4 +259,7 @@
<!-- Import the Classified Content Services --> <!-- Import the Classified Content Services -->
<import resource="classpath:alfresco/module/org_alfresco_module_rm/classified-content-context.xml"/> <import resource="classpath:alfresco/module/org_alfresco_module_rm/classified-content-context.xml"/>
<!-- Import the Content Services -->
<import resource="classpath:alfresco/module/org_alfresco_module_rm/content-context.xml"/>
</beans> </beans>

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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.
* <p>
* Listens for the destruction of sensitive nodes (classified content and records) and schedules
* all their content for immediate destruction.
* <p>
* 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.
* <p>
* 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<Void>()
{
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<QName> 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);
}
}
}
}
}

View File

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

View File

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

View File

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

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<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())
.thenReturn(contentURL);
when(mockedNodeService.getProperty(eq(nodeRef), any(QName.class)))
.thenReturn(mockedDataContent);
return nodeRef;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<Object> mockedSet = mock(Set.class);
when(mockedTransactionalResourceHelper.getSet(EagerContentStoreCleaner.KEY_POST_COMMIT_CLEANSING_URLS))
.thenReturn(mockedSet);
eagerContentStoreCleaner.registerOrphanedContentUrlForCleansing(contentURL);
verify(mockedSet).add(contentURL);
}
/**
* Given that the content requires cleansing
* When the content is deleted from the store
* Then the content is cleansed first
*/
@Test
public void contentRequiresCleaning()
{
String contentURL = AlfMock.generateText();
Set<Object> mockedSet = new HashSet<Object>(Arrays.asList(contentURL));
when(mockedTransactionalResourceHelper.getSet(EagerContentStoreCleaner.KEY_POST_COMMIT_CLEANSING_URLS))
.thenReturn(mockedSet);
FileContentReader mockedReader = mock(FileContentReader.class);
when(mockedReader.exists())
.thenReturn(true);
File mockedFile = mock(File.class);
when(mockedReader.getFile())
.thenReturn(mockedFile);
ContentStore mockedContentStore = mock(ContentStore.class);
when(mockedContentStore.getReader(contentURL))
.thenReturn(mockedReader);
eagerContentStoreCleaner.deleteFromStore(contentURL, mockedContentStore);
verify(mockedContentCleanser).cleanse(mockedFile);
}
/**
* Given that the content does not require cleansing
* When the content is deleted from the store
* Then the content is not cleansed
*/
@Test
public void contentDoesntRequireCleaning()
{
String contentURL = AlfMock.generateText();
Set<Object> mockedSet = new HashSet<Object>(Arrays.asList(contentURL));
when(mockedTransactionalResourceHelper.getSet(EagerContentStoreCleaner.KEY_POST_COMMIT_CLEANSING_URLS))
.thenReturn(mockedSet);
eagerContentStoreCleaner.deleteFromStore(AlfMock.generateText(), mock(ContentStore.class));
verifyZeroInteractions(mockedContentCleanser);
}
}

View File

@@ -0,0 +1,109 @@
/*
* 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.content.cleanser;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
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.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
/**
* Eager content store cleaner unit test.
*
* @author Roy Wetherall
* @since 3.0.a
*/
public class ContentCleanser522022MUnitTest extends BaseUnitTest
{
@InjectMocks @Spy private ContentCleanser522022M contentCleanser522022M = new ContentCleanser522022M()
{
/** dummy implementations */
protected void overwrite(File file, OverwriteOperation overwriteOperation) {};
};
@Mock private File mockedFile;
/**
* Given that a file exists
* When I cleanse it
* Then the content is overwritten
*/
@Test
public void cleanseFile()
{
when(mockedFile.exists())
.thenReturn(true);
when(mockedFile.canWrite())
.thenReturn(true);
contentCleanser522022M.cleanse(mockedFile);
verify(contentCleanser522022M)
.overwrite(mockedFile, contentCleanser522022M.overwriteOnes);
verify(contentCleanser522022M)
.overwrite(mockedFile, contentCleanser522022M.overwriteZeros);
verify(contentCleanser522022M)
.overwrite(mockedFile, contentCleanser522022M.overwriteOnes);
}
/**
* Given that the file does not exist
* When I cleanse it
* Then an exception is thrown
*/
@Test
(
expected=ContentIOException.class
)
public void fileDoesNotExist()
{
when(mockedFile.exists())
.thenReturn(false);
when(mockedFile.canWrite())
.thenReturn(true);
contentCleanser522022M.cleanse(mockedFile);
}
/**
* Given that I can not write to the file
* When I cleanse it
* Then an exception is thrown
*/
@Test
(
expected=ContentIOException.class
)
public void cantWriteToFile()
{
when(mockedFile.exists())
.thenReturn(true);
when(mockedFile.canWrite())
.thenReturn(false);
contentCleanser522022M.cleanse(mockedFile);
}
}