/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * 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.apache.jackrabbit.core.query.lucene; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import javax.jcr.Node; import javax.jcr.RepositoryException; import javax.jcr.Session; import org.apache.jackrabbit.core.SearchManager; import org.apache.jackrabbit.core.TestHelper; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.query.lucene.hits.AbstractHitCollector; import org.apache.jackrabbit.core.state.NodeState; import org.apache.jackrabbit.test.AbstractJCRTest; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.Term; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.TermQuery; public class SearchIndexConsistencyCheckTest extends AbstractJCRTest { private boolean keepRunning; @Override protected void setUp() throws Exception { super.setUp(); } public void testIndexMissesNode() throws Exception { Session s = getHelper().getSuperuserSession(); SearchManager searchManager = TestHelper.getSearchManager(s); SearchIndex searchIndex = (SearchIndex) searchManager.getQueryHandler(); Node foo = testRootNode.addNode("foo"); testRootNode.getSession().save(); NodeId fooId = new NodeId(foo.getIdentifier()); Iterator<NodeId> remove = Collections.singletonList(fooId).iterator(); Iterator<NodeState> add = Collections.<NodeState>emptyList().iterator(); searchIndex.updateNodes(remove, add); ConsistencyCheck consistencyCheck = searchIndex.runConsistencyCheck(); List<ConsistencyCheckError> errors = consistencyCheck.getErrors(); assertEquals("Expected 1 index consistency error", 1, errors.size()); ConsistencyCheckError error = errors.iterator().next(); assertEquals("Different node was reported to be missing", error.id, fooId); consistencyCheck.repair(false); assertTrue("Index was not repaired properly", searchIndexContainsNode(searchIndex, fooId)); assertTrue("Consistency check still reports errors", searchIndex.runConsistencyCheck().getErrors().isEmpty()); } public void testMissingNodeDoubleCheck() throws Exception { Session s = getHelper().getSuperuserSession(); SearchManager searchManager = TestHelper.getSearchManager(s); SearchIndex searchIndex = (SearchIndex) searchManager.getQueryHandler(); Node foo = testRootNode.addNode("foo"); testRootNode.getSession().save(); NodeId fooId = new NodeId(foo.getIdentifier()); Iterator<NodeId> remove = Collections.singletonList(fooId).iterator(); Iterator<NodeState> add = Collections.<NodeState>emptyList().iterator(); searchIndex.updateNodes(remove, add); ConsistencyCheck consistencyCheck = searchIndex.runConsistencyCheck(); List<ConsistencyCheckError> errors = consistencyCheck.getErrors(); assertEquals("Expected 1 index consistency error", 1, errors.size()); // now add foo to the index again so that double check finds a false positive remove = Collections.<NodeId>emptyList().iterator(); add = Collections.singletonList(new NodeState(fooId, null, null, 1, false)).iterator(); searchIndex.updateNodes(remove, add); consistencyCheck.doubleCheckErrors(); assertTrue("Consistency double check of missing node failed", consistencyCheck.getErrors().isEmpty()); } public void testIndexContainsUnknownNode() throws Exception { Session s = getHelper().getSuperuserSession(); SearchManager searchManager = TestHelper.getSearchManager(s); SearchIndex searchIndex = (SearchIndex) searchManager.getQueryHandler(); NodeId nodeId = new NodeId(0, 0); NodeState nodeState = new NodeState(nodeId, null, null, 1, false); Iterator<NodeId> remove = Collections.<NodeId>emptyList().iterator(); Iterator<NodeState> add = Collections.singletonList(nodeState).iterator(); searchIndex.updateNodes(remove, add); ConsistencyCheck consistencyCheck = searchIndex.runConsistencyCheck(); List<ConsistencyCheckError> errors = consistencyCheck.getErrors(); assertEquals("Expected 1 index consistency error", 1, errors.size()); ConsistencyCheckError error = errors.iterator().next(); assertEquals("Different node was reported to be unknown", error.id, nodeId); consistencyCheck.repair(false); assertFalse("Index was not repaired properly", searchIndexContainsNode(searchIndex, nodeId)); assertTrue("Consistency check still reports errors", searchIndex.runConsistencyCheck().getErrors().isEmpty()); } public void testUnknownNodeDoubleCheck() throws Exception { Session s = getHelper().getSuperuserSession(); SearchManager searchManager = TestHelper.getSearchManager(s); SearchIndex searchIndex = (SearchIndex) searchManager.getQueryHandler(); NodeId nodeId = new NodeId(0, 0); NodeState nodeState = new NodeState(nodeId, null, null, 1, false); Iterator<NodeId> remove = Collections.<NodeId>emptyList().iterator(); Iterator<NodeState> add = Collections.singletonList(nodeState).iterator(); searchIndex.updateNodes(remove, add); ConsistencyCheck consistencyCheck = searchIndex.runConsistencyCheck(); List<ConsistencyCheckError> errors = consistencyCheck.getErrors(); assertEquals("Expected 1 index consistency error", 1, errors.size()); // now remove the unknown node from the index again so that double check finds a false positive remove = Collections.singletonList(nodeId).iterator(); add = Collections.<NodeState>emptyList().iterator(); searchIndex.updateNodes(remove, add); consistencyCheck.doubleCheckErrors(); assertTrue("Consistency double check of deleted node failed", consistencyCheck.getErrors().isEmpty()); } public void testIndexMissesAncestor() throws Exception { Session s = getHelper().getSuperuserSession(); SearchManager searchManager = TestHelper.getSearchManager(s); SearchIndex searchIndex = (SearchIndex) searchManager.getQueryHandler(); Node foo = testRootNode.addNode("foo"); Node bar = foo.addNode("bar"); testRootNode.getSession().save(); NodeId fooId = new NodeId(foo.getIdentifier()); NodeId barId = new NodeId(bar.getIdentifier()); Iterator<NodeId> remove = Collections.singletonList(fooId).iterator(); Iterator<NodeState> add = Collections.<NodeState>emptyList().iterator(); searchIndex.updateNodes(remove, add); ConsistencyCheck consistencyCheck = searchIndex.runConsistencyCheck(); List<ConsistencyCheckError> errors = consistencyCheck.getErrors(); assertEquals("Expected 2 index consistency errors", 2, errors.size()); assertEquals("Different node was reported to have missing parent", errors.get(0).id, barId); assertEquals("Different node was reported to be missing", errors.get(1).id, fooId); consistencyCheck.repair(false); assertTrue("Index was not repaired properly", searchIndexContainsNode(searchIndex, fooId)); assertTrue("Consistency check still reports errors", searchIndex.runConsistencyCheck().getErrors().isEmpty()); } public void testMissingAncestorDoubleCheck() throws Exception { Session s = getHelper().getSuperuserSession(); SearchManager searchManager = TestHelper.getSearchManager(s); SearchIndex searchIndex = (SearchIndex) searchManager.getQueryHandler(); Node foo = testRootNode.addNode("foo"); foo.addNode("bar"); testRootNode.getSession().save(); NodeId fooId = new NodeId(foo.getIdentifier()); Iterator<NodeId> remove = Collections.singletonList(fooId).iterator(); Iterator<NodeState> add = Collections.<NodeState>emptyList().iterator(); searchIndex.updateNodes(remove, add); ConsistencyCheck consistencyCheck = searchIndex.runConsistencyCheck(); List<ConsistencyCheckError> errors = consistencyCheck.getErrors(); assertEquals("Expected 2 index consistency errors", 2, errors.size()); remove = Collections.<NodeId>emptyList().iterator(); add = Collections.singletonList(new NodeState(fooId, null, null, 1, true)).iterator(); searchIndex.updateNodes(remove, add); consistencyCheck.doubleCheckErrors(); assertTrue("Consistency double check of missing ancestor failed", consistencyCheck.getErrors().isEmpty()); } public void testIndexContainsMultipleEntries() throws Exception { Session s = getHelper().getSuperuserSession(); SearchManager searchManager = TestHelper.getSearchManager(s); SearchIndex searchIndex = (SearchIndex) searchManager.getQueryHandler(); Node foo = testRootNode.addNode("foo"); testRootNode.getSession().save(); NodeId fooId = new NodeId(foo.getIdentifier()); NodeState nodeState = new NodeState(fooId, null, null, 1, false); Iterator<NodeId> remove = Collections.<NodeId>emptyList().iterator(); Iterator<NodeState> add = Arrays.asList(nodeState).iterator(); searchIndex.updateNodes(remove, add); searchIndex.flush(); remove = Collections.<NodeId>emptyList().iterator(); add = Arrays.asList(nodeState).iterator(); searchIndex.updateNodes(remove, add); ConsistencyCheck consistencyCheck = searchIndex.runConsistencyCheck(); List<ConsistencyCheckError> errors = consistencyCheck.getErrors(); assertEquals("Expected 1 index consistency error", 1, errors.size()); assertEquals("Different node was reported to be duplicate", errors.get(0).id, fooId); consistencyCheck.doubleCheckErrors(); errors = consistencyCheck.getErrors(); assertEquals("Expected 1 index consistency error after double check", 1, errors.size()); assertEquals("Different node was reported to be duplicate after double check", errors.get(0).id, fooId); consistencyCheck.repair(false); assertTrue("Index was not repaired properly", searchIndexContainsNode(searchIndex, fooId)); consistencyCheck.doubleCheckErrors(); errors = consistencyCheck.getErrors(); assertTrue("Consistency double check of multiple entries failed", errors.isEmpty()); assertTrue("Consistency check still finds errors", searchIndex.runConsistencyCheck().getErrors().isEmpty()); } /** * Stress test on the double check mechanism */ public void testDoubleCheckStressTest() throws Exception { Thread t = new Thread(new Runnable() { private Session s = getHelper().getReadWriteSession(); @Override public void run() { while (keepRunning) { try { Node foo = s.getRootNode().getNode(testPath).addNode("foo"); s.save(); foo.remove(); s.save(); } catch (RepositoryException e) { System.out.println(e); } } } }); Session s = getHelper().getSuperuserSession(); SearchManager searchManager = TestHelper.getSearchManager(s); SearchIndex searchIndex = (SearchIndex) searchManager.getQueryHandler(); keepRunning = true; try { t.start(); Thread.sleep(100); for (int i = 100; i > 0; i--) { final ConsistencyCheck consistencyCheck = searchIndex.runConsistencyCheck(); consistencyCheck.doubleCheckErrors(); final List<ConsistencyCheckError> errors = consistencyCheck.getErrors(); assertTrue(errors.isEmpty()); } } finally { keepRunning = false; } } private boolean searchIndexContainsNode(SearchIndex searchIndex, NodeId nodeId) throws IOException { final List<Integer> docs = new ArrayList<Integer>(1); final IndexReader reader = searchIndex.getIndexReader(); try { IndexSearcher searcher = new IndexSearcher(reader); try { Query q = new TermQuery(new Term(FieldNames.UUID, nodeId.toString())); searcher.search(q, new AbstractHitCollector() { @Override protected void collect(final int doc, final float score) { docs.add(doc); } }); } finally { searcher.close(); } } finally { Util.closeOrRelease(reader); } return !docs.isEmpty(); } }