diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index 06ab618468..077e821703 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -51,6 +51,7 @@ + diff --git a/config/alfresco/dbscripts/upgrade/1.4/org.hibernate.dialect.Dialect/AlfrescoSchemaUpdate-1.4-TxnCommitTimeIndex.sql b/config/alfresco/dbscripts/upgrade/1.4/org.hibernate.dialect.Dialect/AlfrescoSchemaUpdate-1.4-TxnCommitTimeIndex.sql new file mode 100644 index 0000000000..bc6c9033eb --- /dev/null +++ b/config/alfresco/dbscripts/upgrade/1.4/org.hibernate.dialect.Dialect/AlfrescoSchemaUpdate-1.4-TxnCommitTimeIndex.sql @@ -0,0 +1,18 @@ +-- +-- Explicit index for alf_transaction.commit_time_ms (Generic Schema 1.4) +-- + +CREATE INDEX idx_commit_time_ms ON alf_transaction (commit_time_ms); +UPDATE alf_transaction SET commit_time_ms = id WHERE commit_time_ms IS NULL; + +-- +-- Record script finish +-- +delete from alf_applied_patch where id = 'patch.db-V1.4-TxnCommitTimeIndex'; +insert into alf_applied_patch + (id, description, fixes_from_schema, fixes_to_schema, applied_to_schema, target_schema, applied_on_date, applied_to_server, was_executed, succeeded, report) + values + ( + 'patch.db-V1.4-TxnCommitTimeIndex', 'Executed script AlfrescoSchemaUpdate-1.4-TxnCommitTimeIndex.sql', + 0, 75, -1, 76, null, 'UNKOWN', 1, 1, 'Script completed' + ); \ No newline at end of file diff --git a/config/alfresco/extension/custom-repository.properties.sample b/config/alfresco/extension/custom-repository.properties.sample index 1e89ff9dc8..4096b57670 100644 --- a/config/alfresco/extension/custom-repository.properties.sample +++ b/config/alfresco/extension/custom-repository.properties.sample @@ -15,6 +15,11 @@ #db.pool.initial=10 #db.pool.max=100 +# +# Sample index tracking frequency +# +#index.tracking.cronExpression=0/5 * * * * ? + # # Property to control whether schema updates are performed automatically. # Updates must be enabled during upgrades as, apart from the static upgrade scripts, diff --git a/config/alfresco/extension/index-tracking-context.xml.sample b/config/alfresco/extension/index-tracking-context.xml.sample index ca543463f1..db0df81b52 100644 --- a/config/alfresco/extension/index-tracking-context.xml.sample +++ b/config/alfresco/extension/index-tracking-context.xml.sample @@ -1,83 +1,4 @@ - - - +Index tracking is now controlled using core properties. +See 'alfresco/repository.properties' and 'alfresco/extension/custom-repository.properties.sample'. - - - - - - - - - - org.alfresco.repo.node.index.IndexRecoveryJob - - - - - - - - - - - - - - - 0,10,20,30,40,50 * * * * ? - - - - - - true - - - - - - - - - - - - - org.alfresco.repo.node.index.IndexRecoveryJob - - - - - - - - - - - - - - - 15 * * * * ? - - - - - - - - - - - - - diff --git a/config/alfresco/index-recovery-context.xml b/config/alfresco/index-recovery-context.xml index 806af263d9..f79dd3f237 100644 --- a/config/alfresco/index-recovery-context.xml +++ b/config/alfresco/index-recovery-context.xml @@ -28,19 +28,18 @@ - - ${index.recovery.mode} + ${index.recovery.mode} + + + ${index.recovery.stopOnError} + + + @@ -60,6 +59,50 @@ + + + + + ${index.tracking.maxTxnDurationMinutes} + + + ${index.tracking.reindexLagMs} + + + ${index.tracking.maxRecordSetSize} + + + + + + + + + org.alfresco.repo.node.index.IndexRecoveryJob + + + + + + + + + + + + + + + ${index.tracking.cronExpression} + + + + + patch.db-V1.4-TxnCommitTimeIndex + patch.schemaUpgradeScript.description + 0 + 110 + 111 + + classpath:alfresco/dbscripts/upgrade/1.4/${db.script.dialect}/AlfrescoSchemaUpdate-1.4-TxnCommitTimeIndex.sql + + + + + + + + + diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index 395c998a4c..e6047e6661 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -13,8 +13,29 @@ dir.indexes=${dir.root}/lucene-indexes # The location for lucene index locks dir.indexes.lock=${dir.indexes}/locks -# The index recovery mode (NONE, VALIDATE, AUTO, FULL) +# ######################################### # +# Index Recovery and Tracking Configuration # +# ######################################### # +# +# Recovery types are: +# NONE: Ignore +# VALIDATE: Checks that the first and last transaction for each store is represented in the indexes +# AUTO: Validates and auto-recovers if validation fails +# FULL: Full index rebuild, processing all transactions in order. The server is temporarily suspended. index.recovery.mode=VALIDATE +# Force FULL recovery to stop when encountering errors +index.recovery.stopOnError=true +# Set the frequency with which the index tracking is triggered. +# By default, this is effectively never, but can be modified as required. +# Examples: +# Once every five seconds: 0/5 * * * * ? +# Once every two seconds : 0/2 * * * * ? +# See http://quartz.sourceforge.net/javadoc/org/quartz/CronTrigger.html +index.tracking.cronExpression=* * * * * ? 2099 +# Other properties. +index.tracking.maxTxnDurationMinutes=60 +index.tracking.reindexLagMs=50 +index.tracking.maxRecordSetSize=1000 # Change the failure behaviour of the configuration checker system.bootstrap.config_check.strict=true diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties index f2aa5f3460..c21f54b95a 100644 --- a/config/alfresco/version.properties +++ b/config/alfresco/version.properties @@ -19,4 +19,4 @@ version.build=@build-number@ # Schema number -version.schema=110 +version.schema=111 diff --git a/source/java/org/alfresco/repo/audit/hibernate/HibernateAuditDAO.java b/source/java/org/alfresco/repo/audit/hibernate/HibernateAuditDAO.java index 4e9dbb45ae..da476c0fd5 100644 --- a/source/java/org/alfresco/repo/audit/hibernate/HibernateAuditDAO.java +++ b/source/java/org/alfresco/repo/audit/hibernate/HibernateAuditDAO.java @@ -484,6 +484,13 @@ public class HibernateAuditDAO extends HibernateDaoSupport implements AuditDAO, getSession().flush(); } + /** + * NO-OP + */ + public void beforeCommit() + { + } + static class SourceKey { String application; diff --git a/source/java/org/alfresco/repo/domain/Transaction.java b/source/java/org/alfresco/repo/domain/Transaction.java index ca1b045fa7..4a5b20698b 100644 --- a/source/java/org/alfresco/repo/domain/Transaction.java +++ b/source/java/org/alfresco/repo/domain/Transaction.java @@ -39,6 +39,10 @@ public interface Transaction public void setChangeTxnId(String changeTxnId); + public Long getCommitTimeMs(); + + public void setCommitTimeMs(Long commitTimeMs); + public Server getServer(); public void setServer(Server server); diff --git a/source/java/org/alfresco/repo/domain/hibernate/PermissionsDaoComponentImpl.java b/source/java/org/alfresco/repo/domain/hibernate/PermissionsDaoComponentImpl.java index 02d30f4dcb..aa97ad444a 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/PermissionsDaoComponentImpl.java +++ b/source/java/org/alfresco/repo/domain/hibernate/PermissionsDaoComponentImpl.java @@ -149,6 +149,13 @@ public class PermissionsDaoComponentImpl extends HibernateDaoSupport implements { getSession().flush(); } + + /** + * NO-OP + */ + public void beforeCommit() + { + } public void setProtocolToACLDAO(Map map) { diff --git a/source/java/org/alfresco/repo/domain/hibernate/Transaction.hbm.xml b/source/java/org/alfresco/repo/domain/hibernate/Transaction.hbm.xml index dd813085bc..fb6d7d14e4 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/Transaction.hbm.xml +++ b/source/java/org/alfresco/repo/domain/hibernate/Transaction.hbm.xml @@ -32,6 +32,7 @@ not-null="false" cascade="none" /> + - + + = :fromTimeInclusive and + txn.commitTimeMs < :toTimeExclusive and + txn.id not in (:excludeTxnIds) + order by + txn.commitTimeMs + ]]> - - + + + - - - select - max(txn.id) - from - org.alfresco.repo.domain.hibernate.NodeStatusImpl as status - join status.transaction as txn - join txn.server as server + org.alfresco.repo.domain.hibernate.TransactionImpl as txn where - server.ipAddress != :serverIpAddress + txn.commitTimeMs >= :fromTimeInclusive and + txn.commitTimeMs < :toTimeExclusive and + txn.id not in (:excludeTxnIds) + order by + txn.commitTimeMs desc + ]]> @@ -100,34 +101,6 @@ org.alfresco.repo.domain.hibernate.TransactionImpl as txn - - :lastTxnId - order by - txn.id - ]]> - - - - :lastTxnId and - server.ipAddress != :serverIpAddress - order by - txn.id - ]]> - - select count(status.key.guid) diff --git a/source/java/org/alfresco/repo/domain/hibernate/TransactionImpl.java b/source/java/org/alfresco/repo/domain/hibernate/TransactionImpl.java index e321cfec29..3423464a77 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/TransactionImpl.java +++ b/source/java/org/alfresco/repo/domain/hibernate/TransactionImpl.java @@ -25,6 +25,7 @@ package org.alfresco.repo.domain.hibernate; import java.io.Serializable; +import java.util.Date; import org.alfresco.repo.domain.Server; import org.alfresco.repo.domain.Transaction; @@ -44,6 +45,7 @@ public class TransactionImpl extends LifecycleAdapter implements Transaction, Se private Long id; private Long version; private String changeTxnId; + private Long commitTimeMs; private Server server; public TransactionImpl() @@ -56,6 +58,7 @@ public class TransactionImpl extends LifecycleAdapter implements Transaction, Se StringBuilder sb = new StringBuilder(50); sb.append("Transaction") .append("[id=").append(id) + .append(", txnTimeMs=").append(new Date(commitTimeMs)) .append(", changeTxnId=").append(changeTxnId) .append("]"); return sb.toString(); @@ -99,6 +102,16 @@ public class TransactionImpl extends LifecycleAdapter implements Transaction, Se this.changeTxnId = changeTransactionId; } + public Long getCommitTimeMs() + { + return commitTimeMs; + } + + public void setCommitTimeMs(Long commitTimeMs) + { + this.commitTimeMs = commitTimeMs; + } + public Server getServer() { return server; diff --git a/source/java/org/alfresco/repo/node/db/NodeDaoService.java b/source/java/org/alfresco/repo/node/db/NodeDaoService.java index 2523ebadbd..fa076e896c 100644 --- a/source/java/org/alfresco/repo/node/db/NodeDaoService.java +++ b/source/java/org/alfresco/repo/node/db/NodeDaoService.java @@ -294,14 +294,31 @@ public interface NodeDaoService public int getNodeCount(final StoreRef storeRef); public Transaction getTxnById(long txnId); - public Transaction getLastTxn(); - public Transaction getLastRemoteTxn(); - public Transaction getLastTxnForStore(final StoreRef storeRef); + /** + * Get all transactions in a given time range. Since time-based retrieval doesn't guarantee uniqueness + * for any given millisecond, a list of optional exclusions may be provided. + * + * @param excludeTxnIds a list of txn IDs to ignore. null is allowed. + */ + public List getTxnsByCommitTimeAscending( + long fromTimeInclusive, + long toTimeExclusive, + int count, + List excludeTxnIds); + /** + * Get all transactions in a given time range. Since time-based retrieval doesn't guarantee uniqueness + * for any given millisecond, a list of optional exclusions may be provided. + * + * @param excludeTxnIds a list of txn IDs to ignore. null is allowed. + */ + public List getTxnsByCommitTimeDescending( + long fromTimeInclusive, + long toTimeExclusive, + int count, + List excludeTxnIds); public int getTxnUpdateCount(final long txnId); public int getTxnDeleteCount(final long txnId); public int getTransactionCount(); - public List getNextTxns(final long lastTxnId, final int count); - public List getNextRemoteTxns(final long lastTxnId, final int count); public List getTxnChangesForStore(final StoreRef storeRef, final long txnId); public List getTxnChanges(final long txnId); } diff --git a/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java b/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java index 071f6fa431..55a5d97c04 100644 --- a/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java @@ -287,6 +287,20 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements return transaction; } + /** + * Ensure that any transaction that might be present is updated to reflect the current time. + */ + public void beforeCommit() + { + Serializable txnId = (Serializable) AlfrescoTransactionSupport.getResource(RESOURCE_KEY_TRANSACTION_ID); + if (txnId != null) + { + // A write was done during the current transaction + Transaction transaction = (Transaction) getHibernateTemplate().get(TransactionImpl.class, txnId); + transaction.setCommitTimeMs(System.currentTimeMillis()); + } + } + /** * Does this Session contain any changes which must be * synchronized with the store? @@ -1417,14 +1431,11 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements /* * Queries for transactions */ - private static final String QUERY_GET_LAST_TXN_ID = "txn.GetLastTxnId"; - private static final String QUERY_GET_LAST_REMOTE_TXN_ID = "txn.GetLastRemoteTxnId"; - private static final String QUERY_GET_LAST_TXN_ID_FOR_STORE = "txn.GetLastTxnIdForStore"; + private static final String QUERY_GET_TXNS_BY_COMMIT_TIME_ASC = "txn.GetTxnsByCommitTimeAsc"; + private static final String QUERY_GET_TXNS_BY_COMMIT_TIME_DESC = "txn.GetTxnsByCommitTimeDesc"; private static final String QUERY_GET_TXN_UPDATE_COUNT_FOR_STORE = "txn.GetTxnUpdateCountForStore"; private static final String QUERY_GET_TXN_DELETE_COUNT_FOR_STORE = "txn.GetTxnDeleteCountForStore"; private static final String QUERY_COUNT_TRANSACTIONS = "txn.CountTransactions"; - private static final String QUERY_GET_NEXT_TXNS = "txn.GetNextTxns"; - private static final String QUERY_GET_NEXT_REMOTE_TXNS = "txn.GetNextRemoteTxns"; private static final String QUERY_GET_TXN_CHANGES_FOR_STORE = "txn.GetTxnChangesForStore"; private static final String QUERY_GET_TXN_CHANGES = "txn.GetTxnChanges"; @@ -1433,78 +1444,6 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements return (Transaction) getSession().get(TransactionImpl.class, new Long(txnId)); } - @SuppressWarnings("unchecked") - public Transaction getLastTxn() - { - HibernateCallback callback = new HibernateCallback() - { - public Object doInHibernate(Session session) - { - Query query = session.getNamedQuery(QUERY_GET_LAST_TXN_ID); - query.setMaxResults(1) - .setReadOnly(true); - return query.uniqueResult(); - } - }; - Long txnId = (Long) getHibernateTemplate().execute(callback); - Transaction txn = null; - if (txnId != null) - { - txn = (Transaction) getSession().get(TransactionImpl.class, txnId); - } - // done - return txn; - } - - @SuppressWarnings("unchecked") - public Transaction getLastRemoteTxn() - { - HibernateCallback callback = new HibernateCallback() - { - public Object doInHibernate(Session session) - { - Query query = session.getNamedQuery(QUERY_GET_LAST_REMOTE_TXN_ID); - query.setString("serverIpAddress", ipAddress) - .setMaxResults(1) - .setReadOnly(true); - return query.uniqueResult(); - } - }; - Long txnId = (Long) getHibernateTemplate().execute(callback); - Transaction txn = null; - if (txnId != null) - { - txn = (Transaction) getSession().get(TransactionImpl.class, txnId); - } - // done - return txn; - } - - @SuppressWarnings("unchecked") - public Transaction getLastTxnForStore(final StoreRef storeRef) - { - HibernateCallback callback = new HibernateCallback() - { - public Object doInHibernate(Session session) - { - Query query = session.getNamedQuery(QUERY_GET_LAST_TXN_ID_FOR_STORE); - query.setString("protocol", storeRef.getProtocol()) - .setString("identifier", storeRef.getIdentifier()) - .setMaxResults(1) - .setReadOnly(true); - return query.uniqueResult(); - } - }; - Long txnId = (Long) getHibernateTemplate().execute(callback); - Transaction txn = null; - if (txnId != null) - { - txn = (Transaction) getSession().get(TransactionImpl.class, txnId); - } - // done - return txn; - } - @SuppressWarnings("unchecked") public int getTxnUpdateCount(final long txnId) { @@ -1559,15 +1498,32 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements return count.intValue(); } + private static final Long TXN_ID_DUD = Long.valueOf(-1L); @SuppressWarnings("unchecked") - public List getNextTxns(final long lastTxnId, final int count) + public List getTxnsByCommitTimeAscending( + final long fromTimeInclusive, + final long toTimeExclusive, + final int count, + List excludeTxnIds) { + // Make sure that we have at least one entry in the exclude list + final List excludeTxnIdsInner = new ArrayList(excludeTxnIds == null ? 1 : excludeTxnIds.size()); + if (excludeTxnIds == null || excludeTxnIds.isEmpty()) + { + excludeTxnIdsInner.add(TXN_ID_DUD); + } + else + { + excludeTxnIdsInner.addAll(excludeTxnIds); + } HibernateCallback callback = new HibernateCallback() { public Object doInHibernate(Session session) { - Query query = session.getNamedQuery(QUERY_GET_NEXT_TXNS); - query.setLong("lastTxnId", lastTxnId) + Query query = session.getNamedQuery(QUERY_GET_TXNS_BY_COMMIT_TIME_ASC); + query.setLong("fromTimeInclusive", fromTimeInclusive) + .setLong("toTimeExclusive", toTimeExclusive) + .setParameterList("excludeTxnIds", excludeTxnIdsInner) .setMaxResults(count) .setReadOnly(true); return query.list(); @@ -1579,15 +1535,30 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements } @SuppressWarnings("unchecked") - public List getNextRemoteTxns(final long lastTxnId, final int count) + public List getTxnsByCommitTimeDescending( + final long fromTimeInclusive, + final long toTimeExclusive, + final int count, + List excludeTxnIds) { + // Make sure that we have at least one entry in the exclude list + final List excludeTxnIdsInner = new ArrayList(excludeTxnIds == null ? 1 : excludeTxnIds.size()); + if (excludeTxnIds == null || excludeTxnIds.isEmpty()) + { + excludeTxnIdsInner.add(TXN_ID_DUD); + } + else + { + excludeTxnIdsInner.addAll(excludeTxnIds); + } HibernateCallback callback = new HibernateCallback() { public Object doInHibernate(Session session) { - Query query = session.getNamedQuery(QUERY_GET_NEXT_REMOTE_TXNS); - query.setLong("lastTxnId", lastTxnId) - .setString("serverIpAddress", ipAddress) + Query query = session.getNamedQuery(QUERY_GET_TXNS_BY_COMMIT_TIME_DESC); + query.setLong("fromTimeInclusive", fromTimeInclusive) + .setLong("toTimeExclusive", toTimeExclusive) + .setParameterList("excludeTxnIds", excludeTxnIdsInner) .setMaxResults(count) .setReadOnly(true); return query.list(); diff --git a/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java b/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java index 1bd4287e7c..ff787c6183 100644 --- a/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java +++ b/source/java/org/alfresco/repo/node/index/AbstractReindexComponent.java @@ -412,6 +412,23 @@ public abstract class AbstractReindexComponent implements IndexRecovery return true; } + /** + * @return Returns false if any one of the transactions aren't in the index. + */ + protected boolean areTxnsInIndex(List txns) + { + for (Transaction txn : txns) + { + long txnId = txn.getId().longValue(); + if (isTxnIdPresentInIndex(txnId) == InIndex.NO) + { + // Missing txn + return false; + } + } + return true; + } + /** * Perform a full reindexing of the given transaction in the context of a completely * new transaction. diff --git a/source/java/org/alfresco/repo/node/index/FullIndexRecoveryComponent.java b/source/java/org/alfresco/repo/node/index/FullIndexRecoveryComponent.java index da09492a73..78e1cf41e3 100644 --- a/source/java/org/alfresco/repo/node/index/FullIndexRecoveryComponent.java +++ b/source/java/org/alfresco/repo/node/index/FullIndexRecoveryComponent.java @@ -24,6 +24,8 @@ */ package org.alfresco.repo.node.index; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import org.alfresco.i18n.I18NUtil; @@ -40,7 +42,10 @@ import org.apache.commons.logging.LogFactory; * Component to check and recover the indexes. By default, the server is * put into read-only mode during the reindex process in order to prevent metadata changes. * This is not critical and can be {@link #setLockServer(boolean) switched off} if the - * server is required immediately. + * server is required immediately. + *

+ * + * @see RecoveryMode * * @author Derek Hulley */ @@ -51,6 +56,7 @@ public class FullIndexRecoveryComponent extends AbstractReindexComponent private static final String MSG_RECOVERY_COMPLETE = "index.recovery.complete"; private static final String MSG_RECOVERY_PROGRESS = "index.recovery.progress"; private static final String MSG_RECOVERY_TERMINATED = "index.recovery.terminated"; + private static final String MSG_RECOVERY_ERROR = "index.recovery.error"; private static Log logger = LogFactory.getLog(FullIndexRecoveryComponent.class); @@ -59,11 +65,17 @@ public class FullIndexRecoveryComponent extends AbstractReindexComponent /** Do nothing - not even a check. */ NONE, /** - * Perform a quick check on the state of the indexes only. + * Perform a quick check on the state of the indexes only. This only checks that the + * first N transactions are present in the index and doesn't guarantee that the indexes + * are wholely consistent. Normally, the indexes are consistent up to a certain time. + * The system does a precautionary index top-up by default, so the last transactions are + * not validated. */ VALIDATE, /** - * Performs a validation and starts a quick recovery, if necessary. + * Performs a validation and starts a recovery if necessary. In this mode, if start + * transactions are missing then FULL mode is enabled. If end transactions are missing + * then the indexes will be "topped up" to bring them up to date. */ AUTO, /** @@ -75,7 +87,16 @@ public class FullIndexRecoveryComponent extends AbstractReindexComponent private RecoveryMode recoveryMode; private boolean lockServer; + private IndexTransactionTracker indexTracker; + private boolean stopOnError; + /** + *

    + *
  • recoveryMode: VALIDATE
  • + *
  • stopOnError: true
  • + *
+ * + */ public FullIndexRecoveryComponent() { recoveryMode = RecoveryMode.VALIDATE; @@ -103,6 +124,29 @@ public class FullIndexRecoveryComponent extends AbstractReindexComponent this.lockServer = lockServer; } + /** + * Set the tracker that will be used for AUTO mode. + * + * @param indexTracker an index tracker component + */ + public void setIndexTracker(IndexTransactionTracker indexTracker) + { + this.indexTracker = indexTracker; + } + + /** + * Set whether a full rebuild should stop in the event of encoutering an error. The default is + * to stop reindexing, and this will lead to the server startup failing when index recovery mode + * is FULL. Sometimes, it is necessary to start the server up regardless of any errors + * with particular nodes. + * + * @param stopOnError true to stop reindexing when an error is encountered. + */ + public void setStopOnError(boolean stopOnError) + { + this.stopOnError = stopOnError; + } + @Override protected void reindexImpl() { @@ -111,42 +155,11 @@ public class FullIndexRecoveryComponent extends AbstractReindexComponent logger.debug("Performing index recovery for type: " + recoveryMode); } - // do we just ignore + // Ignore when NONE if (recoveryMode == RecoveryMode.NONE) { return; } - // check the level of cover required - boolean fullRecoveryRequired = false; - if (recoveryMode == RecoveryMode.FULL) // no validate required - { - fullRecoveryRequired = true; - } - else // validate first - { - Transaction txn = nodeDaoService.getLastTxn(); - if (txn == null) - { - // no transactions - just bug out - return; - } - long txnId = txn.getId(); - InIndex txnInIndex = isTxnIdPresentInIndex(txnId); - if (txnInIndex != InIndex.YES) - { - String msg = I18NUtil.getMessage(ERR_INDEX_OUT_OF_DATE); - logger.warn(msg); - // this store isn't up to date - if (recoveryMode == RecoveryMode.VALIDATE) - { - // the store is out of date - validation failed - } - else if (recoveryMode == RecoveryMode.AUTO) - { - fullRecoveryRequired = true; - } - } - } // put the server into read-only mode for the duration boolean allowWrite = !transactionService.isReadOnly(); @@ -158,10 +171,40 @@ public class FullIndexRecoveryComponent extends AbstractReindexComponent transactionService.setAllowWrite(false); } - // do we need to perform a full recovery - if (fullRecoveryRequired) + // Check that the first and last meaningful transactions are indexed + List startTxns = nodeDaoService.getTxnsByCommitTimeAscending( + Long.MIN_VALUE, Long.MAX_VALUE, 10, null); + boolean startAllPresent = areTxnsInIndex(startTxns); + List endTxns = nodeDaoService.getTxnsByCommitTimeDescending( + Long.MIN_VALUE, Long.MAX_VALUE, 10, null); + boolean endAllPresent = areTxnsInIndex(endTxns); + + // check the level of cover required + switch (recoveryMode) { + case AUTO: + if (!startAllPresent) + { + // Initial transactions are missing - rebuild + performFullRecovery(); + } + else if (!endAllPresent) + { + // Trigger the tracker, which will top up the indexes + indexTracker.reindex(); + } + break; + case VALIDATE: + // Check + if (!startAllPresent || !endAllPresent) + { + // Index is out of date + logger.warn(I18NUtil.getMessage(ERR_INDEX_OUT_OF_DATE)); + } + break; + case FULL: performFullRecovery(); + break; } } finally @@ -182,18 +225,24 @@ public class FullIndexRecoveryComponent extends AbstractReindexComponent // count the transactions int processedCount = 0; - Transaction lastTxn = null; + long fromTimeInclusive = Long.MIN_VALUE; + long toTimeExclusive = Long.MAX_VALUE; + List lastTxnIds = Collections.emptyList(); while(true) { - long lastTxnId = (lastTxn == null) ? -1L : lastTxn.getId().longValue(); - List nextTxns = nodeDaoService.getNextTxns( - lastTxnId, - MAX_TRANSACTIONS_PER_ITERATION); + List nextTxns = nodeDaoService.getTxnsByCommitTimeAscending( + fromTimeInclusive, + toTimeExclusive, + MAX_TRANSACTIONS_PER_ITERATION, + lastTxnIds); + lastTxnIds = new ArrayList(nextTxns.size()); // reindex each transaction for (Transaction txn : nextTxns) { Long txnId = txn.getId(); + // Keep it to ensure we exclude it from the next iteration + lastTxnIds.add(txnId); // check if we have to terminate if (isShuttingDown()) { @@ -201,8 +250,26 @@ public class FullIndexRecoveryComponent extends AbstractReindexComponent logger.warn(msgTerminated); return; } - - reindexTransaction(txnId); + // Allow exception to bubble out or not + if (stopOnError) + { + reindexTransaction(txnId); + } + else + { + try + { + reindexTransaction(txnId); + } + catch (Throwable e) + { + String msgError = I18NUtil.getMessage(MSG_RECOVERY_ERROR, txnId, e.getMessage()); + logger.info(msgError, e); + } + } + // Although we use the same time as this transaction for the next iteration, we also + // make use of the exclusion list to ensure that it doesn't get pulled back again. + fromTimeInclusive = txn.getCommitTimeMs(); // dump a progress report every 10% of the way double before = (double) processedCount / (double) txnCount * 10.0; // 0 - 10 @@ -222,7 +289,6 @@ public class FullIndexRecoveryComponent extends AbstractReindexComponent // there are no more break; } - lastTxn = nextTxns.get(nextTxns.size() - 1); } // done String msgDone = I18NUtil.getMessage(MSG_RECOVERY_COMPLETE); diff --git a/source/java/org/alfresco/repo/node/index/IndexRemoteTransactionTracker.java b/source/java/org/alfresco/repo/node/index/IndexRemoteTransactionTracker.java index dce5c94ea1..c11e4c892a 100644 --- a/source/java/org/alfresco/repo/node/index/IndexRemoteTransactionTracker.java +++ b/source/java/org/alfresco/repo/node/index/IndexRemoteTransactionTracker.java @@ -24,186 +24,41 @@ */ package org.alfresco.repo.node.index; -import java.util.List; - -import org.alfresco.repo.domain.Transaction; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Component to check and recover the indexes. * + * @deprecated Deprecated as of 1.4.5. Use {@linkplain IndexTransactionTracker} + * * @author Derek Hulley */ public class IndexRemoteTransactionTracker extends AbstractReindexComponent { private static Log logger = LogFactory.getLog(IndexRemoteTransactionTracker.class); - private boolean remoteOnly; - private boolean started; - private long currentTxnId; - + /** + * Dumps an error message. + */ public IndexRemoteTransactionTracker() { - remoteOnly = true; - currentTxnId = -1L; + logger.warn( + "The component 'org.alfresco.repo.node.index.IndexRemoteTransactionTracker' " + + "has been replaced by 'org.alfresco.repo.node.index.IndexTransactionTracker' \n" + + "See the extension sample file 'index-tracking-context.xml.sample'. \n" + + "See http://wiki.alfresco.com/wiki/High_Availability_Configuration_V1.4_to_V2.1#Lucene_Index_Synchronization."); } /** - * Set whether or not this component should only track remote transactions. - * By default, it is true, but under certain test conditions, it may - * be desirable to track local transactions too; e.g. during testing of clustering - * when running multiple instances on the same machine. - * - * @param remoteOnly true to reindex only those transactions that were - * committed to the database by a remote server. + * As of release 1.4.5, 2.0.5 and 2.1.1, this property is no longer is use. */ public void setRemoteOnly(boolean remoteOnly) { - this.remoteOnly = remoteOnly; } - - @Override protected void reindexImpl() { - if (!started) - { - // Initialize the starting poing - currentTxnId = getLastIndexedTxn(); - started = true; - } - - if (logger.isDebugEnabled()) - { - logger.debug("Performing index tracking from txn " + currentTxnId); - } - - while (true) - { - // get next transactions to index - List txns = getNextTransactions(currentTxnId); - if (txns.size() == 0) - { - // we've caught up - break; - } - // break out if the VM is shutting down - if (isShuttingDown()) - { - break; - } - // reindex all "foreign" or "local" transactions, one at a time - for (Transaction txn : txns) - { - long txnId = txn.getId(); - reindexTransaction(txnId); - currentTxnId = txnId; - // break out if the VM is shutting down - if (isShuttingDown()) - { - break; - } - } - } - } - - private static final long DECREMENT_COUNT = 10L; - /** - * Finds the last indexed transaction. It works backwards from the - * last index in increments, respecting the {@link #setRemoteOnly(boolean) remoteOnly} - * flag. - * - * @return Returns the last index transaction or -1 if there is none - */ - protected long getLastIndexedTxn() - { - // get the last transaction - Transaction txn = null; - if (remoteOnly) - { - txn = nodeDaoService.getLastRemoteTxn(); - } - else - { - txn = nodeDaoService.getLastTxn(); - } - if (txn == null) - { - // There is no last transaction to use - return -1L; - } - long currentTxnId = txn.getId(); - while (currentTxnId >= 0L) - { - // Check if the current txn is in the index - InIndex txnInIndex = isTxnIdPresentInIndex(currentTxnId); - if (txnInIndex == InIndex.YES) - { - // We found somewhere to start - break; - } - - // Get back in time - long lastCheckTxnId = currentTxnId; - currentTxnId -= DECREMENT_COUNT; - if (currentTxnId < 0L) - { - currentTxnId = -1L; - } - // We don't know if this number we have is a local or remote txn, so get the very next one - Transaction nextTxn = null; - if (remoteOnly) - { - List nextTxns = nodeDaoService.getNextRemoteTxns(currentTxnId, 1); - if (nextTxns.size() > 0) - { - nextTxn = nextTxns.get(0); - } - } - else - { - List nextTxns = nodeDaoService.getNextTxns(currentTxnId, 1); - if (nextTxns.size() > 0) - { - nextTxn = nextTxns.get(0); - } - } - if (nextTxn == null) - { - // There was nothing relevant after this, so keep going back in time - continue; - } - else if (nextTxn.getId() >= lastCheckTxnId) - { - // Decrementing by DECREMENT_COUNT was not enough - continue; - } - // Adjust the last one we looked at to reflect the correct txn id - currentTxnId = nextTxn.getId(); - } - // We are close enough to the beginning, so just go for the first transaction - if (currentTxnId < 0L) - { - currentTxnId = -1L; - } - return currentTxnId; - } - - private static final int MAX_TXN_COUNT = 1000; - private List getNextTransactions(long currentTxnId) - { - List txns = null; - if (remoteOnly) - { - txns = nodeDaoService.getNextRemoteTxns(currentTxnId, MAX_TXN_COUNT); - } - else - { - txns = nodeDaoService.getNextTxns(currentTxnId, MAX_TXN_COUNT); - } - // done - return txns; } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/node/index/IndexTransactionTracker.java b/source/java/org/alfresco/repo/node/index/IndexTransactionTracker.java new file mode 100644 index 0000000000..3865e54d11 --- /dev/null +++ b/source/java/org/alfresco/repo/node/index/IndexTransactionTracker.java @@ -0,0 +1,407 @@ +/* + * Copyright (C) 2005-2006 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.node.index; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.domain.Transaction; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Component to check and recover the indexes. + * + * @author Derek Hulley + */ +public class IndexTransactionTracker extends AbstractReindexComponent +{ + private static Log logger = LogFactory.getLog(IndexTransactionTracker.class); + + private long maxTxnDurationMs; + private long reindexLagMs; + private int maxRecordSetSize; + + private boolean started; + private List previousTxnIds; + private long lastMaxTxnId; + private long fromTimeInclusive; + private Map voids; + + /** + * Set the defaults. + *
    + *
  • Maximum transaction duration: 1 hour
  • + *
  • Reindex lag: 1 second
  • + *
  • Maximum recordset size: 1000
  • + *
+ */ + public IndexTransactionTracker() + { + maxTxnDurationMs = 3600L * 1000L; + reindexLagMs = 1000L; + maxRecordSetSize = 1000; + previousTxnIds = Collections.emptyList(); + lastMaxTxnId = Long.MAX_VALUE; + fromTimeInclusive = -1L; + voids = new TreeMap(); + } + + /** + * Set the expected maximum duration of transaction supported. This value is used to adjust the + * look-back used to detect transactions that committed. Values must be greater than zero. + * + * @param maxTxnDurationMinutes the maximum length of time a transaction will take in minutes + * + * @since 1.4.5, 2.0.5, 2.1.1 + */ + public void setMaxTxnDurationMinutes(long maxTxnDurationMinutes) + { + if (maxTxnDurationMinutes < 1) + { + throw new AlfrescoRuntimeException("Maximum transaction duration must be at least one minute."); + } + this.maxTxnDurationMs = maxTxnDurationMinutes * 60L * 1000L; + } + + /** + * Transaction tracking should lag by the average commit time for a transaction. This will minimize + * the number of holes in the transaction sequence. Values must be greater than zero. + * + * @param reindexLagMs the minimum age of a transaction to be considered by + * the index transaction tracking + * + * @since 1.4.5, 2.0.5, 2.1.1 + */ + public void setReindexLagMs(long reindexLagMs) + { + if (reindexLagMs < 1) + { + throw new AlfrescoRuntimeException("Reindex lag must be at least 1 millisecond."); + } + this.reindexLagMs = reindexLagMs; + } + + /** + * Set the number of transactions to request per query. + */ + public void setMaxRecordSetSize(int maxRecordSetSize) + { + this.maxRecordSetSize = maxRecordSetSize; + } + + @Override + protected void reindexImpl() + { + if (!started) + { + // Make sure that we start clean + voids.clear(); + previousTxnIds = new ArrayList(maxRecordSetSize); + lastMaxTxnId = Long.MAX_VALUE; // So that it is ignored at first + fromTimeInclusive = getStartingTxnCommitTime(); + started = true; + } + + while (true) + { + long toTimeExclusive = System.currentTimeMillis() - reindexLagMs; + + // Check that the voids haven't been filled + fromTimeInclusive = checkVoids(fromTimeInclusive); + + // get next transactions to index + List txns = getNextTransactions(fromTimeInclusive, toTimeExclusive, previousTxnIds); + + if (logger.isDebugEnabled()) + { + String msg = String.format( + "Reindexing %d transactions from %s (%s)", + txns.size(), + (new Date(fromTimeInclusive)).toString(), + txns.isEmpty() ? "---" : txns.get(0).getId().toString()); + logger.debug(msg); + } + + // Reindex the transactions. Voids between the last set of transactions and this + // set will be detected as well. Additionally, the last max transaction will be + // updated by this method. + reindexTransactions(txns); + + // Move the time on. + // Note the subtraction here. Yes, it's odd. But the results of the getNextTransactions + // may be limited by recordset size and it is possible to have multiple transactions share + // the same commit time. If these txns get split up and we exclude the time period, then + // they won't be requeried. The list of previously used transaction IDs is passed back to + // be exluded from the next query. + fromTimeInclusive = toTimeExclusive - 1L; + previousTxnIds.clear(); + for (Transaction txn : txns) + { + previousTxnIds.add(txn.getId()); + } + + // Break out if there were no transactions processed + if (previousTxnIds.isEmpty()) + { + break; + } + + // break out if the VM is shutting down + if (isShuttingDown()) + { + break; + } + } + } + + /** + * Find a transaction time to start indexing from (inclusive). The last recorded transaction by ID + * is taken and the max transaction duration substracted from its commit time. A transaction is + * retrieved for this time and checked for indexing. If it is present, then that value is chosen. + * If not, a step back in time is taken again. This goes on until there are no more transactions + * or a transaction is found in the index. + */ + protected long getStartingTxnCommitTime() + { + // Look back in time by the maximum transaction duration + long toTimeExclusive = System.currentTimeMillis() - maxTxnDurationMs; + long fromTimeInclusive = 0L; + double stepFactor = 1.0D; +found: + while (true) + { + // Get the most recent transaction before the given look-back + List nextTransactions = nodeDaoService.getTxnsByCommitTimeDescending( + 0L, + toTimeExclusive, + 1, + null); + // There are no transactions in that time range + if (nextTransactions.size() == 0) + { + break found; + } + // We found a transaction + Transaction txn = nextTransactions.get(0); + Long txnId = txn.getId(); + long txnCommitTime = txn.getCommitTimeMs(); + // Check that it is in the index + InIndex txnInIndex = isTxnIdPresentInIndex(txnId); + switch (txnInIndex) + { + case YES: + fromTimeInclusive = txnCommitTime; + break found; + default: + // Look further back in time. Step back by the maximum transaction duration and + // increase this step back by a factor of 10% each iteration. + toTimeExclusive = txnCommitTime - (long)(maxTxnDurationMs * stepFactor); + stepFactor *= 1.1D; + continue; + } + } + // We have a starting value + return fromTimeInclusive; + } + + /** + * Voids - otherwise known as 'holes' - in the transaction sequence are timestamped when they are + * discovered. This method discards voids that were timestamped before the given date. It checks + * all remaining voids, passing back the transaction time for the newly-filled void. Otherwise + * the value passed in is passed back. + * + * @param fromTimeInclusive the oldest void to consider + * @return Returns an adjused start position based on any voids being filled + */ + private long checkVoids(long fromTimeInclusive) + { + long maxHistoricalTime = (fromTimeInclusive - maxTxnDurationMs); + long fromTimeAdjusted = fromTimeInclusive; + + List toExpireTxnIds = new ArrayList(1); + // The voids are stored in a sorted map, sorted by the txn ID + for (Long voidTxnId : voids.keySet()) + { + TxnRecord voidTxnRecord = voids.get(voidTxnId); + // Is the transaction around, yet? + Transaction voidTxn = nodeDaoService.getTxnById(voidTxnId); + if (voidTxn == null) + { + // It's still just a void. Shall we expire it? + if (voidTxnRecord.txnCommitTime < maxHistoricalTime) + { + // It's too late for this void + toExpireTxnIds.add(voidTxnId); + } + continue; + } + else + { + if (logger.isDebugEnabled()) + { + logger.debug("Void has become live: " + voidTxn); + } + // We found one that has become a real transaction. + // We don't throw the other voids away. + fromTimeAdjusted = voidTxn.getCommitTimeMs(); + // Break out as sequential rebuilding is required + break; + } + } + // Throw away all the expired ones + for (Long toExpireTxnId : toExpireTxnIds) + { + voids.remove(toExpireTxnId); + } + // Done + return fromTimeAdjusted; + } + + private List getNextTransactions(long fromTimeInclusive, long toTimeExclusive, List previousTxnIds) + { + List txns = nodeDaoService.getTxnsByCommitTimeAscending( + fromTimeInclusive, + toTimeExclusive, + maxRecordSetSize, + previousTxnIds); + // done + return txns; + } + + /** + * Checks that each of the transactions is present in the index. As soon as one is found that + * isn't, all the following transactions will be reindexed. After the reindexing, the sequence + * of transaction IDs will be examined for any voids. These will be recorded. + * + * @param txns transactions ordered by time ascending + * @return returns the + */ + private void reindexTransactions(List txns) + { + if (txns.isEmpty()) + { + return; + } + + Set processedTxnIds = new HashSet(13); + + boolean forceReindex = false; + long minNewTxnId = Long.MAX_VALUE; + long maxNewTxnId = Long.MIN_VALUE; + long maxNewTxnCommitTime = System.currentTimeMillis(); + for (Transaction txn : txns) + { + Long txnId = txn.getId(); + long txnIdLong = txnId.longValue(); + if (txnIdLong < minNewTxnId) + { + minNewTxnId = txnIdLong; + } + if (txnIdLong > maxNewTxnId) + { + maxNewTxnId = txnIdLong; + maxNewTxnCommitTime = txn.getCommitTimeMs(); + } + // Keep track of it for void checking + processedTxnIds.add(txnId); + // Remove this entry from the void list - it is not void + voids.remove(txnId); + + // Reindex the transaction if we are forcing it or if it isn't in the index already + if (forceReindex || isTxnIdPresentInIndex(txnId) == InIndex.NO) + { + // Any indexing means that all the next transactions have to be indexed + forceReindex = true; + try + { + if (logger.isDebugEnabled()) + { + logger.debug("Reindexing transaction: " + txn); + } + // We try the reindex, but for the sake of continuity, have to let it run on + reindexTransaction(txnId); + } + catch (Throwable e) + { + logger.warn("\n" + + "Reindex of transaction failed: \n" + + " Transaction ID: " + txnId + "\n" + + " Error: " + e.getMessage(), + e); + } + } + else + { + if (logger.isDebugEnabled()) + { + logger.debug("Reindex skipping transaction: " + txn); + } + } + } + // We have to search for voids now. Don't start at the min transaction, + // but start at the least of the lastMaxTxnId and minNewTxnId + long voidCheckStartTxnId = (lastMaxTxnId < minNewTxnId ? lastMaxTxnId : minNewTxnId) + 1; + long voidCheckEndTxnId = maxNewTxnId; + // Check for voids in new transactions + for (long i = voidCheckStartTxnId; i <= voidCheckEndTxnId; i++) + { + Long txnId = Long.valueOf(i); + if (processedTxnIds.contains(txnId)) + { + // It is there + continue; + } + + // First make sure that it is a real void. Sometimes, transactions are in the table but don't + // fall within the commit time window that we queried. If they're in the DB AND in the index, + // then they're not really voids and don't need further checks. If they're missing from either, + // then they're voids and must be processed. + Transaction voidTxn = nodeDaoService.getTxnById(txnId); + if (voidTxn != null && isTxnIdPresentInIndex(txnId) != InIndex.NO) + { + // It is a real transaction (not a void) and is already in the index, so just ignore it. + continue; + } + + // Calculate an age for the void. We can't use the current time as that will mean we keep all + // discovered voids, even if they are very old. Rather, we use the commit time of the last transaction + // in the set as it represents the query time for this iteration. + TxnRecord voidRecord = new TxnRecord(); + voidRecord.txnCommitTime = maxNewTxnCommitTime; + voids.put(txnId, voidRecord); + if (logger.isDebugEnabled()) + { + logger.debug("Void detected: " + txnId); + } + } + // Having searched for the nodes, we've recorded all the voids. So move the lastMaxTxnId up. + lastMaxTxnId = voidCheckEndTxnId; + } + + private class TxnRecord + { + private long txnCommitTime; + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/node/index/IndexRemoteTransactionTrackerTest.java b/source/java/org/alfresco/repo/node/index/IndexTransactionTrackerTest.java similarity index 74% rename from source/java/org/alfresco/repo/node/index/IndexRemoteTransactionTrackerTest.java rename to source/java/org/alfresco/repo/node/index/IndexTransactionTrackerTest.java index 30da300800..d83de501bc 100644 --- a/source/java/org/alfresco/repo/node/index/IndexRemoteTransactionTrackerTest.java +++ b/source/java/org/alfresco/repo/node/index/IndexTransactionTrackerTest.java @@ -1,26 +1,18 @@ /* - * Copyright (C) 2005-2007 Alfresco Software Limited. + * Copyright (C) 2005-2006 Alfresco, Inc. * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - - * As a special exception to the terms and conditions of version 2.0 of - * the GPL, you may redistribute this Program in connection with Free/Libre - * and Open Source Software ("FLOSS") applications as described in Alfresco's - * FLOSS exception. You should have recieved a copy of the text describing - * the FLOSS exception, and it is also available here: - * http://www.alfresco.com/legal/licensing" + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. */ package org.alfresco.repo.node.index; @@ -53,7 +45,7 @@ import org.springframework.context.ApplicationContext; * @author Derek Hulley */ @SuppressWarnings("unused") -public class IndexRemoteTransactionTrackerTest extends TestCase +public class IndexTransactionTrackerTest extends TestCase { private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); @@ -66,7 +58,7 @@ public class IndexRemoteTransactionTrackerTest extends TestCase private Indexer indexer; private NodeRef rootNodeRef; - private IndexRemoteTransactionTracker indexTracker; + private IndexTransactionTracker indexTracker; public void setUp() throws Exception { @@ -74,14 +66,14 @@ public class IndexRemoteTransactionTrackerTest extends TestCase searchService = serviceRegistry.getSearchService(); nodeService = serviceRegistry.getNodeService(); fileFolderService = serviceRegistry.getFileFolderService(); - authenticationComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent"); + authenticationComponent = (AuthenticationComponent) ctx.getBean("authenticationComponentImpl"); contentStore = (ContentStore) ctx.getBean("fileContentStore"); ftsIndexer = (FullTextSearchIndexer) ctx.getBean("LuceneFullTextSearchIndexer"); indexer = (Indexer) ctx.getBean("indexerComponent"); NodeDaoService nodeDaoService = (NodeDaoService) ctx.getBean("nodeDaoService"); TransactionService transactionService = serviceRegistry.getTransactionService(); - indexTracker = new IndexRemoteTransactionTracker(); + indexTracker = new IndexTransactionTracker(); indexTracker.setAuthenticationComponent(authenticationComponent); indexTracker.setFtsIndexer(ftsIndexer); indexTracker.setIndexer(indexer); @@ -116,7 +108,7 @@ public class IndexRemoteTransactionTrackerTest extends TestCase return childAssocRef; } }; - ChildAssociationRef childAssocRef = transactionService.getRetryingTransactionHelper().doInTransaction(createNodeWork); + ChildAssociationRef childAssocRef = transactionService.getRetryingTransactionHelper().doInTransaction(createNodeWork, true); } public void testSetup() throws Exception diff --git a/source/java/org/alfresco/repo/transaction/AlfrescoTransactionSupport.java b/source/java/org/alfresco/repo/transaction/AlfrescoTransactionSupport.java index e412ccf16e..c5d138cc6c 100644 --- a/source/java/org/alfresco/repo/transaction/AlfrescoTransactionSupport.java +++ b/source/java/org/alfresco/repo/transaction/AlfrescoTransactionSupport.java @@ -602,6 +602,12 @@ public abstract class AlfrescoTransactionSupport { lucene.prepare(); } + + // Flush the DAOs + for (TransactionalDao dao : daoServices) + { + dao.beforeCommit(); + } } /** diff --git a/source/java/org/alfresco/repo/transaction/TransactionalDao.java b/source/java/org/alfresco/repo/transaction/TransactionalDao.java index e0bcf9f155..9b690962f3 100644 --- a/source/java/org/alfresco/repo/transaction/TransactionalDao.java +++ b/source/java/org/alfresco/repo/transaction/TransactionalDao.java @@ -23,4 +23,11 @@ public interface TransactionalDao * @return true => changes are pending */ public boolean isDirty(); + + /** + * This callback provides a chance for the DAO to do any pre-commit work. + * + * @since 1.4.5 + */ + public void beforeCommit(); }