/* * 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.BufferedInputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.BufferedOutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import org.apache.jackrabbit.core.query.lucene.directory.IndexInputStream; import org.apache.jackrabbit.core.query.lucene.directory.IndexOutputStream; import org.apache.lucene.store.Directory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Stores a sequence of index names and their current generation. */ class IndexInfos implements Cloneable { /** * Logger instance for this class */ private static final Logger log = LoggerFactory.getLogger(IndexInfos.class); /** * IndexInfos version for Jackrabbit 1.0 to 1.5.x */ private static final int NAMES_ONLY = 0; /** * IndexInfos version for Jackrabbit 2.0 */ private static final int WITH_GENERATION = 1; /** * For new segment names. */ private int counter = 0; /** * Map of {@link IndexInfo}s. Key=name */ private LinkedHashMap<String, IndexInfo> indexes = new LinkedHashMap<String, IndexInfo>(); /** * The directory where the index infos are stored. */ private final Directory directory; /** * Base name of the file where the infos are stored. */ private final String name; /** * The generation for this index infos. */ private long generation = 0; /** * When this index infos were last modified. */ private long lastModified; /** * Creates a new IndexInfos using <code>baseName</code> and reads the * current generation. * * @param dir the directory where the index infos are stored. * @param baseName the name of the file where infos are stored. * @throws IOException if an error occurs while reading the index infos * file. */ IndexInfos(Directory dir, String baseName) throws IOException { this.directory = dir; this.name = baseName; long gens[] = getGenerations(getFileNames(dir, baseName), baseName); if (gens.length == 0) { // write initial infos write(); } else { // read most recent generation for (int i = gens.length - 1; i >= 0; i--) { try { this.generation = gens[i]; read(); break; } catch (EOFException e) { String fileName = getFileName(gens[i]); log.warn("deleting invalid index infos file: " + fileName); dir.deleteFile(fileName); // reset generation this.generation = 0; } } } } /** * Creates a new IndexInfos using <code>fileName</code> and reads the given * <code>generation</code> of the index infos. * * @param dir the directory where the index infos are stored. * @param baseName the name of the file where infos are stored. * @param generation the generation to read. * @throws IOException if an error occurs while reading the index infos * file. */ IndexInfos(Directory dir, String baseName, long generation) throws IOException { if (generation < 0) { throw new IllegalArgumentException(); } this.directory = dir; this.name = baseName; this.generation = generation; read(); } /** * Returns the name of the file with the most current version where infos * are stored. * * @return the name of the file where infos are stored. */ String getFileName() { return getFileName(generation); } /** * Writes the index infos to disk. * * @throws IOException if an error occurs. */ void write() throws IOException { // increment generation generation++; String newName = getFileName(); boolean success = false; try { OutputStream out = new BufferedOutputStream(new IndexOutputStream( directory.createOutput(newName))); try { log.debug("Writing IndexInfos {}", newName); DataOutputStream dataOut = new DataOutputStream(out); dataOut.writeInt(WITH_GENERATION); dataOut.writeInt(counter); dataOut.writeInt(indexes.size()); for (Iterator<IndexInfo> it = iterator(); it.hasNext(); ) { IndexInfo info = it.next(); dataOut.writeUTF(info.getName()); dataOut.writeLong(info.getGeneration()); log.debug(" + {}:{}", info.getName(), info.getGeneration()); } } finally { out.close(); } directory.sync(Collections.singleton(newName)); lastModified = System.currentTimeMillis(); success = true; } finally { if (!success) { // try to delete the file and decrement generation try { directory.deleteFile(newName); } catch (IOException e) { log.warn("Unable to delete file: " + directory + "/" + newName); } generation--; } } } /** * @return an iterator over the {@link IndexInfo}s contained in this index * infos. */ Iterator<IndexInfo> iterator() { return indexes.values().iterator(); } /** * Returns the number of index names. * @return the number of index names. */ int size() { return indexes.size(); } /** * @return the time when this index infos where last modified. */ long getLastModified() { return lastModified; } /** * Adds a name to the index infos. * * @param name the name to add. * @param generation the current generation of the index. */ void addName(String name, long generation) { if (indexes.containsKey(name)) { throw new IllegalArgumentException("already contains: " + name); } indexes.put(name, new IndexInfo(name, generation)); } void updateGeneration(String name, long generation) { IndexInfo info = indexes.get(name); if (info == null) { throw new NoSuchElementException(name); } if (info.getGeneration() != generation) { info.setGeneration(generation); } } /** * Removes the name from the index infos. * @param name the name to remove. */ void removeName(String name) { indexes.remove(name); } /** * Returns <code>true</code> if <code>name</code> exists in this * <code>IndexInfos</code>; <code>false</code> otherwise. * * @param name the name to test existence. * @return <code>true</code> it is exists in this <code>IndexInfos</code>. */ boolean contains(String name) { return indexes.containsKey(name); } /** * @return the generation of this index infos. */ long getGeneration() { return generation; } /** * Returns a new unique name for an index folder. * @return a new unique name for an index folder. */ String newName() { return "_" + Integer.toString(counter++, Character.MAX_RADIX); } /** * Clones this index infos. * * @return a clone of this index infos. */ @SuppressWarnings("unchecked") public IndexInfos clone() { try { IndexInfos clone = (IndexInfos) super.clone(); clone.indexes = (LinkedHashMap<String, IndexInfo>) indexes.clone(); for (Map.Entry<String, IndexInfo> entry : clone.indexes.entrySet()) { entry.setValue(entry.getValue().clone()); } return clone; } catch (CloneNotSupportedException e) { // never happens, this class is cloneable throw new RuntimeException(); } } //----------------------------------< internal >---------------------------- /** * Reads the index infos with the currently set {@link #generation}. * * @throws IOException if an error occurs. */ private void read() throws IOException { String fileName = getFileName(generation); InputStream in = new BufferedInputStream(new IndexInputStream( directory.openInput(fileName))); try { LinkedHashMap<String, IndexInfo> indexes = new LinkedHashMap<String, IndexInfo>(); DataInputStream di = new DataInputStream(in); int version; if (generation == 0) { version = NAMES_ONLY; } else { version = di.readInt(); } int counter = di.readInt(); for (int i = di.readInt(); i > 0; i--) { String indexName = di.readUTF(); long gen = 0; if (version >= WITH_GENERATION) { gen = di.readLong(); } indexes.put(indexName, new IndexInfo(indexName, gen)); } // when successfully read set values this.lastModified = directory.fileModified(fileName); this.indexes = indexes; this.counter = counter; } finally { in.close(); } } /** * Returns the name of the file with the given generation where infos * are stored. * * @param gen the generation of the file. * @return the name of the file where infos are stored. */ private String getFileName(long gen) { if (gen == 0) { return name; } else { return name + "_" + Long.toString(gen, Character.MAX_RADIX); } } /** * Returns all generations of this index infos. * * @param directory the directory where the index infos are stored. * @param base the base name for the index infos. * @return names of all generation files of this index infos. */ private static String[] getFileNames(Directory directory, final String base) { String[] names = null; try { names = directory.listAll(); } catch (IOException e) { // TODO: log warning? or throw? } if (names == null) { return new String[0]; } List<String> nameList = new ArrayList<String>(names.length); for (String n : names) { if (n.startsWith(base)) { nameList.add(n); } } return nameList.toArray(new String[nameList.size()]); } /** * Parse the generation off the file name and return it. * * @param fileName the generation file that contains index infos. * @param base the base name. * @return the generation of the given file. */ private static long generationFromFileName(String fileName, String base) { if (fileName.equals(base)) { return 0; } else { return Long.parseLong(fileName.substring(base.length() + 1), Character.MAX_RADIX); } } /** * Returns the generations fo the given files in ascending order. * * @param fileNames the file names from where to obtain the generations. * @param base the base name. * @return the generations in ascending order. */ private static long[] getGenerations(String[] fileNames, String base) { long[] gens = new long[fileNames.length]; for (int i = 0; i < fileNames.length; i++) { gens[i] = generationFromFileName(fileNames[i], base); } Arrays.sort(gens); return gens; } }