| 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.util.Collection; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.ArrayList; |
| import java.util.Map; |
| import java.io.IOException; |
| |
| import org.apache.lucene.store.Directory; |
| |
| /** |
| * An {@link IndexDeletionPolicy} that wraps any other |
| * {@link IndexDeletionPolicy} and adds the ability to hold and later release |
| * snapshots of an index. While a snapshot is held, the {@link IndexWriter} will |
| * not remove any files associated with it even if the index is otherwise being |
| * actively, arbitrarily changed. Because we wrap another arbitrary |
| * {@link IndexDeletionPolicy}, this gives you the freedom to continue using |
| * whatever {@link IndexDeletionPolicy} you would normally want to use with your |
| * index. |
| * |
| * <p> |
| * This class maintains all snapshots in-memory, and so the information is not |
| * persisted and not protected against system failures. If persistence is |
| * important, you can use {@link PersistentSnapshotDeletionPolicy}. |
| * |
| * @lucene.experimental |
| */ |
| public class SnapshotDeletionPolicy extends IndexDeletionPolicy { |
| |
| /** Records how many snapshots are held against each |
| * commit generation */ |
| protected Map<Long,Integer> refCounts = new HashMap<Long,Integer>(); |
| |
| /** Used to map gen to IndexCommit. */ |
| protected Map<Long,IndexCommit> indexCommits = new HashMap<Long,IndexCommit>(); |
| |
| /** Wrapped {@link IndexDeletionPolicy} */ |
| private IndexDeletionPolicy primary; |
| |
| /** Most recently committed {@link IndexCommit}. */ |
| protected IndexCommit lastCommit; |
| |
| /** Used to detect misuse */ |
| private boolean initCalled; |
| |
| /** Sole constructor, taking the incoming {@link |
| * IndexDeletionPolicy} to wrap. */ |
| public SnapshotDeletionPolicy(IndexDeletionPolicy primary) { |
| this.primary = primary; |
| } |
| |
| @Override |
| public synchronized void onCommit(List<? extends IndexCommit> commits) |
| throws IOException { |
| primary.onCommit(wrapCommits(commits)); |
| lastCommit = commits.get(commits.size() - 1); |
| } |
| |
| @Override |
| public synchronized void onInit(List<? extends IndexCommit> commits) |
| throws IOException { |
| initCalled = true; |
| primary.onInit(wrapCommits(commits)); |
| for(IndexCommit commit : commits) { |
| if (refCounts.containsKey(commit.getGeneration())) { |
| indexCommits.put(commit.getGeneration(), commit); |
| } |
| } |
| if (!commits.isEmpty()) { |
| lastCommit = commits.get(commits.size() - 1); |
| } |
| } |
| |
| /** |
| * Release a snapshotted commit. |
| * |
| * @param commit |
| * the commit previously returned by {@link #snapshot} |
| */ |
| public synchronized void release(IndexCommit commit) throws IOException { |
| long gen = commit.getGeneration(); |
| releaseGen(gen); |
| } |
| |
| /** Release a snapshot by generation. */ |
| protected void releaseGen(long gen) throws IOException { |
| if (!initCalled) { |
| throw new IllegalStateException("this instance is not being used by IndexWriter; be sure to use the instance returned from writer.getConfig().getIndexDeletionPolicy()"); |
| } |
| Integer refCount = refCounts.get(gen); |
| if (refCount == null) { |
| throw new IllegalArgumentException("commit gen=" + gen + " is not currently snapshotted"); |
| } |
| int refCountInt = refCount.intValue(); |
| assert refCountInt > 0; |
| refCountInt--; |
| if (refCountInt == 0) { |
| refCounts.remove(gen); |
| indexCommits.remove(gen); |
| } else { |
| refCounts.put(gen, refCountInt); |
| } |
| } |
| |
| /** Increments the refCount for this {@link IndexCommit}. */ |
| protected synchronized void incRef(IndexCommit ic) { |
| long gen = ic.getGeneration(); |
| Integer refCount = refCounts.get(gen); |
| int refCountInt; |
| if (refCount == null) { |
| indexCommits.put(gen, lastCommit); |
| refCountInt = 0; |
| } else { |
| refCountInt = refCount.intValue(); |
| } |
| refCounts.put(gen, refCountInt+1); |
| } |
| |
| /** |
| * Snapshots the last commit and returns it. Once a commit is 'snapshotted,' it is protected |
| * from deletion (as long as this {@link IndexDeletionPolicy} is used). The |
| * snapshot can be removed by calling {@link #release(IndexCommit)} followed |
| * by a call to {@link IndexWriter#deleteUnusedFiles()}. |
| * |
| * <p> |
| * <b>NOTE:</b> while the snapshot is held, the files it references will not |
| * be deleted, which will consume additional disk space in your index. If you |
| * take a snapshot at a particularly bad time (say just before you call |
| * forceMerge) then in the worst case this could consume an extra 1X of your |
| * total index size, until you release the snapshot. |
| * |
| * @throws IllegalStateException |
| * if this index does not have any commits yet |
| * @return the {@link IndexCommit} that was snapshotted. |
| */ |
| public synchronized IndexCommit snapshot() throws IOException { |
| if (!initCalled) { |
| throw new IllegalStateException("this instance is not being used by IndexWriter; be sure to use the instance returned from writer.getConfig().getIndexDeletionPolicy()"); |
| } |
| if (lastCommit == null) { |
| // No commit yet, eg this is a new IndexWriter: |
| throw new IllegalStateException("No index commit to snapshot"); |
| } |
| |
| incRef(lastCommit); |
| |
| return lastCommit; |
| } |
| |
| /** Returns all IndexCommits held by at least one snapshot. */ |
| public synchronized List<IndexCommit> getSnapshots() { |
| return new ArrayList<IndexCommit>(indexCommits.values()); |
| } |
| |
| /** Returns the total number of snapshots currently held. */ |
| public synchronized int getSnapshotCount() { |
| int total = 0; |
| for(Integer refCount : refCounts.values()) { |
| total += refCount.intValue(); |
| } |
| |
| return total; |
| } |
| |
| /** Retrieve an {@link IndexCommit} from its generation; |
| * returns null if this IndexCommit is not currently |
| * snapshotted */ |
| public synchronized IndexCommit getIndexCommit(long gen) { |
| return indexCommits.get(gen); |
| } |
| |
| @Override |
| public synchronized IndexDeletionPolicy clone() { |
| SnapshotDeletionPolicy other = (SnapshotDeletionPolicy) super.clone(); |
| other.primary = this.primary.clone(); |
| other.lastCommit = null; |
| other.refCounts = new HashMap<Long,Integer>(refCounts); |
| other.indexCommits = new HashMap<Long,IndexCommit>(indexCommits); |
| return other; |
| } |
| |
| /** Wraps each {@link IndexCommit} as a {@link |
| * SnapshotCommitPoint}. */ |
| private List<IndexCommit> wrapCommits(List<? extends IndexCommit> commits) { |
| List<IndexCommit> wrappedCommits = new ArrayList<IndexCommit>(commits.size()); |
| for (IndexCommit ic : commits) { |
| wrappedCommits.add(new SnapshotCommitPoint(ic)); |
| } |
| return wrappedCommits; |
| } |
| |
| /** Wraps a provided {@link IndexCommit} and prevents it |
| * from being deleted. */ |
| private class SnapshotCommitPoint extends IndexCommit { |
| |
| /** The {@link IndexCommit} we are preventing from deletion. */ |
| protected IndexCommit cp; |
| |
| /** Creates a {@code SnapshotCommitPoint} wrapping the provided |
| * {@link IndexCommit}. */ |
| protected SnapshotCommitPoint(IndexCommit cp) { |
| this.cp = cp; |
| } |
| |
| @Override |
| public String toString() { |
| return "SnapshotDeletionPolicy.SnapshotCommitPoint(" + cp + ")"; |
| } |
| |
| @Override |
| public void delete() { |
| synchronized (SnapshotDeletionPolicy.this) { |
| // Suppress the delete request if this commit point is |
| // currently snapshotted. |
| if (!refCounts.containsKey(cp.getGeneration())) { |
| cp.delete(); |
| } |
| } |
| } |
| |
| @Override |
| public Directory getDirectory() { |
| return cp.getDirectory(); |
| } |
| |
| @Override |
| public Collection<String> getFileNames() throws IOException { |
| return cp.getFileNames(); |
| } |
| |
| @Override |
| public long getGeneration() { |
| return cp.getGeneration(); |
| } |
| |
| @Override |
| public String getSegmentsFileName() { |
| return cp.getSegmentsFileName(); |
| } |
| |
| @Override |
| public Map<String, String> getUserData() throws IOException { |
| return cp.getUserData(); |
| } |
| |
| @Override |
| public boolean isDeleted() { |
| return cp.isDeleted(); |
| } |
| |
| @Override |
| public int getSegmentCount() { |
| return cp.getSegmentCount(); |
| } |
| } |
| } |