mirror of
				https://github.com/Alfresco/alfresco-community-repo.git
				synced 2025-10-29 15:21:53 +00:00 
			
		
		
		
	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
		
	
		
			
				
	
	
		
			318 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			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;
 | |
|         }
 | |
|     };
 | |
| }
 |