Alan Davis 72662536a7 Merged HEAD-BUG-FIX (4.3/Cloud) to HEAD (4.3/Cloud)
60595: Merged V4.2-BUG-FIX (4.2.2) to HEAD-BUG-FIX (Cloud/4.3)
      60362: Merged DEV to V4.2-BUG-FIX (4.2.2)
         58112: Added missing transaction(s) in CheckOutCheckInServiceImplTest (MNT-9806)
         58113: Added missing transaction(s) in LockableAspectInterceptorTest (MNT-9806)
         58114: Added missing transaction(s) in LockableAspectInterceptorTest (MNT-9806)
         58115: Added missing transaction(s) in TransactionCleanupTest (MNT-9806)
         58116: Added missing transaction(s) in GetChildrenCannedQueryTest (MNT-9806)
         58118: Added missing transaction(s) in RatingServiceIntegrationTest (MNT-9806)
         58119: Added missing transaction(s) in ReplicationServiceIntegrationTest (MNT-9806)
         58121: Added missing transaction(s) in PersonTest (MNT-9806)


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@62326 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
2014-02-12 14:08:49 +00:00

318 lines
11 KiB
Java

package org.alfresco.repo.node.cleanup;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.transaction.UserTransaction;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.repo.domain.node.NodeDAO;
import org.alfresco.repo.domain.node.Transaction;
import org.alfresco.repo.node.db.DeletedNodeCleanupWorker;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.security.MutableAuthenticationService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.test_category.OwnJVMTestsCategory;
import org.alfresco.util.ApplicationContextHelper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.springframework.context.ApplicationContext;
import org.springframework.extensions.webscripts.GUID;
/**
* @author Derek Hulley
* @since 4.0
*/
@Category(OwnJVMTestsCategory.class)
public class TransactionCleanupTest
{
private static Log logger = LogFactory.getLog(TransactionCleanupTest.class);
private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
private TransactionService transactionService;
private NodeService nodeService;
private SearchService searchService;
private MutableAuthenticationService authenticationService;
private NodeDAO nodeDAO;
private SimpleCache<Serializable, Serializable> nodesCache;
private DeletedNodeCleanupWorker worker;
private NodeRef nodeRef1;
private NodeRef nodeRef2;
private NodeRef nodeRef3;
private NodeRef nodeRef4;
private NodeRef nodeRef5;
private RetryingTransactionHelper helper;
@SuppressWarnings("unchecked")
@Before
public void before()
{
ServiceRegistry serviceRegistry = (ServiceRegistry)ctx.getBean("ServiceRegistry");
NamespaceService namespaceService = serviceRegistry.getNamespaceService();
this.transactionService = serviceRegistry.getTransactionService();
this.authenticationService = (MutableAuthenticationService)ctx.getBean("authenticationService");
this.nodeService = serviceRegistry.getNodeService();
this.searchService = serviceRegistry.getSearchService();
this.nodeDAO = (NodeDAO)ctx.getBean("nodeDAO");
this.nodesCache = (SimpleCache<Serializable, Serializable>) ctx.getBean("node.nodesSharedCache");
this.worker = (DeletedNodeCleanupWorker)ctx.getBean("nodeCleanup.deletedNodeCleanup");
this.worker.setMinPurgeAgeDays(0);
this.helper = transactionService.getRetryingTransactionHelper();
authenticationService.authenticate("admin", "admin".toCharArray());
StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore");
NodeRef storeRoot = nodeService.getRootNode(storeRef);
List<NodeRef> nodeRefs = searchService.selectNodes(
storeRoot, "/app:company_home", null, namespaceService, false);
final NodeRef companyHome = nodeRefs.get(0);
RetryingTransactionHelper.RetryingTransactionCallback<NodeRef> createNode = new RetryingTransactionHelper.RetryingTransactionCallback<NodeRef>()
{
@Override
public NodeRef execute() throws Throwable
{
return nodeService.createNode(companyHome, ContentModel.ASSOC_CONTAINS, QName.createQName("test", GUID.generate()), ContentModel.TYPE_CONTENT).getChildRef();
}
};
this.nodeRef1 = helper.doInTransaction(createNode, false, true);
this.nodeRef2 = helper.doInTransaction(createNode, false, true);
this.nodeRef3 = helper.doInTransaction(createNode, false, true);
this.nodeRef4 = helper.doInTransaction(createNode, false, true);
this.nodeRef5 = helper.doInTransaction(createNode, false, true);
}
private Map<NodeRef, List<String>> createTransactions()
{
Map<NodeRef, List<String>> txnIds = new HashMap<NodeRef, List<String>>();
UpdateNode updateNode1 = new UpdateNode(nodeRef1);
UpdateNode updateNode2 = new UpdateNode(nodeRef2);
UpdateNode updateNode3 = new UpdateNode(nodeRef3);
DeleteNode deleteNode4 = new DeleteNode(nodeRef4);
DeleteNode deleteNode5 = new DeleteNode(nodeRef5);
List<String> txnIds1 = new ArrayList<String>();
List<String> txnIds2 = new ArrayList<String>();
List<String> txnIds3 = new ArrayList<String>();
List<String> txnIds4 = new ArrayList<String>();
List<String> txnIds5 = new ArrayList<String>();
txnIds.put(nodeRef1, txnIds1);
txnIds.put(nodeRef2, txnIds2);
txnIds.put(nodeRef3, txnIds3);
txnIds.put(nodeRef4, txnIds4);
txnIds.put(nodeRef5, txnIds5);
for(int i = 0; i < 10; i++)
{
String txnId1 = helper.doInTransaction(updateNode1, false, true);
txnIds1.add(txnId1);
if(i == 0)
{
String txnId2 = helper.doInTransaction(updateNode2, false, true);
txnIds2.add(txnId2);
}
if(i == 1)
{
String txnId3 = helper.doInTransaction(updateNode3, false, true);
txnIds3.add(txnId3);
}
}
String txnId4 = helper.doInTransaction(deleteNode4, false, true);
txnIds4.add(txnId4);
String txnId5 = helper.doInTransaction(deleteNode5, false, true);
txnIds5.add(txnId5);
return txnIds;
}
private boolean containsTransaction(List<Transaction> txns, String txnId)
{
boolean found = false;
for(Transaction tx : txns)
{
if(tx.getChangeTxnId().equals(txnId))
{
found = true;
break;
}
}
return found;
}
@Test
public void testPurgeUnusedTransactions() throws Exception
{
// Execute transactions that update a number of nodes. For nodeRef1, all but the last txn will be unused.
// run the transaction cleaner to clean up any existing unused transactions
worker.doClean();
final long start = System.currentTimeMillis();
final Long minTxnId = nodeDAO.getMinTxnId();
final Map<NodeRef, List<String>> txnIds = createTransactions();
final List<String> txnIds1 = txnIds.get(nodeRef1);
final List<String> txnIds2 = txnIds.get(nodeRef2);
final List<String> txnIds3 = txnIds.get(nodeRef3);
// Pure delete: final List<String> txnIds4 = txnIds.get(nodeRef4);
// Pure delete: final List<String> txnIds5 = txnIds.get(nodeRef5);
// Double-check that n4 and n5 are present in deleted form
nodesCache.clear();
UserTransaction txn = transactionService.getUserTransaction(true);
txn.begin();
try
{
assertNotNull("Node 4 is deleted but not purged", nodeDAO.getNodeRefStatus(nodeRef4));
assertNotNull("Node 5 is deleted but not purged", nodeDAO.getNodeRefStatus(nodeRef5));
}
finally
{
txn.rollback();
}
// run the transaction cleaner
worker.setPurgeSize(5); // small purge size
List<String> reports = worker.doClean();
for (String report : reports)
{
logger.debug(report);
}
// Get transactions committed after the test started
RetryingTransactionCallback<List<Transaction>> getTxnsCallback = new RetryingTransactionCallback<List<Transaction>>()
{
@Override
public List<Transaction> execute() throws Throwable
{
return nodeDAO.getTxnsByCommitTimeAscending(Long.valueOf(start), Long.valueOf(Long.MAX_VALUE), Integer.MAX_VALUE, null, false);
}
};
List<Transaction> txns = transactionService.getRetryingTransactionHelper().doInTransaction(getTxnsCallback, true, false);
List<String> expectedUnusedTxnIds = new ArrayList<String>(10);
expectedUnusedTxnIds.addAll(txnIds1.subList(0, txnIds1.size() - 1));
List<String> expectedUsedTxnIds = new ArrayList<String>(5);
expectedUsedTxnIds.add(txnIds1.get(txnIds1.size() - 1));
expectedUsedTxnIds.addAll(txnIds2);
expectedUsedTxnIds.addAll(txnIds3);
// 4 and 5 should not be in the list because they are deletes
// check that the correct transactions have been purged i.e. all except the last one to update the node
// i.e. in this case, all but the last one in txnIds1
int numFoundUnusedTxnIds = 0;
for(String txnId : expectedUnusedTxnIds)
{
if(!containsTransaction(txns, txnId))
{
numFoundUnusedTxnIds++;
}
else if(txnIds1.contains(txnId))
{
fail("Unused transaction(s) were not purged: " + txnId);
}
}
assertEquals(9, numFoundUnusedTxnIds);
// check that the correct transactions remain i.e. all those in txnIds2, txnIds3, txnIds4 and txnIds5
int numFoundUsedTxnIds = 0;
for(String txnId : expectedUsedTxnIds)
{
if(containsTransaction(txns, txnId))
{
numFoundUsedTxnIds++;
}
}
assertEquals(3, numFoundUsedTxnIds);
// Get transactions committed after the test started
RetryingTransactionCallback<List<Long>> getTxnsUnusedCallback = new RetryingTransactionCallback<List<Long>>()
{
@Override
public List<Long> execute() throws Throwable
{
return nodeDAO.getTxnsUnused(minTxnId, Long.MAX_VALUE, Integer.MAX_VALUE);
}
};
List<Long> txnsUnused = transactionService.getRetryingTransactionHelper().doInTransaction(getTxnsUnusedCallback, true, false);
assertEquals(0, txnsUnused.size());
// Double-check that n4 and n5 were removed as well
nodesCache.clear();
assertNull("Node 4 was not cleaned up", nodeDAO.getNodeRefStatus(nodeRef4));
assertNull("Node 5 was not cleaned up", nodeDAO.getNodeRefStatus(nodeRef5));
}
@After
public void after()
{
ApplicationContextHelper.closeApplicationContext();
}
private class UpdateNode implements RetryingTransactionCallback<String>
{
private NodeRef nodeRef;
UpdateNode(NodeRef nodeRef)
{
this.nodeRef = nodeRef;
}
@Override
public String execute() throws Throwable
{
nodeService.setProperty(nodeRef, ContentModel.PROP_NAME, GUID.generate());
String txnId = AlfrescoTransactionSupport.getTransactionId();
return txnId;
}
};
private class DeleteNode implements RetryingTransactionCallback<String>
{
private NodeRef nodeRef;
DeleteNode(NodeRef nodeRef)
{
this.nodeRef = nodeRef;
}
@Override
public String execute() throws Throwable
{
nodeService.addAspect(nodeRef, ContentModel.ASPECT_TEMPORARY, null);
nodeService.deleteNode(nodeRef);
String txnId = AlfrescoTransactionSupport.getTransactionId();
return txnId;
}
};
}