diff --git a/core/pom.xml b/core/pom.xml index 5205f6dc37..e04589452a 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -128,8 +128,8 @@ test - commons-dbcp - commons-dbcp + org.apache.commons + commons-dbcp2 test diff --git a/core/src/main/java/org/alfresco/util/transaction/ConnectionPoolException.java b/core/src/main/java/org/alfresco/util/transaction/ConnectionPoolException.java index c2886846c8..6c6f496f8c 100644 --- a/core/src/main/java/org/alfresco/util/transaction/ConnectionPoolException.java +++ b/core/src/main/java/org/alfresco/util/transaction/ConnectionPoolException.java @@ -21,7 +21,7 @@ package org.alfresco.util.transaction; import org.alfresco.error.AlfrescoRuntimeException; /** - * Exception wraps {@link java.util.NoSuchElementException} from {@link org.apache.commons.dbcp.BasicDataSource} + * Exception wraps {@link java.util.NoSuchElementException} from {@link org.apache.commons.dbcp2.BasicDataSource} * * @author alex.mukha * @since 4.1.9 diff --git a/pom.xml b/pom.xml index ed815a8ff9..3c4d54bb4f 100644 --- a/pom.xml +++ b/pom.xml @@ -65,7 +65,7 @@ 1.70 3.11.2 20211205 - 1.4-DBCP330 + 2.9.0 2.11.0 2.8.5 4.5.13 @@ -75,7 +75,7 @@ 1.7.35 0.16 3.0.9 - 2.2.1 + 2.2.1 5.6.1 7.7.10 4.1.2 @@ -755,8 +755,8 @@ ${dependency.mockito-core.version} - commons-dbcp - commons-dbcp + org.apache.commons + commons-dbcp2 ${dependency.commons-dbcp.version} diff --git a/repository/pom.xml b/repository/pom.xml index 8ea8d34c42..d9d0f8b416 100644 --- a/repository/pom.xml +++ b/repository/pom.xml @@ -77,8 +77,8 @@ - commons-dbcp - commons-dbcp + org.apache.commons + commons-dbcp2 commons-fileupload diff --git a/repository/src/main/java/org/alfresco/heartbeat/ConfigurationDataCollector.java b/repository/src/main/java/org/alfresco/heartbeat/ConfigurationDataCollector.java index 3fb841eaf8..6a67c52235 100644 --- a/repository/src/main/java/org/alfresco/heartbeat/ConfigurationDataCollector.java +++ b/repository/src/main/java/org/alfresco/heartbeat/ConfigurationDataCollector.java @@ -51,7 +51,7 @@ import org.alfresco.service.cmr.workflow.WorkflowAdminService; import org.alfresco.service.transaction.TransactionService; import org.alfresco.traitextender.SpringExtensionBundle; import org.alfresco.util.PropertyCheck; -import org.apache.commons.dbcp.BasicDataSource; +import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; @@ -89,7 +89,7 @@ import javax.sql.DataSource; * *
  • db: Database configuration * *
  • *
  • authentication: Authentication configuration. @@ -326,7 +326,7 @@ public class ConfigurationDataCollector extends HBBaseDataCollector implements I if (dataSource instanceof BasicDataSource) { Map db = new HashMap<>(); - db.put("maxConnections", ((BasicDataSource) dataSource).getMaxActive()); + db.put("maxConnections", ((BasicDataSource) dataSource).getMaxTotal()); configurationValues.put("db", db); } diff --git a/repository/src/main/java/org/alfresco/heartbeat/SystemUsageDataCollector.java b/repository/src/main/java/org/alfresco/heartbeat/SystemUsageDataCollector.java index 8161947f46..a1bdcc2896 100644 --- a/repository/src/main/java/org/alfresco/heartbeat/SystemUsageDataCollector.java +++ b/repository/src/main/java/org/alfresco/heartbeat/SystemUsageDataCollector.java @@ -31,7 +31,7 @@ import org.alfresco.heartbeat.datasender.HBData; import org.alfresco.heartbeat.jobs.HeartBeatJobScheduler; import org.alfresco.repo.descriptor.DescriptorDAO; import org.alfresco.util.PropertyCheck; -import org.apache.commons.dbcp.BasicDataSource; +import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; diff --git a/repository/src/main/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java b/repository/src/main/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java index b9fd46ea90..d12792fd5d 100644 --- a/repository/src/main/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java +++ b/repository/src/main/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java @@ -35,6 +35,7 @@ import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Locale; diff --git a/repository/src/main/java/org/alfresco/repo/domain/node/NodeDAO.java b/repository/src/main/java/org/alfresco/repo/domain/node/NodeDAO.java index 4b84067ea0..787587761e 100644 --- a/repository/src/main/java/org/alfresco/repo/domain/node/NodeDAO.java +++ b/repository/src/main/java/org/alfresco/repo/domain/node/NodeDAO.java @@ -29,6 +29,7 @@ import java.io.Serializable; import java.util.Collection; import java.util.Date; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; @@ -944,4 +945,49 @@ public interface NodeDAO extends NodeBulkLoader */ public Long getNextTxCommitTime(Long fromCommitTime); + /** + * + * @param maxCommitTime + * @return Iterator over node ids + */ + default public Iterator selectDeletedNodesByCommitTime(long maxCommitTime) + { + throw new UnsupportedOperationException("Not Implemented"); + } + + /** + * Purge the nodes marked as deleted + * @param minAge + * @param deleteBatchSize + * @return the count of nodes deleted in each batch + */ + default public List purgeDeletedNodes(long minAge, int deleteBatchSize) + { + throw new UnsupportedOperationException("This operation is not supported"); + } + + /** + * + * @param maxCommitTime + * @return Iterator over transaction ids + */ + default public Iterator selectUnusedTransactionsByCommitTime(long maxCommitTime) + { + throw new UnsupportedOperationException("Not Implemented"); + } + + + /** + * Purge the transactions of purged nodes + * @param minAge + * @param deleteBatchSize + * @return the count of transactions deleted in each batch + */ + default public List purgeEmptyTransactions(long minAge, int deleteBatchSize) + { + throw new UnsupportedOperationException("This operation is not supported"); + } + + + } diff --git a/repository/src/main/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java b/repository/src/main/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java index c7a04958ec..e1e9e546f9 100644 --- a/repository/src/main/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java +++ b/repository/src/main/java/org/alfresco/repo/domain/node/ibatis/NodeDAOImpl.java @@ -25,16 +25,6 @@ */ package org.alfresco.repo.domain.node.ibatis; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.SortedSet; - import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.ibatis.IdsEntity; import org.alfresco.model.ContentModel; @@ -65,6 +55,7 @@ import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.namespace.QName; import org.alfresco.util.Pair; +import org.apache.ibatis.cursor.Cursor; import org.apache.ibatis.executor.result.DefaultResultContext; import org.apache.ibatis.session.ResultContext; import org.apache.ibatis.session.ResultHandler; @@ -72,6 +63,17 @@ import org.apache.ibatis.session.RowBounds; import org.mybatis.spring.SqlSessionTemplate; import org.springframework.util.Assert; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; + /** * iBatis-specific extension of the Node abstract DAO @@ -166,6 +168,12 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl private static final String SELECT_TXN_MIN_TX_ID_IN_NODE_IDRANGE = "alfresco.node.select_TxnMinTxIdInNodeIdRange"; private static final String SELECT_TXN_MAX_TX_ID_IN_NODE_IDRANGE = "alfresco.node.select_TxnMaxTxIdInNodeIdRange"; private static final String SELECT_TXN_NEXT_TXN_COMMIT_TIME = "select_TxnNextTxnCommitTime"; + private static final String SELECT_NODES_DELETED_BY_TXN_COMMIT_TIME = "alfresco.node.select.select_Deleted_NodesByTxnCommitTime"; + private static final String DELETE_NODES_BY_ID = "alfresco.node.delete_NodesById"; + private static final String DELETE_NODE_PROPS_BY_NODE_ID = "alfresco.node.delete_NodePropsByNodeId"; + private static final String SELECT_TXNS_UNUSED_BY_TXN_COMMIT_TIME = "alfresco.node.select.select_Txns_UnusedByTxnCommitTime"; + private static final String DELETE_TXNS_UNUSED_BY_ID = "alfresco.node.delete_Txns_UnusedById"; + protected QNameDAO qnameDAO; protected DictionaryService dictionaryService; @@ -1794,8 +1802,138 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl return template.selectOne(SELECT_TXN_NEXT_TXN_COMMIT_TIME, fromCommitTimeEntity); } - - + + public Iterator selectDeletedNodesByCommitTime(long maxCommitTime) + { + // Get the deleted nodes + Pair deletedTypePair = qnameDAO.getQName(ContentModel.TYPE_DELETED); + if (deletedTypePair == null) + { + // Nothing to do + return null; + } + + TransactionQueryEntity transactionQueryEntity = new TransactionQueryEntity(); + transactionQueryEntity.setMaxCommitTime(maxCommitTime); + transactionQueryEntity.setTypeQNameId(deletedTypePair.getFirst()); + Cursor cursor = template.selectCursor(SELECT_NODES_DELETED_BY_TXN_COMMIT_TIME, transactionQueryEntity); + + return cursor.iterator(); + } + + public Iterator selectUnusedTransactionsByCommitTime(long maxCommitTime) + { + TransactionQueryEntity maxCommitTimeEntity = new TransactionQueryEntity(); + maxCommitTimeEntity.setMaxCommitTime(maxCommitTime); + Cursor cursor = template.selectCursor(SELECT_TXNS_UNUSED_BY_TXN_COMMIT_TIME, maxCommitTimeEntity); + + return cursor.iterator(); + } + + @Override + public List purgeDeletedNodes(long minAge, int deleteBatchSize) + { + final long maxCommitTime = System.currentTimeMillis() - minAge; + Iterator nodeIdIterator = this.selectDeletedNodesByCommitTime(maxCommitTime); + ArrayList nodeIdList = new ArrayList<>(); + List deleteResult = new ArrayList<>(); + if (isDebugEnabled) + { + logger.debug("nodes selected for deletion, deleteBatchSize:" + deleteBatchSize); + } + while (nodeIdIterator != null && nodeIdIterator.hasNext()) + { + if (deleteBatchSize == nodeIdList.size()) + { + int count = deleteSelectedNodesAndProperties(nodeIdList); + if (isDebugEnabled) + { + logger.debug("nodes deleted:" + count); + } + deleteResult.add("Purged old nodes: " + count); + nodeIdList.clear(); + } + else + { + nodeIdList.add(nodeIdIterator.next()); + } + } + if (nodeIdList.size() > 0) + { + int count = deleteSelectedNodesAndProperties(nodeIdList); + if (isDebugEnabled) + { + logger.debug("remaining nodes deleted:" + count); + } + deleteResult.add("Purged old nodes: " + count); + nodeIdList.clear(); + } + return deleteResult; + } + + public List purgeEmptyTransactions(long minAge, int deleteBatchSize) + { + final long maxCommitTime = System.currentTimeMillis() - minAge; + Iterator transactionIdIterator = this.selectUnusedTransactionsByCommitTime(maxCommitTime); + ArrayList transactionIdList = new ArrayList<>(); + List deleteResult = new ArrayList<>(); + if (isDebugEnabled) + { + logger.debug("transactions selected for deletion, deleteBatchSize:" + deleteBatchSize); + } + while (transactionIdIterator.hasNext()) + { + if (deleteBatchSize == transactionIdList.size()) + { + int count = deleteSelectedTransactions(transactionIdList); + deleteResult.add("Purged old transactions: " + count); + if (isDebugEnabled) + { + logger.debug("transactions deleted:" + count); + } + transactionIdList.clear(); + + } + else + { + transactionIdList.add(transactionIdIterator.next()); + } + } + if (transactionIdList.size() > 0) + { + int count = deleteSelectedTransactions(transactionIdList); + deleteResult.add("Purged old transactions: " + count); + if (isDebugEnabled) + { + logger.debug("final batch of transactions deleted:" + count); + } + transactionIdList.clear(); + } + return deleteResult; + } + + private int deleteSelectedNodesAndProperties(List nodeIdList) + { + int cnt = template.delete(DELETE_NODE_PROPS_BY_NODE_ID, nodeIdList); + if (isDebugEnabled) + { + logger.debug("nodes props deleted:" + cnt); + } + // Finally, remove the nodes + cnt = template.delete(DELETE_NODES_BY_ID, nodeIdList); + if (isDebugEnabled) + { + logger.debug("nodes deleted:" + cnt); + } + return cnt; + } + + private int deleteSelectedTransactions(List transactionIdList) + { + return template.delete(DELETE_TXNS_UNUSED_BY_ID, transactionIdList); + } + + /* * DAO OVERRIDES */ @@ -1936,4 +2074,4 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl assocQName); } } -} \ No newline at end of file +} diff --git a/repository/src/main/java/org/alfresco/repo/node/db/DeletedNodeCleanupWorker.java b/repository/src/main/java/org/alfresco/repo/node/db/DeletedNodeCleanupWorker.java index f32d5edc4e..aa82a4bc65 100644 --- a/repository/src/main/java/org/alfresco/repo/node/db/DeletedNodeCleanupWorker.java +++ b/repository/src/main/java/org/alfresco/repo/node/db/DeletedNodeCleanupWorker.java @@ -50,6 +50,13 @@ public class DeletedNodeCleanupWorker extends AbstractNodeCleanupWorker // of the chunk (in ms). Default is a couple of hours. private int purgeSize = 7200000; // ms + //to determine if we need a time based window deletion of nodes or in fixed size batches. + private String algorithm; + + private int deleteBatchSize; + + private static final String NODE_TABLE_CLEANER_ALG_V2 = "V2"; + /** * Default constructor */ @@ -67,15 +74,57 @@ public class DeletedNodeCleanupWorker extends AbstractNodeCleanupWorker { return Collections.singletonList("Minimum purge age is negative; purge disabled"); } - - List purgedNodes = purgeOldDeletedNodes(minPurgeAgeMs); - List purgedTxns = purgeOldEmptyTransactions(minPurgeAgeMs); - - List allResults = new ArrayList(100); + List purgedNodes, purgedTxns; + + if (NODE_TABLE_CLEANER_ALG_V2.equals(algorithm)) + { + refreshLock(); + if (logger.isDebugEnabled()) + { + logger.debug("DeletedNodeCleanupWorker using batch deletion: About to execute the clean up nodes "); + + } + purgedNodes = purgeOldDeletedNodesV2(minPurgeAgeMs); + if (logger.isDebugEnabled()) + { + logger.debug(purgedNodes); + } + refreshLock(); + if (logger.isDebugEnabled()) + { + logger.debug("DeletedNodeCleanupWorker: About to execute the clean up txns "); + } + + purgedTxns = purgeOldEmptyTransactionsV2(minPurgeAgeMs); + } + else + { + + if (logger.isDebugEnabled()) + { + logger.debug("DeletedNodeCleanupWorker: About to start purgeOldDeletedNodes "); + } + + purgedNodes = purgeOldDeletedNodes(minPurgeAgeMs); + logger.debug(purgedNodes); + + if (logger.isDebugEnabled()) + { + logger.debug("DeletedNodeCleanupWorker: About to start purgeOldEmptyTransactions "); + } + + purgedTxns = purgeOldEmptyTransactions(minPurgeAgeMs); + } + + if (logger.isDebugEnabled()) + { + logger.debug(purgedTxns); + } + + List allResults = new ArrayList<>(100); allResults.addAll(purgedNodes); allResults.addAll(purgedTxns); - - // Done + return allResults; } @@ -110,7 +159,17 @@ public class DeletedNodeCleanupWorker extends AbstractNodeCleanupWorker this.purgeSize = purgeSize; } - /** + public void setAlgorithm(String algorithm) + { + this.algorithm = algorithm; + } + + public void setDeleteBatchSize(int deleteBatchSize) + { + this.deleteBatchSize = deleteBatchSize; + } + + /** * Cleans up deleted nodes that are older than the given minimum age. * * @param minAge the minimum age of a transaction or deleted node @@ -122,10 +181,12 @@ public class DeletedNodeCleanupWorker extends AbstractNodeCleanupWorker final long maxCommitTime = System.currentTimeMillis() - minAge; long fromCommitTime = fromCustomCommitTime; + if (fromCommitTime <= 0L) { fromCommitTime = nodeDAO.getMinTxnCommitTimeForDeletedNodes().longValue(); } + if ( fromCommitTime == 0L ) { String msg = "There are no old nodes to purge."; @@ -134,7 +195,10 @@ public class DeletedNodeCleanupWorker extends AbstractNodeCleanupWorker } long loopPurgeSize = purgeSize; - Long purgeCount = new Long(0); + if(logger.isDebugEnabled()) + { + logger.debug("DeletedNodeCleanupWorker: purgeOldDeletedNodes started "); + } while (true) { // Ensure we keep the lock @@ -153,9 +217,9 @@ public class DeletedNodeCleanupWorker extends AbstractNodeCleanupWorker try { DeleteNodesByTransactionsCallback purgeNodesCallback = new DeleteNodesByTransactionsCallback(nodeDAO, fromCommitTime, toCommitTime); - purgeCount = txnHelper.doInTransaction(purgeNodesCallback, false, true); + Long purgeCount = txnHelper.doInTransaction(purgeNodesCallback, false, true); - if (purgeCount.longValue() > 0) + if (purgeCount > 0) { String msg = "Purged old nodes: \n" + @@ -220,7 +284,8 @@ public class DeletedNodeCleanupWorker extends AbstractNodeCleanupWorker break; } } - + + logger.debug("DeletedNodeCleanupWorker: purgeOldDeletedNodes finished "); // Done return results; } @@ -245,6 +310,10 @@ public class DeletedNodeCleanupWorker extends AbstractNodeCleanupWorker { fromCommitTime = nodeDAO.getMinUnusedTxnCommitTime().longValue(); } + if(logger.isDebugEnabled()) + { + logger.debug("DeletedNodeCleanupWorker: purgeOldEmptyTransactions started "); + } // delete unused transactions in batches of size 'purgeTxnBlockSize' while (true) { @@ -298,14 +367,46 @@ public class DeletedNodeCleanupWorker extends AbstractNodeCleanupWorker } fromCommitTime += purgeSize; - if(fromCommitTime >= maxCommitTime) + if (fromCommitTime >= maxCommitTime) { - break; + break; } } + logger.debug("DeletedNodeCleanupWorker: purgeOldEmptyTransactions finished "); // Done return results; } + + + private List purgeOldDeletedNodesV2(long minAge) + { + refreshLock(); + final List returnList = new ArrayList<>(); + RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper(); + + RetryingTransactionCallback callback = () -> { + returnList.addAll(nodeDAO.purgeDeletedNodes(minAge, deleteBatchSize)); + return null; + }; + + txnHelper.doInTransaction(callback, false, true); + return returnList; + } + + private List purgeOldEmptyTransactionsV2(long minAge) + { + refreshLock(); + final List returnList = new ArrayList<>(); + RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper(); + + RetryingTransactionCallback callback = () -> { + returnList.addAll(nodeDAO.purgeEmptyTransactions(minAge, deleteBatchSize)); + return null; + }; + txnHelper.doInTransaction(callback, false, true); + + return returnList; + } private static abstract class DeleteByTransactionsCallback implements RetryingTransactionCallback { @@ -356,4 +457,5 @@ public class DeletedNodeCleanupWorker extends AbstractNodeCleanupWorker return count; } } -} \ No newline at end of file + +} diff --git a/repository/src/main/java/org/alfresco/repo/tenant/TenantBasicDataSource.java b/repository/src/main/java/org/alfresco/repo/tenant/TenantBasicDataSource.java index 6feb97ce7d..fc9230cf6d 100644 --- a/repository/src/main/java/org/alfresco/repo/tenant/TenantBasicDataSource.java +++ b/repository/src/main/java/org/alfresco/repo/tenant/TenantBasicDataSource.java @@ -1,33 +1,33 @@ -/* - * #%L - * Alfresco Repository - * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited - * %% - * This file is part of the Alfresco software. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - * #L% - */ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ package org.alfresco.repo.tenant; import java.sql.SQLException; -import org.apache.commons.dbcp.BasicDataSource; +import org.apache.commons.dbcp2.BasicDataSource; /** * Experimental @@ -41,7 +41,7 @@ public class TenantBasicDataSource extends BasicDataSource { // tenant-specific this.setUrl(tenantUrl); - this.setMaxActive(tenantMaxActive == -1 ? bds.getMaxActive() : tenantMaxActive); + this.setMaxTotal(tenantMaxActive == -1 ? bds.getMaxTotal() : tenantMaxActive); // defaults/overrides - see also 'baseDefaultDataSource' (core-services-context.xml + repository.properties) @@ -54,7 +54,7 @@ public class TenantBasicDataSource extends BasicDataSource this.setMaxIdle(bds.getMaxIdle()); this.setDefaultAutoCommit(bds.getDefaultAutoCommit()); this.setDefaultTransactionIsolation(bds.getDefaultTransactionIsolation()); - this.setMaxWait(bds.getMaxWait()); + this.setMaxWaitMillis(bds.getMaxWaitMillis()); this.setValidationQuery(bds.getValidationQuery()); this.setTimeBetweenEvictionRunsMillis(bds.getTimeBetweenEvictionRunsMillis()); this.setMinEvictableIdleTimeMillis(bds.getMinEvictableIdleTimeMillis()); @@ -62,7 +62,7 @@ public class TenantBasicDataSource extends BasicDataSource this.setTestOnBorrow(bds.getTestOnBorrow()); this.setTestOnReturn(bds.getTestOnReturn()); this.setTestWhileIdle(bds.getTestWhileIdle()); - this.setRemoveAbandoned(bds.getRemoveAbandoned()); + this.setRemoveAbandonedOnBorrow(bds.getRemoveAbandonedOnBorrow()); this.setRemoveAbandonedTimeout(bds.getRemoveAbandonedTimeout()); this.setPoolPreparedStatements(bds.isPoolPreparedStatements()); this.setMaxOpenPreparedStatements(bds.getMaxOpenPreparedStatements()); diff --git a/repository/src/main/java/org/alfresco/repo/tenant/TenantRoutingDataSource.java b/repository/src/main/java/org/alfresco/repo/tenant/TenantRoutingDataSource.java index adfe2e686c..14ebb1e8f3 100644 --- a/repository/src/main/java/org/alfresco/repo/tenant/TenantRoutingDataSource.java +++ b/repository/src/main/java/org/alfresco/repo/tenant/TenantRoutingDataSource.java @@ -1,28 +1,28 @@ -/* - * #%L - * Alfresco Repository - * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited - * %% - * This file is part of the Alfresco software. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - * #L% - */ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ package org.alfresco.repo.tenant; import java.sql.SQLException; @@ -32,7 +32,7 @@ import java.util.Map; import javax.sql.DataSource; import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.apache.commons.dbcp.BasicDataSource; +import org.apache.commons.dbcp2.BasicDataSource; import org.springframework.extensions.surf.util.ParameterCheck; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; diff --git a/repository/src/main/resources/alfresco/activiti-context.xml b/repository/src/main/resources/alfresco/activiti-context.xml index d07b3967b8..967fa00065 100644 --- a/repository/src/main/resources/alfresco/activiti-context.xml +++ b/repository/src/main/resources/alfresco/activiti-context.xml @@ -30,7 +30,7 @@ 0 - + 1 diff --git a/repository/src/main/resources/alfresco/core-services-context.xml b/repository/src/main/resources/alfresco/core-services-context.xml index 8fb1e46483..b1d852015a 100644 --- a/repository/src/main/resources/alfresco/core-services-context.xml +++ b/repository/src/main/resources/alfresco/core-services-context.xml @@ -156,7 +156,7 @@ - + ${db.driver} @@ -172,7 +172,7 @@ ${db.pool.initial} - + ${db.pool.max} @@ -187,7 +187,7 @@ ${db.txn.isolation} - + ${db.pool.wait.max} @@ -211,7 +211,7 @@ ${db.pool.evict.validate} - + ${db.pool.abandoned.detect} diff --git a/repository/src/main/resources/alfresco/ibatis/alfresco-SqlMapConfig.xml b/repository/src/main/resources/alfresco/ibatis/alfresco-SqlMapConfig.xml index 093df2fb70..9c9f0d0f66 100644 --- a/repository/src/main/resources/alfresco/ibatis/alfresco-SqlMapConfig.xml +++ b/repository/src/main/resources/alfresco/ibatis/alfresco-SqlMapConfig.xml @@ -203,6 +203,7 @@ Inbound settings from iBatis + diff --git a/repository/src/main/resources/alfresco/ibatis/org.alfresco.repo.domain.dialect.Dialect/node-common-SqlMap.xml b/repository/src/main/resources/alfresco/ibatis/org.alfresco.repo.domain.dialect.Dialect/node-common-SqlMap.xml index 0c3e37bb39..2c1ba5bf32 100644 --- a/repository/src/main/resources/alfresco/ibatis/org.alfresco.repo.domain.dialect.Dialect/node-common-SqlMap.xml +++ b/repository/src/main/resources/alfresco/ibatis/org.alfresco.repo.domain.dialect.Dialect/node-common-SqlMap.xml @@ -1505,5 +1505,32 @@ where commit_time_ms > #{minCommitTime} + + + delete from alf_node + where + id IN + + #{item} + + + + + delete from alf_node_properties + where + node_id IN + + #{item} + + + + + delete from alf_transaction + where + id in + + #{item} + + \ No newline at end of file diff --git a/repository/src/main/resources/alfresco/ibatis/org.alfresco.repo.domain.dialect.Dialect/node-delete-SqlMap.xml b/repository/src/main/resources/alfresco/ibatis/org.alfresco.repo.domain.dialect.Dialect/node-delete-SqlMap.xml index b6d00c10a7..2db7806cc3 100644 --- a/repository/src/main/resources/alfresco/ibatis/org.alfresco.repo.domain.dialect.Dialect/node-delete-SqlMap.xml +++ b/repository/src/main/resources/alfresco/ibatis/org.alfresco.repo.domain.dialect.Dialect/node-delete-SqlMap.xml @@ -36,5 +36,5 @@ ) ]]> - + \ No newline at end of file diff --git a/repository/src/main/resources/alfresco/ibatis/org.alfresco.repo.domain.dialect.Dialect/node-select-SqlMap.xml b/repository/src/main/resources/alfresco/ibatis/org.alfresco.repo.domain.dialect.Dialect/node-select-SqlMap.xml new file mode 100644 index 0000000000..52ec37ea9d --- /dev/null +++ b/repository/src/main/resources/alfresco/ibatis/org.alfresco.repo.domain.dialect.Dialect/node-select-SqlMap.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/repository/src/main/resources/alfresco/ibatis/org.alfresco.repo.domain.dialect.MySQLInnoDBDialect/node-delete-SqlMap.xml b/repository/src/main/resources/alfresco/ibatis/org.alfresco.repo.domain.dialect.MySQLInnoDBDialect/node-delete-SqlMap.xml index d555c2b89d..7db68bb25e 100644 --- a/repository/src/main/resources/alfresco/ibatis/org.alfresco.repo.domain.dialect.MySQLInnoDBDialect/node-delete-SqlMap.xml +++ b/repository/src/main/resources/alfresco/ibatis/org.alfresco.repo.domain.dialect.MySQLInnoDBDialect/node-delete-SqlMap.xml @@ -27,5 +27,5 @@ txn.commit_time_ms < #{maxCommitTime}) ]]> - + \ No newline at end of file diff --git a/repository/src/main/resources/alfresco/ibatis/org.alfresco.repo.domain.dialect.MySQLInnoDBDialect/node-select-SqlMap.xml b/repository/src/main/resources/alfresco/ibatis/org.alfresco.repo.domain.dialect.MySQLInnoDBDialect/node-select-SqlMap.xml new file mode 100644 index 0000000000..a8c97446b8 --- /dev/null +++ b/repository/src/main/resources/alfresco/ibatis/org.alfresco.repo.domain.dialect.MySQLInnoDBDialect/node-select-SqlMap.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/repository/src/main/resources/alfresco/node-services-context.xml b/repository/src/main/resources/alfresco/node-services-context.xml index 150c52ded8..518a674888 100644 --- a/repository/src/main/resources/alfresco/node-services-context.xml +++ b/repository/src/main/resources/alfresco/node-services-context.xml @@ -238,8 +238,14 @@ ${index.tracking.purgeSize} + + ${system.node_table_cleaner.algorithm} + + + ${system.node_cleanup.delete_batchSize} + - + diff --git a/repository/src/main/resources/alfresco/repository.properties b/repository/src/main/resources/alfresco/repository.properties index 18260d38f0..df37d71660 100644 --- a/repository/src/main/resources/alfresco/repository.properties +++ b/repository/src/main/resources/alfresco/repository.properties @@ -1246,6 +1246,11 @@ system.delete_not_exists.read_only=false system.delete_not_exists.timeout_seconds=-1 system.prop_table_cleaner.algorithm=V2 +# --Node cleanup batch - default settings +system.node_cleanup.delete_batchSize=1000 +system.node_table_cleaner.algorithm=V1 + + # Configure the system-wide (ACS) settings for direct access urls. # # For Direct Access URLs to be usable on the service-layer, the feature must be enabled both system-wide and on the diff --git a/repository/src/test/java/org/alfresco/AllDBTestsTestSuite.java b/repository/src/test/java/org/alfresco/AllDBTestsTestSuite.java index 1737976f0e..83355816a6 100644 --- a/repository/src/test/java/org/alfresco/AllDBTestsTestSuite.java +++ b/repository/src/test/java/org/alfresco/AllDBTestsTestSuite.java @@ -87,7 +87,8 @@ import org.junit.runners.Suite; org.alfresco.repo.node.cleanup.TransactionCleanupTest.class, org.alfresco.repo.security.person.GetPeopleCannedQueryTest.class, - org.alfresco.repo.domain.schema.script.DeleteNotExistsExecutorTest.class + org.alfresco.repo.domain.schema.script.DeleteNotExistsExecutorTest.class, + org.alfresco.repo.node.cleanup.DeletedNodeBatchCleanupTest.class }) public class AllDBTestsTestSuite { diff --git a/repository/src/test/java/org/alfresco/AppContext03TestSuite.java b/repository/src/test/java/org/alfresco/AppContext03TestSuite.java index bf5836839f..898670d05e 100644 --- a/repository/src/test/java/org/alfresco/AppContext03TestSuite.java +++ b/repository/src/test/java/org/alfresco/AppContext03TestSuite.java @@ -84,6 +84,7 @@ import org.junit.runners.Suite; org.alfresco.repo.node.archive.ArchiveAndRestoreTest.class, org.alfresco.repo.node.db.DbNodeServiceImplTest.class, org.alfresco.repo.node.cleanup.TransactionCleanupTest.class, + org.alfresco.repo.node.cleanup.DeletedNodeBatchCleanupTest.class, org.alfresco.repo.node.db.DbNodeServiceImplPropagationTest.class, }) public class AppContext03TestSuite diff --git a/repository/src/test/java/org/alfresco/heartbeat/ConfigurationDataCollectorTest.java b/repository/src/test/java/org/alfresco/heartbeat/ConfigurationDataCollectorTest.java index 872bd93a87..f2281bd0f3 100644 --- a/repository/src/test/java/org/alfresco/heartbeat/ConfigurationDataCollectorTest.java +++ b/repository/src/test/java/org/alfresco/heartbeat/ConfigurationDataCollectorTest.java @@ -57,7 +57,7 @@ import org.alfresco.service.cmr.workflow.WorkflowAdminService; import org.alfresco.service.descriptor.Descriptor; import org.alfresco.service.transaction.TransactionService; import org.alfresco.traitextender.SpringExtensionBundle; -import org.apache.commons.dbcp.BasicDataSource; +import org.apache.commons.dbcp2.BasicDataSource; import org.junit.Before; import org.junit.Test; diff --git a/repository/src/test/java/org/alfresco/heartbeat/SystemUsageDataCollectorTest.java b/repository/src/test/java/org/alfresco/heartbeat/SystemUsageDataCollectorTest.java index 462c730157..44c7ec6dc0 100644 --- a/repository/src/test/java/org/alfresco/heartbeat/SystemUsageDataCollectorTest.java +++ b/repository/src/test/java/org/alfresco/heartbeat/SystemUsageDataCollectorTest.java @@ -32,7 +32,7 @@ import org.alfresco.heartbeat.jobs.HeartBeatJobScheduler; import org.alfresco.repo.descriptor.DescriptorDAO; import org.alfresco.service.cmr.repository.HBDataCollectorService; import org.alfresco.service.descriptor.Descriptor; -import org.apache.commons.dbcp.BasicDataSource; +import org.apache.commons.dbcp2.BasicDataSource; import org.junit.Before; import org.junit.Test; diff --git a/repository/src/test/java/org/alfresco/repo/node/cleanup/DeletedNodeBatchCleanupTest.java b/repository/src/test/java/org/alfresco/repo/node/cleanup/DeletedNodeBatchCleanupTest.java new file mode 100644 index 0000000000..2a760a2574 --- /dev/null +++ b/repository/src/test/java/org/alfresco/repo/node/cleanup/DeletedNodeBatchCleanupTest.java @@ -0,0 +1,366 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2022 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ + +package org.alfresco.repo.node.cleanup; + +import static java.util.stream.Collectors.toList; +import static java.util.stream.Stream.of; + +import javax.transaction.UserTransaction; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +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.domain.node.ibatis.NodeDAOImpl; +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.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.AuthenticationService; +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.BaseSpringTest; +import org.alfresco.util.testing.category.DBTests; +import org.junit.Before; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.extensions.webscripts.GUID; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; + +@Category({ OwnJVMTestsCategory.class, DBTests.class }) +@DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD) +public class DeletedNodeBatchCleanupTest extends BaseSpringTest +{ + + @Autowired + private AuthenticationService authenticationService; + @Autowired + private NodeDAO nodeDAO; + @Autowired + @Qualifier("node.nodesSharedCache") + private SimpleCache nodesCache; + @Autowired + private DeletedNodeCleanupWorker worker; + @Autowired + private NamespaceService namespaceService; + @Autowired + private TransactionService transactionService; + @Autowired + private NodeService nodeService; + @Autowired + private SearchService searchService; + private RetryingTransactionHelper helper; + private List testNodes; + + @Before + public void before() + { + helper = transactionService.getRetryingTransactionHelper(); + authenticationService.authenticate("admin", "admin".toCharArray()); + + resetWorkerConfig(); + + // create 5 test nodes + final NodeRef companyHome = getCompanyHome(); + testNodes = IntStream.range(0, 5) + .mapToObj(i -> helper.doInTransaction(createNodeCallback(companyHome), false, true)) + .collect(toList()); + + // clean up pre-existing data + helper.doInTransaction(() -> worker.doClean(), false, true); + } + + private void resetWorkerConfig() + { + worker.setMinPurgeAgeDays(0); + worker.setAlgorithm("V2"); + worker.setDeleteBatchSize(20); + } + + private NodeRef getCompanyHome() + { + StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"); + NodeRef storeRoot = nodeService.getRootNode(storeRef); + List nodeRefs = searchService.selectNodes(storeRoot, "/app:company_home", null, namespaceService, + false); + + return nodeRefs.get(0); + } + + private RetryingTransactionCallback createNodeCallback(NodeRef companyHome) + { + return () -> nodeService.createNode( + companyHome, ContentModel.ASSOC_CONTAINS, QName.createQName("test", GUID.generate()), + ContentModel.TYPE_CONTENT).getChildRef(); + } + + private void deleteNodes(NodeRef nodeRef, NodeRef... additionalNodeRefs) + { + Stream.concat(of(nodeRef), of(additionalNodeRefs)) + .forEach(this::deleteNode); + } + + private void deleteNode(NodeRef nodeRef) + { + helper.doInTransaction(new DeleteNode(nodeRef), false, true); + } + + @Test + public void testPurgeNodesDeleted() + { + final NodeRef nodeRef4 = getNode(4); + final NodeRef nodeRef5 = getNode(5); + + // delete nodes 4 and 5 + deleteNodes(nodeRef4, nodeRef5); + + // double-check that node 4 and 5 are present in deleted form + nodesCache.clear(); + assertTrue("Node 4 is deleted but not purged", nodeDAO.getNodeRefStatus(nodeRef4).isDeleted()); + assertTrue("Node 5 is deleted but not purged", nodeDAO.getNodeRefStatus(nodeRef5).isDeleted()); + + worker.doClean(); + + // verify that node 4 and 5 were purged + nodesCache.clear(); + assertNull("Node 4 was not cleaned up", nodeDAO.getNodeRefStatus(nodeRef4)); + assertNull("Node 5 was not cleaned up", nodeDAO.getNodeRefStatus(nodeRef5)); + } + + @Test + public void testNodesDeletedNotPurgedWhenNotAfterPurgeAge() + { + final NodeRef nodeRef1 = getNode(1); + final NodeRef nodeRef2 = getNode(2); + + // delete nodes 1 and 2 + deleteNodes(nodeRef1, nodeRef2); + + // double-check that node 1 and 2 are present in deleted form + nodesCache.clear(); + assertTrue("Node 1 is deleted but not purged", nodeDAO.getNodeRefStatus(nodeRef1).isDeleted()); + assertTrue("Node 2 is deleted but not purged", nodeDAO.getNodeRefStatus(nodeRef2).isDeleted()); + + // run the worker + worker.setMinPurgeAgeDays(1); + worker.doClean(); + + // verify that node 1 and 2 were not purged + nodesCache.clear(); + assertNotNull("Node 1 was cleaned up", nodeDAO.getNodeRefStatus(nodeRef1)); + assertNotNull("Node 2 was cleaned up", nodeDAO.getNodeRefStatus(nodeRef2)); + } + + @Test + public void testPurgeUnusedTransactions() throws Exception + { + // Execute transactions that update a number of nodes. For nodeRef1, all but the last txn will be unused. + final long start = System.currentTimeMillis(); + final Long minTxnId = nodeDAO.getMinTxnId(); + + final Map> txnIds = createTransactions(); + final List txnIds1 = txnIds.get(getNode(1)); + final List txnIds2 = txnIds.get(getNode(2)); + final List txnIds3 = txnIds.get(getNode(3)); + + // Double-check that n4 and n5 are present in deleted form + nodesCache.clear(); + UserTransaction txn = transactionService.getUserTransaction(true); + txn.begin(); + try + { + assertTrue("Node 4 is deleted but not purged", nodeDAO.getNodeRefStatus(getNode(4)).isDeleted()); + assertTrue("Node 5 is deleted but not purged", nodeDAO.getNodeRefStatus(getNode(5)).isDeleted()); + } + finally + { + txn.rollback(); + } + + // run the transaction cleaner + worker.doClean(); + + // Get transactions committed after the test started + RetryingTransactionHelper.RetryingTransactionCallback> getTxnsCallback = () -> ((NodeDAOImpl) nodeDAO).selectTxns( + start, Long.MAX_VALUE, Integer.MAX_VALUE, null, null, true); + List txns = transactionService.getRetryingTransactionHelper() + .doInTransaction(getTxnsCallback, true, false); + + List expectedUnusedTxnIds = new ArrayList<>(10); + expectedUnusedTxnIds.addAll(txnIds1.subList(0, txnIds1.size() - 1)); + + List expectedUsedTxnIds = new ArrayList<>(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 + List unusedTxnsNotPurged = expectedUnusedTxnIds.stream() + .filter(txnId -> containsTransaction(txns, txnId)) + .collect(toList()); + if (!unusedTxnsNotPurged.isEmpty()) + { + fail("Unused transaction(s) were not purged: " + unusedTxnsNotPurged); + } + + long numFoundUnusedTxnIds = expectedUnusedTxnIds.stream() + .filter(txnId -> !containsTransaction(txns, txnId)) + .count(); + assertEquals(9, numFoundUnusedTxnIds); + + // check that the correct transactions remain i.e. all those in txnIds2, txnIds3, txnIds4 and txnIds5 + long numFoundUsedTxnIds = expectedUsedTxnIds.stream() + .filter(txnId -> containsTransaction(txns, txnId)) + .count(); + assertEquals(3, numFoundUsedTxnIds); + + // Get transactions committed after the test started + RetryingTransactionHelper.RetryingTransactionCallback> getTxnsUnusedCallback = () -> nodeDAO.getTxnsUnused( + minTxnId, Long.MAX_VALUE, Integer.MAX_VALUE); + List 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(getNode(4))); + assertNull("Node 5 was not cleaned up", nodeDAO.getNodeRefStatus(getNode(5))); + } + + private boolean containsTransaction(List txns, String txnId) + { + return txns.stream() + .map(Transaction::getChangeTxnId) + .filter(changeTxnId -> changeTxnId.equals(txnId)) + .map(match -> true) + .findFirst() + .orElse(false); + } + + private Map> createTransactions() + { + Map> txnIds = new HashMap<>(); + + UpdateNode updateNode1 = new UpdateNode(getNode(1)); + UpdateNode updateNode2 = new UpdateNode(getNode(2)); + UpdateNode updateNode3 = new UpdateNode(getNode(3)); + DeleteNode deleteNode4 = new DeleteNode(getNode(4)); + DeleteNode deleteNode5 = new DeleteNode(getNode(5)); + + List txnIds1 = new ArrayList<>(); + List txnIds2 = new ArrayList<>(); + List txnIds3 = new ArrayList<>(); + List txnIds4 = new ArrayList<>(); + List txnIds5 = new ArrayList<>(); + txnIds.put(getNode(1), txnIds1); + txnIds.put(getNode(2), txnIds2); + txnIds.put(getNode(3), txnIds3); + txnIds.put(getNode(4), txnIds4); + txnIds.put(getNode(5), 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 class UpdateNode implements RetryingTransactionHelper.RetryingTransactionCallback + { + private final NodeRef nodeRef; + + UpdateNode(NodeRef nodeRef) + { + this.nodeRef = nodeRef; + } + + @Override + public String execute() throws Throwable + { + nodeService.setProperty(nodeRef, ContentModel.PROP_NAME, GUID.generate()); + return AlfrescoTransactionSupport.getTransactionId(); + } + } + + private class DeleteNode implements RetryingTransactionHelper.RetryingTransactionCallback + { + private final NodeRef nodeRef; + + DeleteNode(NodeRef nodeRef) + { + this.nodeRef = nodeRef; + } + + @Override + public String execute() throws Throwable + { + nodeService.addAspect(nodeRef, ContentModel.ASPECT_TEMPORARY, null); + nodeService.deleteNode(nodeRef); + return AlfrescoTransactionSupport.getTransactionId(); + } + } + + private NodeRef getNode(int i) + { + return testNodes.get(i - 1); + } + +} \ No newline at end of file diff --git a/repository/src/test/java/org/alfresco/repo/node/cleanup/TransactionCleanupTest.java b/repository/src/test/java/org/alfresco/repo/node/cleanup/TransactionCleanupTest.java index ea0aee0da3..f345f7728a 100644 --- a/repository/src/test/java/org/alfresco/repo/node/cleanup/TransactionCleanupTest.java +++ b/repository/src/test/java/org/alfresco/repo/node/cleanup/TransactionCleanupTest.java @@ -110,6 +110,8 @@ public class TransactionCleanupTest this.nodesCache = (SimpleCache) ctx.getBean("node.nodesSharedCache"); this.worker = (DeletedNodeCleanupWorker)ctx.getBean("nodeCleanup.deletedNodeCleanup"); this.worker.setMinPurgeAgeDays(0); + this.worker.setAlgorithm("V1"); + this.helper = transactionService.getRetryingTransactionHelper(); authenticationService.authenticate("admin", "admin".toCharArray()); diff --git a/repository/src/test/resources/ibatis/hierarchy-test/hierarchy-test-context.xml b/repository/src/test/resources/ibatis/hierarchy-test/hierarchy-test-context.xml index 2caaecdf80..310e6c4163 100644 --- a/repository/src/test/resources/ibatis/hierarchy-test/hierarchy-test-context.xml +++ b/repository/src/test/resources/ibatis/hierarchy-test/hierarchy-test-context.xml @@ -37,7 +37,7 @@ - + diff --git a/repository/src/test/resources/test-database-context.xml b/repository/src/test/resources/test-database-context.xml index 1f229c8ca9..09825ee3d6 100644 --- a/repository/src/test/resources/test-database-context.xml +++ b/repository/src/test/resources/test-database-context.xml @@ -1,80 +1,80 @@ - - - - - - - - - ${db.driver} - - - ${db.url} - - - ${db.username} - - - ${db.password} - - - ${db.pool.initial} - - - ${db.pool.max} - - - ${db.pool.min} - - - ${db.pool.idle} - - - false - - - ${db.txn.isolation} - - - ${db.pool.wait.max} - - - ${db.pool.validate.query} - - - ${db.pool.evict.interval} - - - ${db.pool.evict.idle.min} - - - ${db.pool.validate.borrow} - - - ${db.pool.validate.return} - - - ${db.pool.evict.validate} - - - ${db.pool.abandoned.detect} - - - ${db.pool.abandoned.time} - - - ${db.pool.statements.enable} - - - ${db.pool.statements.max} - - - - - - - - + + + + + + + + + ${db.driver} + + + ${db.url} + + + ${db.username} + + + ${db.password} + + + ${db.pool.initial} + + + ${db.pool.max} + + + ${db.pool.min} + + + ${db.pool.idle} + + + false + + + ${db.txn.isolation} + + + ${db.pool.wait.max} + + + ${db.pool.validate.query} + + + ${db.pool.evict.interval} + + + ${db.pool.evict.idle.min} + + + ${db.pool.validate.borrow} + + + ${db.pool.validate.return} + + + ${db.pool.evict.validate} + + + ${db.pool.abandoned.detect} + + + ${db.pool.abandoned.time} + + + ${db.pool.statements.enable} + + + ${db.pool.statements.max} + + + + + + + + \ No newline at end of file diff --git a/repository/src/test/resources/test/alfresco/test-database-context.xml b/repository/src/test/resources/test/alfresco/test-database-context.xml index 0b0971a6b5..33e4e5cf31 100644 --- a/repository/src/test/resources/test/alfresco/test-database-context.xml +++ b/repository/src/test/resources/test/alfresco/test-database-context.xml @@ -6,7 +6,7 @@ - ${db.driver} @@ -23,7 +23,7 @@ ${db.pool.initial} - + ${db.pool.max} @@ -38,7 +38,7 @@ ${db.txn.isolation} - + ${db.pool.wait.max} @@ -59,7 +59,7 @@ ${db.pool.evict.validate} - + ${db.pool.abandoned.detect}