/* * 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.exoplatform.services.jcr.impl.core.query.lucene; import org.apache.lucene.store.Directory; import org.exoplatform.commons.utils.SecurityHelper; import org.exoplatform.services.jcr.impl.core.query.lucene.directory.IndexInputStream; import org.exoplatform.services.jcr.impl.core.query.lucene.directory.IndexOutputStream; import org.exoplatform.services.jcr.impl.util.io.DirectoryHelper; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; /** * Stores a sequence of index names. */ public class IndexInfos { private static final Log LOG = ExoLogger.getLogger("exo.jcr.component.core.IndexInfos"); /** * Default file name, that is used if not defined; */ public final static String DEFALUT_NAME = "indexes"; /** * For new segment names. */ private int counter; /** * Flag that indicates if index infos needs to be written to disk. */ private boolean dirty; /** * List of index names */ private List<String> indexes = new ArrayList<String>(); /** * Set of names for quick lookup. */ private Set<String> names = new HashSet<String>(); /** * Name of the file where the infos are stored. */ private final String name; /** * Directory, where index names file is stored. */ private Directory dir; /** * {@link MultiIndex} instance for callbacking when list of indexes changed */ protected MultiIndex multiIndex; /** * Creates a new IndexInfos using <code>"indexes"</code> as a filename. */ public IndexInfos() { this(DEFALUT_NAME); } /** * Creates a new IndexInfos using <code>fileName</code>. * * @param fileName the name of the file where infos are stored. */ public IndexInfos(String fileName) { this.name = fileName; } /** * Returns the name of the file where infos are stored. * * @return the name of the file where infos are stored. */ public String getFileName() { return name; } /** * Reads the index infos. Before reading it checks if file exists * * @throws IOException if an error occurs. */ public void read() throws IOException { SecurityHelper.doPrivilegedIOExceptionAction(new PrivilegedExceptionAction<Object>() { public Object run() throws Exception { // Known issue for NFS bases on ext3. Need to refresh directory to read actual data. dir.listAll(); names.clear(); indexes.clear(); if (dir.fileExists(name)) { // clear current lists InputStream in = new IndexInputStream(dir.openInput(name)); DataInputStream di = null; try { di = new DataInputStream(in); counter = di.readInt(); for (int i = di.readInt(); i > 0; i--) { String indexName = di.readUTF(); indexes.add(indexName); names.add(indexName); } } finally { if (di != null) di.close(); in.close(); } } return null; } }); } /** * Writes the index infos to disk if they are dirty. * * @throws IOException if an error occurs. */ public void write() throws IOException { SecurityHelper.doPrivilegedIOExceptionAction(new PrivilegedExceptionAction<Object>() { public Object run() throws Exception { // do not write if not dirty if (!dirty) { return null; } OutputStream out = new IndexOutputStream(dir.createOutput(name + ".new")); DataOutputStream dataOut = null; try { dataOut = new DataOutputStream(out); dataOut.writeInt(counter); dataOut.writeInt(indexes.size()); for (int i = 0; i < indexes.size(); i++) { dataOut.writeUTF(getName(i)); } } finally { if (dataOut != null) dataOut.close(); out.close(); } // delete old if (dir.fileExists(name)) { dir.deleteFile(name); } rename(name + ".new", name); dirty = false; return null; } }); } /** * Renames file by copying. * * @param from * @param to * @throws IOException */ private void rename(String from, String to) throws IOException { IndexOutputStream out = null; IndexInputStream in = null; try { out = new IndexOutputStream(dir.createOutput(to)); in = new IndexInputStream(dir.openInput(from)); DirectoryHelper.transfer(in, out); } finally { if (in != null) { in.close(); } if (out != null) { out.flush(); out.close(); } } try { // delete old one if (dir.fileExists(from)) { dir.deleteFile(from); } } catch (IOException e) { // do noting. Will be removed later if (LOG.isTraceEnabled()) { LOG.trace("Can't deleted file: " + e.getMessage()); } } } /** * Returns the index name at position <code>i</code>. * @param i the position. * @return the index name. */ public String getName(int i) { return indexes.get(i); } /** * Returns the index name at position <code>i</code>. * @return the index name. */ public Set<String> getNames() { return new HashSet<String>(indexes); } /** * Returns the number of index names. * @return the number of index names. */ public int size() { return indexes.size(); } /** * Adds a name to the index infos. * @param name the name to add. */ public void addName(String name) { if (names.contains(name)) { throw new IllegalArgumentException("already contains: " + name); } indexes.add(name); names.add(name); dirty = true; } /** * Removes the name from the index infos. * @param name the name to remove. */ public void removeName(String name) { indexes.remove(name); names.remove(name); dirty = true; } /** * Removes the name from the index infos. * @param i the position. */ public void removeName(int i) { Object name = indexes.remove(i); names.remove(name); dirty = true; } /** * 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>. */ public boolean contains(String name) { return names.contains(name); } /** * Returns a new unique name for an index folder. * @return a new unique name for an index folder. */ public String newName() { dirty = true; return "_" + Integer.toString(counter++, Character.MAX_RADIX); } /** * Sets directory, where file with index names is stored. * @param dir */ public void setDirectory(Directory dir) { this.dir = dir; } /** * Sets new names, clearing existing. It is thought to be used when list of indexes can * be externally changed. * * @param names */ protected void setNames(Set<String> names) { this.names.clear(); this.indexes.clear(); this.names.addAll(names); this.indexes.addAll(names); // new list of indexes if thought to be up to date dirty = false; } /** * Sets {@link MultiIndex} instance. * @param multiIndex */ public void setMultiIndex(MultiIndex multiIndex) { this.multiIndex = multiIndex; } /** * Returns multiIndex instance * @return */ public MultiIndex getMultiIndex() { return multiIndex; } /** * Returns true, if changes weren't saved to FS. * @return */ protected boolean isDirty() { return dirty; } }