diff --git a/search-services/alfresco-solr/src/main/java/org/alfresco/solr/tracker/AbstractTracker.java b/search-services/alfresco-solr/src/main/java/org/alfresco/solr/tracker/AbstractTracker.java index eedc723c2..2a17c7399 100644 --- a/search-services/alfresco-solr/src/main/java/org/alfresco/solr/tracker/AbstractTracker.java +++ b/search-services/alfresco-solr/src/main/java/org/alfresco/solr/tracker/AbstractTracker.java @@ -289,6 +289,11 @@ public abstract class AbstractTracker implements Tracker return this.writeLock; } + public Semaphore getRunLock() { + return this.runLock; + } + + /** * @return Alfresco version Solr was built for */ diff --git a/search-services/alfresco-solr/src/main/java/org/alfresco/solr/tracker/CommitTracker.java b/search-services/alfresco-solr/src/main/java/org/alfresco/solr/tracker/CommitTracker.java index ca6e3b61b..8d3f62e01 100644 --- a/search-services/alfresco-solr/src/main/java/org/alfresco/solr/tracker/CommitTracker.java +++ b/search-services/alfresco-solr/src/main/java/org/alfresco/solr/tracker/CommitTracker.java @@ -22,6 +22,8 @@ package org.alfresco.solr.tracker; import java.util.List; import java.util.Properties; import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicInteger; + import org.alfresco.solr.InformationServer; import org.alfresco.solr.client.SOLRAPIClient; import org.slf4j.Logger; @@ -38,6 +40,7 @@ public class CommitTracker extends AbstractTracker private long lastSearcherOpened; private long commitInterval; private long newSearcherInterval; + private AtomicInteger rollbackCount = new AtomicInteger(0); protected final static Logger log = LoggerFactory.getLogger(CommitTracker.class); @@ -75,6 +78,10 @@ public class CommitTracker extends AbstractTracker return false; } + public int getRollbackCount() { + return rollbackCount.get(); + } + public void maintenance() throws Exception { for(Tracker tracker : trackers) @@ -171,6 +178,7 @@ public class CommitTracker extends AbstractTracker tracker.setRollback(false); tracker.invalidateState(); } + rollbackCount.incrementAndGet(); } } diff --git a/search-services/alfresco-solr/src/test/java/org/alfresco/solr/tracker/AlfrescoSolrTrackerRollbackTest.java b/search-services/alfresco-solr/src/test/java/org/alfresco/solr/tracker/AlfrescoSolrTrackerRollbackTest.java new file mode 100644 index 000000000..5a2d3f327 --- /dev/null +++ b/search-services/alfresco-solr/src/test/java/org/alfresco/solr/tracker/AlfrescoSolrTrackerRollbackTest.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2005-2014 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.solr.tracker; + +import static org.alfresco.solr.AlfrescoSolrUtils.ancestors; +import static org.alfresco.solr.AlfrescoSolrUtils.createGUID; +import static org.alfresco.solr.AlfrescoSolrUtils.getAcl; +import static org.alfresco.solr.AlfrescoSolrUtils.getAclChangeSet; +import static org.alfresco.solr.AlfrescoSolrUtils.getAclReaders; +import static org.alfresco.solr.AlfrescoSolrUtils.getNode; +import static org.alfresco.solr.AlfrescoSolrUtils.getNodeMetaData; +import static org.alfresco.solr.AlfrescoSolrUtils.getTransaction; +import static org.alfresco.solr.AlfrescoSolrUtils.indexAclChangeSet; +import static org.alfresco.solr.AlfrescoSolrUtils.list; + +import java.util.ArrayList; +import java.util.List; +import java.util.Collection; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.search.adaptor.lucene.QueryConstants; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.solr.AbstractAlfrescoSolrTests; +import org.alfresco.solr.AlfrescoCoreAdminHandler; +import org.alfresco.solr.SolrInformationServer; +import org.alfresco.solr.TrackerState; +import org.alfresco.solr.client.Acl; +import org.alfresco.solr.client.AclChangeSet; +import org.alfresco.solr.client.AclReaders; +import org.alfresco.solr.client.Node; +import org.alfresco.solr.client.NodeMetaData; +import org.alfresco.solr.client.SOLRAPIQueueClient; +import org.alfresco.solr.client.StringPropertyValue; +import org.alfresco.solr.client.Transaction; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.lucene.index.Term; +import org.apache.lucene.search.BooleanClause; +import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.LegacyNumericRangeQuery; +import org.apache.lucene.search.TermQuery; +import org.apache.lucene.util.LuceneTestCase; +import org.apache.solr.SolrTestCaseJ4; +import org.apache.solr.common.params.ModifiableSolrParams; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +@LuceneTestCase.SuppressCodecs({"Appending","Lucene3x","Lucene40","Lucene41","Lucene42","Lucene43", "Lucene44", "Lucene45","Lucene46","Lucene47","Lucene48","Lucene49"}) +@SolrTestCaseJ4.SuppressSSL +public class AlfrescoSolrTrackerRollbackTest extends AbstractAlfrescoSolrTests +{ + private static Log logger = LogFactory.getLog(AlfrescoSolrTrackerTest.class); + private static long MAX_WAIT_TIME = 80000; + @BeforeClass + public static void beforeClass() throws Exception + { + initAlfrescoCore("solrconfig-afts.xml", "schema-afts.xml"); + } + + @Before + public void setUp() throws Exception { + // if you override setUp or tearDown, you better callf + // the super classes version + //clearIndex(); + //assertU(commit()); + } + + @After + public void clearQueue() throws Exception { + SOLRAPIQueueClient.nodeMetaDataMap.clear(); + SOLRAPIQueueClient.transactionQueue.clear(); + SOLRAPIQueueClient.aclChangeSetQueue.clear(); + SOLRAPIQueueClient.aclReadersMap.clear(); + SOLRAPIQueueClient.aclMap.clear(); + SOLRAPIQueueClient.nodeMap.clear(); + } + + + @Test + public void testTrackers() throws Exception + { + + AlfrescoCoreAdminHandler alfrescoCoreAdminHandler = (AlfrescoCoreAdminHandler)h.getCore().getCoreDescriptor().getCoreContainer().getMultiCoreHandler(); + TrackerRegistry trackerRegistry = alfrescoCoreAdminHandler.getTrackerRegistry(); + Collection trackers = trackerRegistry.getTrackersForCore(h.coreName); + MetadataTracker metadataTracker = null; + CommitTracker commitTracker = null; + + for(Tracker tracker : trackers) { + if(tracker instanceof MetadataTracker) { + metadataTracker = (MetadataTracker)tracker; + } else if(tracker instanceof CommitTracker) { + commitTracker = (CommitTracker)tracker; + } + } + + /* + * Create and index an AclChangeSet. + */ + + + AclChangeSet aclChangeSet = getAclChangeSet(1); + + Acl acl = getAcl(aclChangeSet); + Acl acl2 = getAcl(aclChangeSet); + + + AclReaders aclReaders = getAclReaders(aclChangeSet, acl, list("joel"), list("phil"), null); + AclReaders aclReaders2 = getAclReaders(aclChangeSet, acl2, list("jim"), list("phil"), null); + + + indexAclChangeSet(aclChangeSet, + list(acl, acl2), + list(aclReaders, aclReaders2)); + + + //Check for the ACL state stamp. + BooleanQuery.Builder builder = new BooleanQuery.Builder(); + builder.add(new BooleanClause(new TermQuery(new Term(QueryConstants.FIELD_SOLR4_ID, "TRACKER!STATE!ACLTX")), BooleanClause.Occur.MUST)); + builder.add(new BooleanClause(LegacyNumericRangeQuery.newLongRange(QueryConstants.FIELD_S_ACLTXID, aclChangeSet.getId(), aclChangeSet.getId() + 1, true, false), BooleanClause.Occur.MUST)); + BooleanQuery waitForQuery = builder.build(); + waitForDocCount(waitForQuery, 1, MAX_WAIT_TIME); + + /* + * Create and index a Transaction + */ + + //First create a transaction. + Transaction txn = getTransaction(0, 3); + + //Next create two nodes to update for the transaction + Node folderNode = getNode(txn, acl, Node.SolrApiNodeStatus.UPDATED); + Node fileNode = getNode(txn, acl, Node.SolrApiNodeStatus.UPDATED); + Node errorNode = getNode(txn, acl, Node.SolrApiNodeStatus.UPDATED); + + //Next create the NodeMetaData for each node. TODO: Add more metadata + NodeMetaData folderMetaData = getNodeMetaData(folderNode, txn, acl, "mike", null, false); + NodeMetaData fileMetaData = getNodeMetaData(fileNode, txn, acl, "mike", ancestors(folderMetaData.getNodeRef()), false); + //The errorNodeMetaData will cause an exception. + NodeMetaData errorMetaData = getNodeMetaData(errorNode, txn, acl, "lisa", ancestors(folderMetaData.getNodeRef()), true); + + //Index the transaction, nodes, and nodeMetaDatas. + //Note that the content is automatically created by the test framework. + indexTransaction(txn, + list(errorNode, folderNode, fileNode), + list(errorMetaData, folderMetaData, fileMetaData)); + + + //Check for the TXN state stamp. + builder = new BooleanQuery.Builder(); + builder.add(new BooleanClause(new TermQuery(new Term(QueryConstants.FIELD_SOLR4_ID, "TRACKER!STATE!TX")), BooleanClause.Occur.MUST)); + builder.add(new BooleanClause(LegacyNumericRangeQuery.newLongRange(QueryConstants.FIELD_S_TXID, txn.getId(), txn.getId() + 1, true, false), BooleanClause.Occur.MUST)); + waitForQuery = builder.build(); + + waitForDocCount(waitForQuery, 1, MAX_WAIT_TIME); + + waitForDocCount(new TermQuery(new Term("content@s___t@{http://www.alfresco.org/model/content/1.0}content", "world")), 2, MAX_WAIT_TIME); + waitForDocCount(new TermQuery(new Term("content@s___t@{http://www.alfresco.org/model/content/1.0}content", Long.toString(fileNode.getId()))), 1, MAX_WAIT_TIME); + + //Stop the commit tracker + commitTracker.getRunLock().acquire(); + + Transaction rollbackTxn = getTransaction(0, 1); + + Node rollbackNode = getNode(rollbackTxn, acl, Node.SolrApiNodeStatus.UPDATED); + + NodeMetaData rollbackMetaData = getNodeMetaData(rollbackNode, rollbackTxn, acl, "mike", null, false); + + indexTransaction(rollbackTxn, + list(rollbackNode), + list(rollbackMetaData)); + + metadataTracker.setRollback(true); + commitTracker.getRunLock().release(); + + while(commitTracker.getRollbackCount() == 0) { + Thread.sleep(1000); + } + + //The rollback occurred + //Let's add another node + + Transaction afterRollbackTxn = getTransaction(0, 1); + + Node afterRollbackNode = getNode(afterRollbackTxn, acl, Node.SolrApiNodeStatus.UPDATED); + + //Next create the NodeMetaData for each node. TODO: Add more metadata + NodeMetaData afterRollbackMetaData = getNodeMetaData(afterRollbackNode, txn, acl, "mike", null, false); + + //Index the transaction, nodes, and nodeMetaDatas. + //Note that the content is automatically created by the test framework. + indexTransaction(afterRollbackTxn, + list(afterRollbackNode), + list(afterRollbackMetaData)); + + //Wait for the node to appear + //Assert the rolled back transaction is not in the index. + + waitForDocCount(new TermQuery(new Term("content@s___t@{http://www.alfresco.org/model/content/1.0}content", "world")), 3, MAX_WAIT_TIME); + waitForDocCount(new TermQuery(new Term("content@s___t@{http://www.alfresco.org/model/content/1.0}content", Long.toString(afterRollbackNode.getId()))), 1, MAX_WAIT_TIME); + waitForDocCount(new TermQuery(new Term("content@s___t@{http://www.alfresco.org/model/content/1.0}content", Long.toString(rollbackNode.getId()))), 0, MAX_WAIT_TIME); + } +}