diff --git a/config/alfresco/action-services-context.xml b/config/alfresco/action-services-context.xml index 6a55930f53..9cbf25a7b3 100644 --- a/config/alfresco/action-services-context.xml +++ b/config/alfresco/action-services-context.xml @@ -25,7 +25,7 @@ - + diff --git a/config/alfresco/avm-services-context.xml b/config/alfresco/avm-services-context.xml index 62cd4bc666..5adf5c14bb 100644 --- a/config/alfresco/avm-services-context.xml +++ b/config/alfresco/avm-services-context.xml @@ -11,7 +11,7 @@ node - + @@ -20,7 +20,7 @@ layer - + @@ -171,7 +171,7 @@ 1000 - + diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index eea9358eca..7f548934bf 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -254,7 +254,7 @@ - + @@ -296,7 +296,7 @@ - + @@ -319,7 +319,7 @@ - + diff --git a/config/alfresco/content-services-context.xml b/config/alfresco/content-services-context.xml index d6c1a8f5b4..0907691dae 100644 --- a/config/alfresco/content-services-context.xml +++ b/config/alfresco/content-services-context.xml @@ -33,7 +33,7 @@ - + 14 @@ -52,7 +52,7 @@ - + diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index d6945f3750..5bcbdd568d 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -145,7 +145,8 @@ - + + @@ -156,10 +157,10 @@ - + - 20 + ${server.transaction.max-retries} @@ -654,7 +655,7 @@ - + @@ -776,7 +777,7 @@ - + diff --git a/config/alfresco/domain/transaction.properties b/config/alfresco/domain/transaction.properties index f0d6d6d583..f5c65ba4a4 100644 --- a/config/alfresco/domain/transaction.properties +++ b/config/alfresco/domain/transaction.properties @@ -7,3 +7,5 @@ server.transaction.mode.readOnly=PROPAGATION_REQUIRED, readOnly #server.transaction.allow-writes=false server.transaction.mode.default=PROPAGATION_REQUIRED server.transaction.allow-writes=true + +server.transaction.max-retries=20 diff --git a/config/alfresco/import-export-context.xml b/config/alfresco/import-export-context.xml index ca88c658ff..e564233750 100644 --- a/config/alfresco/import-export-context.xml +++ b/config/alfresco/import-export-context.xml @@ -181,7 +181,7 @@ - + @@ -203,7 +203,7 @@ - + diff --git a/config/alfresco/index-recovery-context.xml b/config/alfresco/index-recovery-context.xml index ebe7b15b41..806af263d9 100644 --- a/config/alfresco/index-recovery-context.xml +++ b/config/alfresco/index-recovery-context.xml @@ -7,8 +7,8 @@ - - + + diff --git a/config/alfresco/network-protocol-context.xml b/config/alfresco/network-protocol-context.xml index 490f856887..862e3d3789 100644 --- a/config/alfresco/network-protocol-context.xml +++ b/config/alfresco/network-protocol-context.xml @@ -38,7 +38,7 @@ - + @@ -53,7 +53,7 @@ - + @@ -78,7 +78,7 @@ - + diff --git a/config/alfresco/node-services-context.xml b/config/alfresco/node-services-context.xml index 7d2ac5eb62..542e20757f 100644 --- a/config/alfresco/node-services-context.xml +++ b/config/alfresco/node-services-context.xml @@ -63,7 +63,7 @@ - + diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index 16f5a50861..b161702864 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -51,7 +51,7 @@ - + diff --git a/config/alfresco/workflow-context.xml b/config/alfresco/workflow-context.xml index acbc9d47eb..559ce117b8 100644 --- a/config/alfresco/workflow-context.xml +++ b/config/alfresco/workflow-context.xml @@ -8,7 +8,7 @@ - + @@ -36,7 +36,7 @@ - + diff --git a/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java b/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java index b27bb82865..5c742b5f26 100644 --- a/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java +++ b/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java @@ -38,7 +38,7 @@ import org.alfresco.repo.search.impl.lucene.LuceneQueryParser; import org.alfresco.repo.search.impl.lucene.fts.FullTextSearchIndexer; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.transaction.TransactionComponent; +import org.alfresco.repo.transaction.TransactionServiceImpl; import org.alfresco.repo.transaction.TransactionUtil; import org.alfresco.repo.transaction.TransactionUtil.TransactionWork; import org.alfresco.service.cmr.repository.ChildAssociationRef; @@ -72,7 +72,7 @@ public abstract class AbstractReindexComponent implements IndexRecovery private AuthenticationComponent authenticationComponent; /** provides transactions to atomically index each missed transaction */ - protected TransactionComponent transactionService; + protected TransactionServiceImpl transactionService; /** the component to index the node hierarchy */ protected Indexer indexer; /** the FTS indexer that we will prompt to pick up on any un-indexed text */ @@ -136,9 +136,9 @@ public abstract class AbstractReindexComponent implements IndexRecovery * * @param transactionComponent provide transactions to index each missed transaction */ - public void setTransactionComponent(TransactionComponent transactionComponent) + public void setTransactionService(TransactionServiceImpl transactionService) { - this.transactionService = transactionComponent; + this.transactionService = transactionService; } /** diff --git a/source/java/org/alfresco/repo/node/index/IndexRemoteTransactionTrackerTest.java b/source/java/org/alfresco/repo/node/index/IndexRemoteTransactionTrackerTest.java index 8161acab05..30a93e2cd2 100644 --- a/source/java/org/alfresco/repo/node/index/IndexRemoteTransactionTrackerTest.java +++ b/source/java/org/alfresco/repo/node/index/IndexRemoteTransactionTrackerTest.java @@ -32,7 +32,7 @@ import org.alfresco.repo.node.db.NodeDaoService; import org.alfresco.repo.search.Indexer; import org.alfresco.repo.search.impl.lucene.fts.FullTextSearchIndexer; import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.transaction.TransactionComponent; +import org.alfresco.repo.transaction.TransactionServiceImpl; import org.alfresco.repo.transaction.TransactionUtil; import org.alfresco.repo.transaction.TransactionUtil.TransactionWork; import org.alfresco.service.ServiceRegistry; @@ -89,7 +89,7 @@ public class IndexRemoteTransactionTrackerTest extends TestCase indexTracker.setNodeDaoService(nodeDaoService); indexTracker.setNodeService(nodeService); indexTracker.setSearcher(searchService); - indexTracker.setTransactionComponent((TransactionComponent)transactionService); + indexTracker.setTransactionService((TransactionServiceImpl)transactionService); // authenticate authenticationComponent.setSystemUserAsCurrentUser(); diff --git a/source/java/org/alfresco/repo/node/index/MissingContentReindexComponentTest.java b/source/java/org/alfresco/repo/node/index/MissingContentReindexComponentTest.java index f09bb697d4..11ecb1641e 100644 --- a/source/java/org/alfresco/repo/node/index/MissingContentReindexComponentTest.java +++ b/source/java/org/alfresco/repo/node/index/MissingContentReindexComponentTest.java @@ -35,7 +35,7 @@ import org.alfresco.repo.search.Indexer; import org.alfresco.repo.search.impl.lucene.AbstractLuceneIndexerImpl; import org.alfresco.repo.search.impl.lucene.fts.FullTextSearchIndexer; import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.transaction.TransactionComponent; +import org.alfresco.repo.transaction.TransactionServiceImpl; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.repository.ContentData; @@ -91,7 +91,7 @@ public class MissingContentReindexComponentTest extends TestCase reindexer.setNodeDaoService(nodeDaoService); reindexer.setNodeService(nodeService); reindexer.setSearcher(searchService); - reindexer.setTransactionComponent((TransactionComponent)transactionService); + reindexer.setTransactionService((TransactionServiceImpl)transactionService); // authenticate authenticationComponent.setSystemUserAsCurrentUser(); diff --git a/source/java/org/alfresco/repo/transaction/DummyTransactionService.java b/source/java/org/alfresco/repo/transaction/DummyTransactionService.java index 01f310c64c..c05631dcaf 100644 --- a/source/java/org/alfresco/repo/transaction/DummyTransactionService.java +++ b/source/java/org/alfresco/repo/transaction/DummyTransactionService.java @@ -76,4 +76,13 @@ public class DummyTransactionService implements TransactionService { return txn; } + + public RetryingTransactionHelper getRetryingTransactionHelper() + { + RetryingTransactionHelper helper = new RetryingTransactionHelper(); + helper.setMaxRetries(20); + helper.setTransactionService(this); + helper.setReadOnly(false); + return helper; + } } diff --git a/source/java/org/alfresco/repo/transaction/RetryingTransactionHelper.java b/source/java/org/alfresco/repo/transaction/RetryingTransactionHelper.java index f8c2df7ec1..d038fce041 100644 --- a/source/java/org/alfresco/repo/transaction/RetryingTransactionHelper.java +++ b/source/java/org/alfresco/repo/transaction/RetryingTransactionHelper.java @@ -32,6 +32,7 @@ import javax.transaction.UserTransaction; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.error.ExceptionStackUtil; +import org.alfresco.repo.security.permissions.AccessDeniedException; import org.alfresco.service.transaction.TransactionService; import org.apache.log4j.Logger; import org.hibernate.StaleObjectStateException; @@ -48,6 +49,7 @@ import org.springframework.dao.DeadlockLoserDataAccessException; */ public class RetryingTransactionHelper { + private static final String MSG_READ_ONLY = "permissions.err_read_only"; private static Logger fgLogger = Logger.getLogger(RetryingTransactionHelper.class); /** @@ -74,6 +76,11 @@ public class RetryingTransactionHelper */ private int fMaxRetries; + /** + * Whether the the transactions may only be reads + */ + private boolean readOnly; + /** * Random number generator for retry delays. */ @@ -119,6 +126,14 @@ public class RetryingTransactionHelper fMaxRetries = maxRetries; } + /** + * Set whether this helper only supports read transactions. + */ + public void setReadOnly(boolean readOnly) + { + this.readOnly = readOnly; + } + /** * Execute a callback in a transaction until it succeeds, fails * because of an error not the result of an optimistic locking failure, @@ -172,6 +187,10 @@ public class RetryingTransactionHelper */ public R doInTransaction(RetryingTransactionCallback cb, boolean readOnly, boolean newTransaction) { + if (this.readOnly && !readOnly) + { + throw new AccessDeniedException(MSG_READ_ONLY); + } // Track the last exception caught, so that we // can throw it if we run out of retries. RuntimeException lastException = null; @@ -208,7 +227,9 @@ public class RetryingTransactionHelper { if (count != 0) { - fgLogger.debug("Transaction succeeded after " + count + " retries"); + fgLogger.debug( + "Transaction succeeded after " + count + + " retries on thread " + Thread.currentThread().getName()); } } return result; diff --git a/source/java/org/alfresco/repo/transaction/TransactionComponent.java b/source/java/org/alfresco/repo/transaction/TransactionServiceImpl.java similarity index 82% rename from source/java/org/alfresco/repo/transaction/TransactionComponent.java rename to source/java/org/alfresco/repo/transaction/TransactionServiceImpl.java index 70a1277dca..f318f68ab6 100644 --- a/source/java/org/alfresco/repo/transaction/TransactionComponent.java +++ b/source/java/org/alfresco/repo/transaction/TransactionServiceImpl.java @@ -36,10 +36,11 @@ import org.springframework.transaction.TransactionDefinition; * * @author David Caruana */ -public class TransactionComponent implements TransactionService +public class TransactionServiceImpl implements TransactionService { private PlatformTransactionManager transactionManager; private boolean readOnly = false; + private int maxRetries = 20; /** * Set the transaction manager to use @@ -65,6 +66,17 @@ public class TransactionComponent implements TransactionService { return readOnly; } + + /** + * Set the maximum number of retries that will be done by the + * {@link RetryingTransactionHelper transaction helper}. + * + * @param maxRetries the maximum transaction retries + */ + public void setMaxRetries(int maxRetries) + { + this.maxRetries = maxRetries; + } /** * @see org.springframework.transaction.TransactionDefinition#PROPAGATION_REQUIRED @@ -121,4 +133,16 @@ public class TransactionComponent implements TransactionService TransactionDefinition.TIMEOUT_DEFAULT); return txn; } + + /** + * Creates a new helper instance. It can be reused. + */ + public RetryingTransactionHelper getRetryingTransactionHelper() + { + RetryingTransactionHelper helper = new RetryingTransactionHelper(); + helper.setMaxRetries(maxRetries); + helper.setTransactionService(this); + helper.setReadOnly(readOnly); + return helper; + } } diff --git a/source/java/org/alfresco/repo/transaction/TransactionComponentTest.java b/source/java/org/alfresco/repo/transaction/TransactionServiceImplTest.java similarity index 65% rename from source/java/org/alfresco/repo/transaction/TransactionComponentTest.java rename to source/java/org/alfresco/repo/transaction/TransactionServiceImplTest.java index 8235ff9f94..b859d64e75 100644 --- a/source/java/org/alfresco/repo/transaction/TransactionComponentTest.java +++ b/source/java/org/alfresco/repo/transaction/TransactionServiceImplTest.java @@ -30,6 +30,8 @@ import javax.transaction.UserTransaction; import junit.framework.TestCase; +import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.util.ApplicationContextHelper; @@ -38,24 +40,24 @@ import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.transaction.PlatformTransactionManager; /** - * @see org.alfresco.repo.transaction.TransactionComponent + * @see org.alfresco.repo.transaction.TransactionServiceImpl * * @author Derek Hulley */ -public class TransactionComponentTest extends TestCase +public class TransactionServiceImplTest extends TestCase { private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); private PlatformTransactionManager transactionManager; - private TransactionComponent transactionComponent; + private TransactionServiceImpl transactionService; private NodeService nodeService; public void setUp() throws Exception { transactionManager = (PlatformTransactionManager) ctx.getBean("transactionManager"); - transactionComponent = new TransactionComponent(); - transactionComponent.setTransactionManager(transactionManager); - transactionComponent.setAllowWrite(true); + transactionService = new TransactionServiceImpl(); + transactionService.setTransactionManager(transactionManager); + transactionService.setAllowWrite(true); nodeService = (NodeService) ctx.getBean("dbNodeService"); } @@ -63,12 +65,12 @@ public class TransactionComponentTest extends TestCase public void testPropagatingTxn() throws Exception { // start a transaction - UserTransaction txnOuter = transactionComponent.getUserTransaction(); + UserTransaction txnOuter = transactionService.getUserTransaction(); txnOuter.begin(); String txnIdOuter = AlfrescoTransactionSupport.getTransactionId(); // start a propagating txn - UserTransaction txnInner = transactionComponent.getUserTransaction(); + UserTransaction txnInner = transactionService.getUserTransaction(); txnInner.begin(); String txnIdInner = AlfrescoTransactionSupport.getTransactionId(); @@ -97,12 +99,12 @@ public class TransactionComponentTest extends TestCase public void testNonPropagatingTxn() throws Exception { // start a transaction - UserTransaction txnOuter = transactionComponent.getUserTransaction(); + UserTransaction txnOuter = transactionService.getUserTransaction(); txnOuter.begin(); String txnIdOuter = AlfrescoTransactionSupport.getTransactionId(); // start a propagating txn - UserTransaction txnInner = transactionComponent.getNonPropagatingUserTransaction(); + UserTransaction txnInner = transactionService.getNonPropagatingUserTransaction(); txnInner.begin(); String txnIdInner = AlfrescoTransactionSupport.getTransactionId(); @@ -119,9 +121,9 @@ public class TransactionComponentTest extends TestCase public void testReadOnlyTxn() throws Exception { // start a read-only transaction - transactionComponent.setAllowWrite(false); + transactionService.setAllowWrite(false); - UserTransaction txn = transactionComponent.getUserTransaction(); + UserTransaction txn = transactionService.getUserTransaction(); txn.begin(); // do some writing @@ -135,8 +137,39 @@ public class TransactionComponentTest extends TestCase } catch (InvalidDataAccessApiUsageException e) { + @SuppressWarnings("unused") int i = 0; // expected } } + + public void testGetRetryingTransactionHelper() + { + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + return null; + } + }; + + assertFalse("Retriers must be new instances", + transactionService.getRetryingTransactionHelper() == transactionService.getRetryingTransactionHelper()); + + transactionService.setAllowWrite(true); + transactionService.getRetryingTransactionHelper().doInTransaction(callback, true); + transactionService.getRetryingTransactionHelper().doInTransaction(callback, false); + + transactionService.setAllowWrite(false); + transactionService.getRetryingTransactionHelper().doInTransaction(callback, true); + try + { + transactionService.getRetryingTransactionHelper().doInTransaction(callback, false); + fail("Expected AccessDeniedException when starting to write to a read-only transaction service."); + } + catch (AccessDeniedException e) + { + // Expected + } + } } diff --git a/source/java/org/alfresco/repo/version/VersionServiceImplTest.java b/source/java/org/alfresco/repo/version/VersionServiceImplTest.java index e98699c468..37cdb47ca5 100644 --- a/source/java/org/alfresco/repo/version/VersionServiceImplTest.java +++ b/source/java/org/alfresco/repo/version/VersionServiceImplTest.java @@ -256,7 +256,7 @@ public class VersionServiceImplTest extends BaseVersionStoreTest * Test revert */ @SuppressWarnings("unused") - public void xtestRevert() + public void testRevert() { // Create a versionable node NodeRef versionableNode = createNewVersionableNode(); diff --git a/source/java/org/alfresco/service/transaction/TransactionService.java b/source/java/org/alfresco/service/transaction/TransactionService.java index c647488b49..4935d37894 100644 --- a/source/java/org/alfresco/service/transaction/TransactionService.java +++ b/source/java/org/alfresco/service/transaction/TransactionService.java @@ -26,6 +26,7 @@ package org.alfresco.service.transaction; import javax.transaction.UserTransaction; +import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.service.NotAuditable; import org.alfresco.service.PublicService; @@ -96,4 +97,14 @@ public interface TransactionService */ @NotAuditable UserTransaction getNonPropagatingUserTransaction(boolean readOnly); + + /** + * Get the standard instance of the helper object that supports transaction retrying. + * + * @return + * Returns a helper object that executes units of work transactionally. The helper + * can be reused or altered as required. + */ + @NotAuditable + RetryingTransactionHelper getRetryingTransactionHelper(); }