diff --git a/source/java/org/alfresco/repo/transaction/RetryingTransactionHelper.java b/source/java/org/alfresco/repo/transaction/RetryingTransactionHelper.java index 62dd5fdc35..89e16bad45 100644 --- a/source/java/org/alfresco/repo/transaction/RetryingTransactionHelper.java +++ b/source/java/org/alfresco/repo/transaction/RetryingTransactionHelper.java @@ -208,6 +208,12 @@ public class RetryingTransactionHelper */ private List> extraExceptions; + /** + * A local variable holding the Rollback-only transaction flag which can be set by {@link #setRollbackOnly()} + * and is flushed by the + */ + private static ThreadLocal rollbackOnly = new ThreadLocal(); + /** * Callback interface * @author Derek Hulley @@ -458,7 +464,11 @@ public class RetryingTransactionHelper // Only commit if we 'own' the transaction. if (txn != null) { - if (txn.getStatus() == Status.STATUS_MARKED_ROLLBACK) + // Check if force rollback is on + boolean mustRollBack = isRollbackOnly(); + // This method owns the transaction; make sure we don't leak the state + rollbackOnly.set(null); + if (txn.getStatus() == Status.STATUS_MARKED_ROLLBACK || mustRollBack) { if (logger.isDebugEnabled()) { @@ -511,6 +521,10 @@ public class RetryingTransactionHelper " Exception follows:", e); } + + // make sure we don't leak the state + rollbackOnly.set(null); + // Rollback if we can. if (txn != null) { @@ -670,6 +684,29 @@ public class RetryingTransactionHelper return retryCause; } + /** + * @return Returns true if the current thread transaction was marked as RollbackOnly + */ + public static boolean isRollbackOnly() + { + if (rollbackOnly.get() == null) + { + return false; + } + else + { + return rollbackOnly.get(); + } + } + + /** + * Helper method to set the RollbackOnly state of the transaction in the current thread + */ + public static void setRollbackOnly() + { + rollbackOnly.set(true); + } + /** * Utility method to get the active transaction. The transaction status can be queried and * marked for rollback. diff --git a/source/java/org/alfresco/repo/transaction/RetryingTransactionInterceptor.java b/source/java/org/alfresco/repo/transaction/RetryingTransactionInterceptor.java index 964f465faf..9add2b8e05 100755 --- a/source/java/org/alfresco/repo/transaction/RetryingTransactionInterceptor.java +++ b/source/java/org/alfresco/repo/transaction/RetryingTransactionInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2011 Alfresco Software Limited. + * Copyright (C) 2005-2015 Alfresco Software Limited. * * This file is part of Alfresco * @@ -80,11 +80,13 @@ public class RetryingTransactionInterceptor extends TransactionAspectSupport imp } catch (RuntimeException e) { + RetryingTransactionHelper.setRollbackOnly(); completeTransactionAfterThrowing(txInfo, e); throw e; } catch (Throwable e) { + RetryingTransactionHelper.setRollbackOnly(); // Wrap non-runtime exceptions so that they can be preserved completeTransactionAfterThrowing(txInfo, e); throw new WrapperException(e); diff --git a/source/test-java/org/alfresco/repo/node/NodeServiceTest.java b/source/test-java/org/alfresco/repo/node/NodeServiceTest.java index 0a91746a3c..e2e1f2bd27 100644 --- a/source/test-java/org/alfresco/repo/node/NodeServiceTest.java +++ b/source/test-java/org/alfresco/repo/node/NodeServiceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2012 Alfresco Software Limited. + * Copyright (C) 2005-2015 Alfresco Software Limited. * * This file is part of Alfresco * @@ -703,6 +703,61 @@ public class NodeServiceTest assertEquals("Expected exact number of reference assocs", 1, childAssocRefs.size()); } + /** + * Test for MNT-12501 + */ + @Test public void testRollbackTransaction() + { + final NodeRef workspaceRootNodeRef = nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE); + final String name = GUID.generate(); + final NodeRef[] nodes = new NodeRef[2]; + // Now create 2 nodes with the same name and swallow the exception inside + RetryingTransactionCallback newNodeCallback = new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + Map props = new HashMap(3); + props.put(ContentModel.PROP_NAME, name); + try + { + NodeRef node1 = nodeService.createNode( + workspaceRootNodeRef, + ContentModel.ASSOC_CONTAINS, + QName.createQName(NAMESPACE, "duplicate"), + ContentModel.TYPE_FOLDER, + props).getChildRef(); + nodes[0] = node1; + NodeRef node2 = nodeService.createNode( + workspaceRootNodeRef, + ContentModel.ASSOC_CONTAINS, + QName.createQName(NAMESPACE, "duplicate"), + ContentModel.TYPE_FOLDER, + props).getChildRef(); + nodes[1] = node2; + } + catch (Exception e) + { + // swallow the exception + } + return null; + } + }; + try + { + txnService.getRetryingTransactionHelper().doInTransaction(newNodeCallback); + // The exception is swallowed inside the callback + //fail("Duplicate child node name not detected."); + } + catch (DuplicateChildNodeNameException e) + { + // The exception is swallowed inside the callback + } + assertNotNull("The first node was not created.", nodes[0]); + assertFalse("The node creation should be rolled back.", nodeService.exists(nodes[0])); + assertNull("The duplicate node should not be created.", nodes[1]); + } + private NodeRef setupTestGetChildren(final NodeRef workspaceRootNodeRef, final int numberOfReferences) { RetryingTransactionCallback setupCallback = new RetryingTransactionCallback()