--- /dev/null
+package org.apache.lucene.index;
+
+/**
+ * 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.
+ */
+
+
+import java.io.IOException;
+import java.util.BitSet;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.lucene.analysis.MockAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.store.Directory;
+
+/**
+ * Test class to illustrate using IndexDeletionPolicy to provide multi-level rollback capability.
+ * This test case creates an index of records 1 to 100, introducing a commit point every 10 records.
+ *
+ * A "keep all" deletion policy is used to ensure we keep all commit points for testing purposes
+ */
+
+public class TestTransactionRollback extends LuceneTestCase {
+
+ private static final String FIELD_RECORD_ID = "record_id";
+ private Directory dir;
+
+ //Rolls back index to a chosen ID
+ private void rollBackLast(int id) throws Exception {
+
+ // System.out.println("Attempting to rollback to "+id);
+ String ids="-"+id;
+ IndexCommit last=null;
+ Collection<IndexCommit> commits = IndexReader.listCommits(dir);
+ for (Iterator<IndexCommit> iterator = commits.iterator(); iterator.hasNext();) {
+ IndexCommit commit = iterator.next();
+ Map<String,String> ud=commit.getUserData();
+ if (ud.size() > 0)
+ if (ud.get("index").endsWith(ids))
+ last=commit;
+ }
+
+ if (last==null)
+ throw new RuntimeException("Couldn't find commit point "+id);
+
+ IndexWriter w = new IndexWriter(dir, newIndexWriterConfig(
+ TEST_VERSION_CURRENT, new MockAnalyzer(random)).setIndexDeletionPolicy(
+ new RollbackDeletionPolicy(id)).setIndexCommit(last));
+ Map<String,String> data = new HashMap<String,String>();
+ data.put("index", "Rolled back to 1-"+id);
+ w.commit(data);
+ w.close();
+ }
+
+ public void testRepeatedRollBacks() throws Exception {
+
+ int expectedLastRecordId=100;
+ while (expectedLastRecordId>10) {
+ expectedLastRecordId -=10;
+ rollBackLast(expectedLastRecordId);
+
+ BitSet expecteds = new BitSet(100);
+ expecteds.set(1,(expectedLastRecordId+1),true);
+ checkExpecteds(expecteds);
+ }
+ }
+
+ private void checkExpecteds(BitSet expecteds) throws Exception {
+ IndexReader r = IndexReader.open(dir, true);
+
+ //Perhaps not the most efficient approach but meets our needs here.
+ for (int i = 0; i < r.maxDoc(); i++) {
+ if(!r.isDeleted(i)) {
+ String sval=r.document(i).get(FIELD_RECORD_ID);
+ if(sval!=null) {
+ int val=Integer.parseInt(sval);
+ assertTrue("Did not expect document #"+val, expecteds.get(val));
+ expecteds.set(val,false);
+ }
+ }
+ }
+ r.close();
+ assertEquals("Should have 0 docs remaining ", 0 ,expecteds.cardinality());
+ }
+
+ /*
+ private void showAvailableCommitPoints() throws Exception {
+ Collection commits = IndexReader.listCommits(dir);
+ for (Iterator iterator = commits.iterator(); iterator.hasNext();) {
+ IndexCommit comm = (IndexCommit) iterator.next();
+ System.out.print("\t Available commit point:["+comm.getUserData()+"] files=");
+ Collection files = comm.getFileNames();
+ for (Iterator iterator2 = files.iterator(); iterator2.hasNext();) {
+ String filename = (String) iterator2.next();
+ System.out.print(filename+", ");
+ }
+ System.out.println();
+ }
+ }
+ */
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ dir = newDirectory();
+ //Build index, of records 1 to 100, committing after each batch of 10
+ IndexDeletionPolicy sdp=new KeepAllDeletionPolicy();
+ IndexWriter w=new IndexWriter(dir, newIndexWriterConfig( TEST_VERSION_CURRENT, new MockAnalyzer(random)).setIndexDeletionPolicy(sdp));
+ for(int currentRecordId=1;currentRecordId<=100;currentRecordId++) {
+ Document doc=new Document();
+ doc.add(newField(FIELD_RECORD_ID,""+currentRecordId,Field.Store.YES,Field.Index.ANALYZED));
+ w.addDocument(doc);
+
+ if (currentRecordId%10 == 0) {
+ Map<String,String> data = new HashMap<String,String>();
+ data.put("index", "records 1-"+currentRecordId);
+ w.commit(data);
+ }
+ }
+
+ w.close();
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ dir.close();
+ super.tearDown();
+ }
+
+ // Rolls back to previous commit point
+ class RollbackDeletionPolicy implements IndexDeletionPolicy {
+ private int rollbackPoint;
+
+ public RollbackDeletionPolicy(int rollbackPoint) {
+ this.rollbackPoint = rollbackPoint;
+ }
+
+ public void onCommit(List<? extends IndexCommit> commits) throws IOException {
+ }
+
+ public void onInit(List<? extends IndexCommit> commits) throws IOException {
+ for (final IndexCommit commit : commits) {
+ Map<String,String> userData=commit.getUserData();
+ if (userData.size() > 0) {
+ // Label for a commit point is "Records 1-30"
+ // This code reads the last id ("30" in this example) and deletes it
+ // if it is after the desired rollback point
+ String x = userData.get("index");
+ String lastVal = x.substring(x.lastIndexOf("-")+1);
+ int last = Integer.parseInt(lastVal);
+ if (last>rollbackPoint) {
+ /*
+ System.out.print("\tRolling back commit point:" +
+ " UserData="+commit.getUserData() +") ("+(commits.size()-1)+" commit points left) files=");
+ Collection files = commit.getFileNames();
+ for (Iterator iterator2 = files.iterator(); iterator2.hasNext();) {
+ System.out.print(" "+iterator2.next());
+ }
+ System.out.println();
+ */
+
+ commit.delete();
+ }
+ }
+ }
+ }
+ }
+
+ class DeleteLastCommitPolicy implements IndexDeletionPolicy {
+
+ public void onCommit(List<? extends IndexCommit> commits) throws IOException {}
+
+ public void onInit(List<? extends IndexCommit> commits) throws IOException {
+ commits.get(commits.size()-1).delete();
+ }
+ }
+
+ public void testRollbackDeletionPolicy() throws Exception {
+ for(int i=0;i<2;i++) {
+ // Unless you specify a prior commit point, rollback
+ // should not work:
+ new IndexWriter(dir, newIndexWriterConfig( TEST_VERSION_CURRENT, new MockAnalyzer(random))
+ .setIndexDeletionPolicy(new DeleteLastCommitPolicy())).close();
+ IndexReader r = IndexReader.open(dir, true);
+ assertEquals(100, r.numDocs());
+ r.close();
+ }
+ }
+
+ // Keeps all commit points (used to build index)
+ class KeepAllDeletionPolicy implements IndexDeletionPolicy {
+ public void onCommit(List<? extends IndexCommit> commits) throws IOException {}
+ public void onInit(List<? extends IndexCommit> commits) throws IOException {}
+ }
+}