/* * 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.lucene.replicator; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyWriter; import org.apache.lucene.facet.taxonomy.writercache.TaxonomyWriterCache; import org.apache.lucene.index.IndexCommit; import org.apache.lucene.index.IndexDeletionPolicy; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.index.IndexWriterConfig.OpenMode; import org.apache.lucene.index.SnapshotDeletionPolicy; import org.apache.lucene.store.Directory; import org.apache.lucene.store.IOContext; /** * A {@link Revision} of a single index and taxonomy index files which comprises * the list of files from both indexes. This revision should be used whenever a * pair of search and taxonomy indexes need to be replicated together to * guarantee consistency of both on the replicating (client) side. * * @see IndexRevision * * @lucene.experimental */ public class IndexAndTaxonomyRevision implements Revision { /** * A {@link DirectoryTaxonomyWriter} which sets the underlying * {@link IndexWriter}'s {@link IndexDeletionPolicy} to * {@link SnapshotDeletionPolicy}. */ public static final class SnapshotDirectoryTaxonomyWriter extends DirectoryTaxonomyWriter { private SnapshotDeletionPolicy sdp; private IndexWriter writer; /** * @see DirectoryTaxonomyWriter#DirectoryTaxonomyWriter(Directory, * IndexWriterConfig.OpenMode, TaxonomyWriterCache) */ public SnapshotDirectoryTaxonomyWriter(Directory directory, OpenMode openMode, TaxonomyWriterCache cache) throws IOException { super(directory, openMode, cache); } /** @see DirectoryTaxonomyWriter#DirectoryTaxonomyWriter(Directory, IndexWriterConfig.OpenMode) */ public SnapshotDirectoryTaxonomyWriter(Directory directory, OpenMode openMode) throws IOException { super(directory, openMode); } /** @see DirectoryTaxonomyWriter#DirectoryTaxonomyWriter(Directory) */ public SnapshotDirectoryTaxonomyWriter(Directory d) throws IOException { super(d); } @Override protected IndexWriterConfig createIndexWriterConfig(OpenMode openMode) { IndexWriterConfig conf = super.createIndexWriterConfig(openMode); sdp = new SnapshotDeletionPolicy(conf.getIndexDeletionPolicy()); conf.setIndexDeletionPolicy(sdp); return conf; } @Override protected IndexWriter openIndexWriter(Directory directory, IndexWriterConfig config) throws IOException { writer = super.openIndexWriter(directory, config); return writer; } /** Returns the {@link SnapshotDeletionPolicy} used by the underlying {@link IndexWriter}. */ public SnapshotDeletionPolicy getDeletionPolicy() { return sdp; } /** Returns the {@link IndexWriter} used by this {@link DirectoryTaxonomyWriter}. */ public IndexWriter getIndexWriter() { return writer; } } private static final int RADIX = 16; public static final String INDEX_SOURCE = "index"; public static final String TAXONOMY_SOURCE = "taxo"; private final IndexWriter indexWriter; private final SnapshotDirectoryTaxonomyWriter taxoWriter; private final IndexCommit indexCommit, taxoCommit; private final SnapshotDeletionPolicy indexSDP, taxoSDP; private final String version; private final Map<String,List<RevisionFile>> sourceFiles; /** Returns a singleton map of the revision files from the given {@link IndexCommit}. */ public static Map<String, List<RevisionFile>> revisionFiles(IndexCommit indexCommit, IndexCommit taxoCommit) throws IOException { HashMap<String,List<RevisionFile>> files = new HashMap<>(); files.put(INDEX_SOURCE, IndexRevision.revisionFiles(indexCommit).values().iterator().next()); files.put(TAXONOMY_SOURCE, IndexRevision.revisionFiles(taxoCommit).values().iterator().next()); return files; } /** * Returns a String representation of a revision's version from the given * {@link IndexCommit}s of the search and taxonomy indexes. */ public static String revisionVersion(IndexCommit indexCommit, IndexCommit taxoCommit) { return Long.toString(indexCommit.getGeneration(), RADIX) + ":" + Long.toString(taxoCommit.getGeneration(), RADIX); } /** * Constructor over the given {@link IndexWriter}. Uses the last * {@link IndexCommit} found in the {@link Directory} managed by the given * writer. */ public IndexAndTaxonomyRevision(IndexWriter indexWriter, SnapshotDirectoryTaxonomyWriter taxoWriter) throws IOException { IndexDeletionPolicy delPolicy = indexWriter.getConfig().getIndexDeletionPolicy(); if (!(delPolicy instanceof SnapshotDeletionPolicy)) { throw new IllegalArgumentException("IndexWriter must be created with SnapshotDeletionPolicy"); } this.indexWriter = indexWriter; this.taxoWriter = taxoWriter; this.indexSDP = (SnapshotDeletionPolicy) delPolicy; this.taxoSDP = taxoWriter.getDeletionPolicy(); this.indexCommit = indexSDP.snapshot(); this.taxoCommit = taxoSDP.snapshot(); this.version = revisionVersion(indexCommit, taxoCommit); this.sourceFiles = revisionFiles(indexCommit, taxoCommit); } @Override public int compareTo(String version) { final String[] parts = version.split(":"); final long indexGen = Long.parseLong(parts[0], RADIX); final long taxoGen = Long.parseLong(parts[1], RADIX); final long indexCommitGen = indexCommit.getGeneration(); final long taxoCommitGen = taxoCommit.getGeneration(); // if the index generation is not the same as this commit's generation, // compare by it. Otherwise, compare by the taxonomy generation. if (indexCommitGen < indexGen) { return -1; } else if (indexCommitGen > indexGen) { return 1; } else { return taxoCommitGen < taxoGen ? -1 : (taxoCommitGen > taxoGen ? 1 : 0); } } @Override public int compareTo(Revision o) { IndexAndTaxonomyRevision other = (IndexAndTaxonomyRevision) o; int cmp = indexCommit.compareTo(other.indexCommit); return cmp != 0 ? cmp : taxoCommit.compareTo(other.taxoCommit); } @Override public String getVersion() { return version; } @Override public Map<String,List<RevisionFile>> getSourceFiles() { return sourceFiles; } @Override public InputStream open(String source, String fileName) throws IOException { assert source.equals(INDEX_SOURCE) || source.equals(TAXONOMY_SOURCE) : "invalid source; expected=(" + INDEX_SOURCE + " or " + TAXONOMY_SOURCE + ") got=" + source; IndexCommit ic = source.equals(INDEX_SOURCE) ? indexCommit : taxoCommit; return new IndexInputInputStream(ic.getDirectory().openInput(fileName, IOContext.READONCE)); } @Override public void release() throws IOException { try { indexSDP.release(indexCommit); } finally { taxoSDP.release(taxoCommit); } try { indexWriter.deleteUnusedFiles(); } finally { taxoWriter.getIndexWriter().deleteUnusedFiles(); } } @Override public String toString() { return "IndexAndTaxonomyRevision version=" + version + " files=" + sourceFiles; } }