package org.exist.indexing.lucene;
import org.apache.log4j.Logger;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.exist.indexing.AbstractIndex;
import org.exist.indexing.IndexWorker;
import org.exist.storage.BrokerPool;
import org.exist.storage.DBBroker;
import org.exist.storage.btree.DBException;
import org.exist.util.DatabaseConfigurationException;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import java.io.File;
import java.io.IOException;
public class LuceneIndex extends AbstractIndex {
private static final Logger LOG = Logger.getLogger(LuceneIndexWorker.class);
public final static String ID = LuceneIndex.class.getName();
protected Directory directory;
protected Analyzer defaultAnalyzer;
protected double bufferSize = IndexWriter.DEFAULT_RAM_BUFFER_SIZE_MB;
protected IndexWriter cachedWriter = null;
protected int writerUseCount = 0;
protected IndexReader cachedReader = null;
protected int readerUseCount = 0;
protected IndexReader cachedWritingReader = null;
protected int writingReaderUseCount = 0;
protected IndexSearcher cachedSearcher = null;
protected int searcherUseCount = 0;
public LuceneIndex() {
//Nothing special to do
}
@Override
public void configure(BrokerPool pool, String dataDir, Element config) throws DatabaseConfigurationException {
super.configure(pool, dataDir, config);
if (LOG.isDebugEnabled())
LOG.debug("Configuring Lucene index");
String bufferSizeParam = config.getAttribute("buffer");
if (bufferSizeParam != null)
try {
bufferSize = Double.parseDouble(bufferSizeParam);
} catch (NumberFormatException e) {
LOG.warn("Invalid buffer size setting for lucene index: " + bufferSizeParam, e);
}
if (LOG.isDebugEnabled())
LOG.debug("Using buffer size: " + bufferSize);
NodeList nl = config.getElementsByTagName("analyzer");
if (nl.getLength() > 0) {
Element node = (Element) nl.item(0);
defaultAnalyzer = AnalyzerConfig.configureAnalyzer(node);
}
if (defaultAnalyzer == null)
defaultAnalyzer = new StandardAnalyzer();
if (LOG.isDebugEnabled())
LOG.debug("Using default analyzer: " + defaultAnalyzer.getClass().getName());
}
@Override
public void open() throws DatabaseConfigurationException {
File dir = new File(getDataDir(), "lucene");
if (LOG.isDebugEnabled())
LOG.debug("Opening Lucene index directory: " + dir.getAbsolutePath());
if (dir.exists()) {
if (!dir.isDirectory())
throw new DatabaseConfigurationException("Lucene index location is not a directory: " +
dir.getAbsolutePath());
} else
dir.mkdirs();
IndexWriter writer = null;
try {
directory = FSDirectory.getDirectory(dir);
writer = getWriter();
} catch (IOException e) {
throw new DatabaseConfigurationException("Exception while reading lucene index directory: " +
e.getMessage(), e);
} finally {
releaseWriter(writer);
}
}
@Override
public void close() throws DBException {
try {
if (cachedWriter != null)
cachedWriter.close();
directory.close();
} catch (IOException e) {
throw new DBException("Caught exception while closing lucene indexes: " + e.getMessage());
}
}
@Override
public void sync() throws DBException {
//Nothing special to do
}
@Override
public void remove() throws DBException {
try {
String[] files = directory.list();
for (int i = 0; i < files.length; i++) {
String file = files[i];
directory.deleteFile(file);
}
close();
} catch (Exception e) {
LOG.warn(e.getMessage(), e);
}
}
@Override
public IndexWorker getWorker(DBBroker broker) {
return new LuceneIndexWorker(this, broker);
}
@Override
public boolean checkIndex(DBBroker broker) {
return false; //To change body of implemented methods use File | Settings | File Templates.
}
protected Analyzer getDefaultAnalyzer() {
return defaultAnalyzer;
}
protected synchronized IndexWriter getWriter() throws IOException {
while (writingReaderUseCount > 0) {
try {
wait();
} catch (InterruptedException e) {
//Nothing special to do
}
}
if (cachedWriter != null) {
writerUseCount++;
} else {
cachedWriter = new IndexWriter(directory, true, defaultAnalyzer);
cachedWriter.setRAMBufferSizeMB(bufferSize);
writerUseCount = 1;
}
notifyAll();
return cachedWriter;
}
protected synchronized void releaseWriter(IndexWriter writer) {
if (writer == null)
return;
if (writer != cachedWriter)
throw new IllegalStateException("IndexWriter was not obtained from getWriter().");
writerUseCount--;
if (writerUseCount == 0) {
try {
cachedWriter.close();
} catch (IOException e) {
LOG.warn("Exception while closing lucene index: " + e.getMessage(), e);
} finally {
cachedWriter = null;
}
}
notifyAll();
waitForReadersAndReopen();
}
protected synchronized IndexReader getReader() throws IOException {
if (cachedReader != null) {
readerUseCount++;
} else {
cachedReader = IndexReader.open(directory);
readerUseCount = 1;
}
return cachedReader;
}
protected synchronized void releaseReader(IndexReader reader) {
if (reader == null)
return;
if (reader != cachedReader)
throw new IllegalStateException("IndexReader was not obtained from getReader().");
readerUseCount--;
notifyAll();
// try {
// cachedReader.close();
// } catch (IOException e) {
// LOG.warn("Exception while closing lucene index: " + e.getMessage(), e);
// } finally {
// cachedReader = null;
// }
}
protected synchronized IndexReader getWritingReader() throws IOException {
while (writerUseCount > 0) {
try {
wait();
} catch (InterruptedException e) {
//Nothing special to do
}
}
if (cachedWritingReader != null) {
writingReaderUseCount++;
} else {
cachedWritingReader = IndexReader.open(directory);
writingReaderUseCount = 1;
}
notifyAll();
return cachedWritingReader;
}
protected synchronized void releaseWritingReader(IndexReader reader) {
if (reader == null)
return;
if (reader != cachedWritingReader)
throw new IllegalStateException("IndexReader was not obtained from getWritingReader().");
writingReaderUseCount--;
if (writingReaderUseCount == 0) {
try {
cachedWritingReader.close();
} catch (IOException e) {
LOG.warn("Exception while closing lucene index: " + e.getMessage(), e);
} finally {
cachedWritingReader = null;
}
}
notifyAll();
waitForReadersAndReopen();
}
private void waitForReadersAndReopen() {
while (readerUseCount > 0 || searcherUseCount > 0) {
try {
wait();
} catch (InterruptedException e) {
//Nothing special to do
}
}
reopenReaders();
}
private void reopenReaders() {
if (cachedReader == null)
return;
IndexReader oldReader = cachedReader;
try {
cachedReader = cachedReader.reopen();
if (oldReader != cachedReader) {
oldReader.close();
}
} catch (IOException e) {
LOG.warn("Exception while refreshing lucene index: " + e.getMessage(), e);
}
if (cachedSearcher != null)
cachedSearcher = new IndexSearcher(cachedReader);
}
protected synchronized IndexSearcher getSearcher() throws IOException {
if (cachedSearcher != null) {
searcherUseCount++;
} else {
cachedSearcher = new IndexSearcher(getReader());
readerUseCount--;
searcherUseCount = 1;
}
return cachedSearcher;
}
protected synchronized void releaseSearcher(IndexSearcher searcher) {
if (searcher == null)
return;
if (searcher != cachedSearcher)
throw new IllegalStateException("IndexSearcher was not obtained from getWritingReader().");
searcherUseCount--;
notifyAll();
//try {
//cachedSearcher.close();
//} catch (IOException e) {
//LOG.warn("Exception while closing lucene index: " + e.getMessage(), e);
//} finally {
//cachedSearcher = null;
//}
}
}