/* * 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 org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.lucene.store.Directory; import java.util.TreeMap; import java.util.Collections; import java.util.Iterator; import java.util.Map; import java.io.IOException; /** * <code>IndexHistory</code> implements a history of index segments. Whenever * the index is flushed a new {@link IndexInfos} instance is created which * represents the current state of the index. This includes the names of the * index segments as well as their current generation number. */ class IndexHistory { /** * The logger instance for this class. */ private static final Logger log = LoggerFactory.getLogger(IndexHistory.class); /** * Name of the file that contains the index infos. */ private static final String INDEXES = "indexes"; /** * the directory from where to read the index history. */ private final Directory indexDir; /** * The maximum age (in milliseconds) of an index infos generation until it * is removed. */ private final long maxAge; /** * Maps generation (Long) to {@link IndexInfos}. Youngest generation first * (-> higher value). */ private final Map<Long, IndexInfos> indexInfosMap = new TreeMap<Long, IndexInfos>(Collections.reverseOrder()); /** * Creates a new <code>IndexHistory</code> from the given <code>dir</code>. * * @param dir the directory from where to read the index history. * @param maxAge the maximum age in milliseconds for unused index infos. * @throws IOException if an error occurs while reading the index history. */ IndexHistory(Directory dir, long maxAge) throws IOException { this.indexDir = dir; this.maxAge = maxAge; // read all index infos String[] names = dir.listAll(); if (names != null) { for (String name : names) { if (name.startsWith(INDEXES)) { long gen; if (name.length() == INDEXES.length()) { gen = 0; } else if (name.charAt(INDEXES.length()) == '_') { gen = Long.parseLong(name.substring(INDEXES.length() + 1), Character.MAX_RADIX); } else { continue; } try { IndexInfos infos = new IndexInfos(dir, INDEXES, gen); indexInfosMap.put(gen, infos); } catch (IOException e) { log.warn("ignoring invalid index infos file: " + name); } } } } } /** * Returns the time when the index segment with the given <code>indexName</code> * was in use for the last time. The returned time does not accurately * say until when an index segment was in use, but it does guarantee that * the index segment in question was not in use anymore at the returned * time. * <p> * There are two special cases of return values: * <ul> * <li>{@link Long#MAX_VALUE}: indicates that the index segment is still in active use.</li> * <li>{@link Long#MIN_VALUE}: indicates that there is no index segment with the given name.</li> * </ul> * * @param indexName name of an index segment. * @return the time when the index segment with the given name was in use * the last time. */ long getLastUseOf(String indexName) { Long previous = null; for (Map.Entry<Long, IndexInfos> entry : indexInfosMap.entrySet()) { IndexInfos infos = entry.getValue(); if (infos.contains(indexName)) { if (previous == null) { // still in use return Long.MAX_VALUE; } else { return previous; } } previous = infos.getLastModified(); } return Long.MIN_VALUE; } /** * Removes index infos older than {@link #maxAge} from this history. */ void pruneOutdated() { long threshold = System.currentTimeMillis() - maxAge; log.debug("Pruning index infos older than: " + threshold + "(" + indexDir + ")"); Iterator<IndexInfos> it = indexInfosMap.values().iterator(); // never prune the current generation if (it.hasNext()) { IndexInfos infos = it.next(); log.debug("Skipping first index infos. generation=" + infos.getGeneration()); } while (it.hasNext()) { IndexInfos infos = (IndexInfos) it.next(); if (infos.getLastModified() < threshold) { // check associated redo log try { String logName = getRedoLogName(infos.getGeneration()); if (indexDir.fileExists(logName)) { long lastModified = indexDir.fileModified(logName); if (lastModified > threshold) { log.debug("Keeping redo log with generation={}, timestamp={}", infos.getGeneration(), lastModified); continue; } // try do delete it try { indexDir.deleteFile(logName); log.debug("Deleted redo log with generation={}, timestamp={}", infos.getGeneration(), lastModified); } catch (IOException e) { log.warn("Unable to delete: " + indexDir + "/" + logName); continue; } } // delete index infos try { indexDir.deleteFile(infos.getFileName()); log.debug("Deleted index infos with generation={}", infos.getGeneration()); it.remove(); } catch (IOException e) { log.warn("Unable to delete: " + indexDir + "/" + infos.getFileName()); } } catch (IOException e) { log.warn("Failed to check if {} is outdated: {}", infos.getFileName(), e); } } } } /** * Adds an index infos to the history. This method will not modify nor keep * a reference to the passed <code>infos</code>. * * @param infos the index infos to add. */ void addIndexInfos(IndexInfos infos) { // must clone infos because it is modifiable indexInfosMap.put(infos.getGeneration(), infos.clone()); } //-------------------------------< internal >------------------------------- /** * Returns the name of the redo log file with the given generation. * * @param generation the index infos generation. * @return the name of the redo log file with the given generation. */ String getRedoLogName(long generation) { if (generation == 0) { return DefaultRedoLog.REDO_LOG; } else { return DefaultRedoLog.REDO_LOG_PREFIX + Long.toString(generation, Character.MAX_RADIX) + DefaultRedoLog.DOT_LOG; } } }