diff --git a/source/java/org/alfresco/util/test/junitrules/TemporaryNodes.java b/source/java/org/alfresco/util/test/junitrules/TemporaryNodes.java index 2971bcde74..0900ee481f 100644 --- a/source/java/org/alfresco/util/test/junitrules/TemporaryNodes.java +++ b/source/java/org/alfresco/util/test/junitrules/TemporaryNodes.java @@ -31,6 +31,7 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.coci.CheckOutCheckInService; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentWriter; @@ -41,12 +42,13 @@ import org.alfresco.service.namespace.QName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.rules.ExternalResource; +import org.springframework.context.ApplicationContext; /** * A JUnit rule designed to help with the automatic cleanup of temporary test nodes. * * @author Neil Mc Erlean - * @since Odin + * @since 4.1 */ public class TemporaryNodes extends ExternalResource { @@ -73,8 +75,11 @@ public class TemporaryNodes extends ExternalResource @Override protected void after() { - final RetryingTransactionHelper transactionHelper = (RetryingTransactionHelper) appContextRule.getApplicationContext().getBean("retryingTransactionHelper"); - final NodeService nodeService = (NodeService) appContextRule.getApplicationContext().getBean("nodeService"); + final ApplicationContext springContext = appContextRule.getApplicationContext(); + + final RetryingTransactionHelper transactionHelper = springContext.getBean("retryingTransactionHelper", RetryingTransactionHelper.class); + final CheckOutCheckInService cociService = springContext.getBean("CheckOutCheckInService", CheckOutCheckInService.class); + final NodeService nodeService = springContext.getBean("NodeService", NodeService.class); // Run as admin to ensure all non-system nodes can be deleted irrespecive of which user created them. AuthenticationUtil.runAs(new RunAsWork() @@ -88,8 +93,16 @@ public class TemporaryNodes extends ExternalResource // Although we loop through all nodes, this is a cascade-delete and so we may only need to delete the first node. for (NodeRef node : temporaryNodeRefs) { + // If it's already been deleted, don't worry about it. if (nodeService.exists(node)) { + // If it has been checked out, cancel the checkout before deletion. + if (cociService.isCheckedOut(node)) + { + log.debug("Cancelling checkout of temporary node " + nodeService.getProperty(node, ContentModel.PROP_NAME)); + NodeRef workingCopy = cociService.getWorkingCopy(node); + cociService.cancelCheckout(workingCopy); + } log.debug("Deleting temporary node " + nodeService.getProperty(node, ContentModel.PROP_NAME)); nodeService.deleteNode(node); } @@ -100,7 +113,7 @@ public class TemporaryNodes extends ExternalResource }); return null; } - }, AuthenticationUtil.getAdminUserName()); + }, AuthenticationUtil.getSystemUserName()); } /** diff --git a/source/java/org/alfresco/util/test/junitrules/TemporaryNodesTest.java b/source/java/org/alfresco/util/test/junitrules/TemporaryNodesTest.java index 09f5cb2f59..16ef2430c9 100644 --- a/source/java/org/alfresco/util/test/junitrules/TemporaryNodesTest.java +++ b/source/java/org/alfresco/util/test/junitrules/TemporaryNodesTest.java @@ -22,8 +22,11 @@ package org.alfresco.util.test.junitrules; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import org.alfresco.model.ContentModel; @@ -31,6 +34,7 @@ import org.alfresco.repo.model.Repository; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.coci.CheckOutCheckInService; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.NodeRef; @@ -46,8 +50,8 @@ import org.junit.rules.RuleChain; /** * Test class for {@link TemporaryNodes}. * - * @author Neil McErlean - * @since Odin + * @author Neil Mc Erlean + * @since 4.1 */ public class TemporaryNodesTest { @@ -74,6 +78,7 @@ public class TemporaryNodesTest @Rule public RunAsFullyAuthenticatedRule runAsRule = new RunAsFullyAuthenticatedRule(AuthenticationUtil.getAdminUserName()); // Various services + private static CheckOutCheckInService COCI_SERVICE; private static ContentService CONTENT_SERVICE; private static NodeService NODE_SERVICE; private static RetryingTransactionHelper TRANSACTION_HELPER; @@ -85,6 +90,7 @@ public class TemporaryNodesTest @BeforeClass public static void initStaticData() throws Exception { + COCI_SERVICE = APP_CONTEXT_INIT.getApplicationContext().getBean("checkOutCheckInService", CheckOutCheckInService.class); CONTENT_SERVICE = APP_CONTEXT_INIT.getApplicationContext().getBean("contentService", ContentService.class); NODE_SERVICE = APP_CONTEXT_INIT.getApplicationContext().getBean("nodeService", NodeService.class); TRANSACTION_HELPER = APP_CONTEXT_INIT.getApplicationContext().getBean("retryingTransactionHelper", RetryingTransactionHelper.class); @@ -131,4 +137,57 @@ public class TemporaryNodesTest } }); } + + @Test public void ensureCheckedOutNodesAreCleanedUp() throws Throwable + { + // Note that because we need to test that the Rule's 'after' behaviour has worked correctly, we cannot + // use the Rule that has been declared in the normal way - otherwise nothing would be cleaned up until + // after our test method. + // Therefore we have to manually poke the Rule to get it to cleanup during test execution. + // NOTE! This is *not* how a JUnit Rule would normally be used. + TemporaryNodes myTemporaryNodes = new TemporaryNodes(APP_CONTEXT_INIT); + + // Currently this is a no-op, but just in case that changes. + myTemporaryNodes.before(); + + + // Create some test nodes. + final List nodesThatShouldBeDeletedByRule = new ArrayList(); + + nodesThatShouldBeDeletedByRule.add(myTemporaryNodes.createNode(COMPANY_HOME, "normal node", ContentModel.TYPE_CONTENT, TEST_USER1.getUsername())); + final NodeRef checkedoutNode = myTemporaryNodes.createNode(COMPANY_HOME, "checkedout node", ContentModel.TYPE_CONTENT, TEST_USER1.getUsername()); + nodesThatShouldBeDeletedByRule.add(checkedoutNode); + + // and check one of them out. + TRANSACTION_HELPER.doInTransaction(new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + NodeRef workingCopy = COCI_SERVICE.checkout(checkedoutNode); + + // Ensure that the working copy is cleaned up too. + nodesThatShouldBeDeletedByRule.add(workingCopy); + return null; + } + }); + + // Now trigger the Rule's cleanup behaviour. + myTemporaryNodes.after(); + + // and ensure that the nodes are all gone. + TRANSACTION_HELPER.doInTransaction(new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + for (NodeRef node : nodesThatShouldBeDeletedByRule) + { + if (NODE_SERVICE.exists(node)) + { + fail("Node '" + NODE_SERVICE.getProperty(node, ContentModel.PROP_NAME) + "' still exists."); + } + } + return null; + } + }); + } }