1 package org.apache.lucene.index;
4 * Licensed to the Apache Software Foundation (ASF) under one or more
5 * contributor license agreements. See the NOTICE file distributed with this
6 * work for additional information regarding copyright ownership. The ASF
7 * licenses this file to You under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16 * License for the specific language governing permissions and limitations under
20 import java.io.IOException;
21 import java.util.HashMap;
22 import java.util.List;
24 import java.util.Map.Entry;
26 import org.apache.lucene.document.Document;
27 import org.apache.lucene.document.Field;
28 import org.apache.lucene.document.Fieldable;
29 import org.apache.lucene.document.Field.Index;
30 import org.apache.lucene.document.Field.Store;
31 import org.apache.lucene.index.IndexWriterConfig.OpenMode;
32 import org.apache.lucene.store.Directory;
33 import org.apache.lucene.store.LockObtainFailedException;
34 import org.apache.lucene.util.Version;
37 * A {@link SnapshotDeletionPolicy} which adds a persistence layer so that
38 * snapshots can be maintained across the life of an application. The snapshots
39 * are persisted in a {@link Directory} and are committed as soon as
40 * {@link #snapshot(String)} or {@link #release(String)} is called.
42 * <b>NOTE:</b> this class receives a {@link Directory} to persist the data into
43 * a Lucene index. It is highly recommended to use a dedicated directory (and on
44 * stable storage as well) for persisting the snapshots' information, and not
45 * reuse the content index directory, or otherwise conflicts and index
46 * corruptions will occur.
48 * <b>NOTE:</b> you should call {@link #close()} when you're done using this
49 * class for safetyness (it will close the {@link IndexWriter} instance used).
51 public class PersistentSnapshotDeletionPolicy extends SnapshotDeletionPolicy {
53 // Used to validate that the given directory includes just one document w/ the
54 // given ID field. Otherwise, it's not a valid Directory for snapshotting.
55 private static final String SNAPSHOTS_ID = "$SNAPSHOTS_DOC$";
57 // The index writer which maintains the snapshots metadata
58 private final IndexWriter writer;
61 * Reads the snapshots information from the given {@link Directory}. This
62 * method can be used if the snapshots information is needed, however you
63 * cannot instantiate the deletion policy (because e.g., some other process
64 * keeps a lock on the snapshots directory).
66 public static Map<String, String> readSnapshotsInfo(Directory dir) throws IOException {
67 IndexReader r = IndexReader.open(dir, true);
68 Map<String, String> snapshots = new HashMap<String, String>();
70 int numDocs = r.numDocs();
71 // index is allowed to have exactly one document or 0.
73 Document doc = r.document(r.maxDoc() - 1);
74 Field sid = doc.getField(SNAPSHOTS_ID);
76 throw new IllegalStateException("directory is not a valid snapshots store!");
78 doc.removeField(SNAPSHOTS_ID);
79 for (Fieldable f : doc.getFields()) {
80 snapshots.put(f.name(), f.stringValue());
82 } else if (numDocs != 0) {
83 throw new IllegalStateException(
84 "should be at most 1 document in the snapshots directory: " + numDocs);
93 * {@link PersistentSnapshotDeletionPolicy} wraps another
94 * {@link IndexDeletionPolicy} to enable flexible snapshotting.
97 * the {@link IndexDeletionPolicy} that is used on non-snapshotted
98 * commits. Snapshotted commits, by definition, are not deleted until
99 * explicitly released via {@link #release(String)}.
101 * the {@link Directory} which will be used to persist the snapshots
104 * specifies whether a new index should be created, deleting all
105 * existing snapshots information (immediately), or open an existing
106 * index, initializing the class with the snapshots information.
107 * @param matchVersion
108 * specifies the {@link Version} that should be used when opening the
111 public PersistentSnapshotDeletionPolicy(IndexDeletionPolicy primary,
112 Directory dir, OpenMode mode, Version matchVersion)
113 throws CorruptIndexException, LockObtainFailedException, IOException {
114 super(primary, null);
116 // Initialize the index writer over the snapshot directory.
117 writer = new IndexWriter(dir, new IndexWriterConfig(matchVersion, null).setOpenMode(mode));
118 if (mode != OpenMode.APPEND) {
119 // IndexWriter no longer creates a first commit on an empty Directory. So
120 // if we were asked to CREATE*, call commit() just to be sure. If the
121 // index contains information and mode is CREATE_OR_APPEND, it's a no-op.
126 // Initializes the snapshots information. This code should basically run
127 // only if mode != CREATE, but if it is, it's no harm as we only open the
128 // reader once and immediately close it.
129 for (Entry<String, String> e : readSnapshotsInfo(dir).entrySet()) {
130 registerSnapshotInfo(e.getKey(), e.getValue(), null);
132 } catch (RuntimeException e) {
133 writer.close(); // don't leave any open file handles
135 } catch (IOException e) {
136 writer.close(); // don't leave any open file handles
142 public synchronized void onInit(List<? extends IndexCommit> commits)
144 // super.onInit() needs to be called first to ensure that initialization
145 // behaves as expected. The superclass, SnapshotDeletionPolicy, ensures
146 // that any snapshot IDs with empty IndexCommits are released. Since this
147 // happens, this class needs to persist these changes.
148 super.onInit(commits);
149 persistSnapshotInfos(null, null);
153 * Snapshots the last commit using the given ID. Once this method returns, the
154 * snapshot information is persisted in the directory.
156 * @see SnapshotDeletionPolicy#snapshot(String)
159 public synchronized IndexCommit snapshot(String id) throws IOException {
160 checkSnapshotted(id);
161 if (SNAPSHOTS_ID.equals(id)) {
162 throw new IllegalArgumentException(id + " is reserved and cannot be used as a snapshot id");
164 persistSnapshotInfos(id, lastCommit.getSegmentsFileName());
165 return super.snapshot(id);
169 * Deletes a snapshotted commit by ID. Once this method returns, the snapshot
170 * information is committed to the directory.
172 * @see SnapshotDeletionPolicy#release(String)
175 public synchronized void release(String id) throws IOException {
177 persistSnapshotInfos(null, null);
180 /** Closes the index which writes the snapshots to the directory. */
181 public void close() throws CorruptIndexException, IOException {
186 * Persists all snapshots information. If the given id and segment are not
187 * null, it persists their information as well.
189 private void persistSnapshotInfos(String id, String segment) throws IOException {
191 Document d = new Document();
192 d.add(new Field(SNAPSHOTS_ID, "", Store.YES, Index.NO));
193 for (Entry<String, String> e : super.getSnapshots().entrySet()) {
194 d.add(new Field(e.getKey(), e.getValue(), Store.YES, Index.NO));
197 d.add(new Field(id, segment, Store.YES, Index.NO));
199 writer.addDocument(d);