/*
* Copyright (C) 2009 eXo Platform SAS.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.xcmis.search.lucene.index;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.lucene.analysis.SimpleAnalyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriter.MaxFieldLength;
import org.apache.lucene.index.SerialMergeScheduler;
import org.apache.lucene.index.Term;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.LockObtainFailedException;
import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.util.Version;
import org.xcmis.spi.utils.Logger;
/**
* Created by The eXo Platform SAS.
*
* @author <a href="mailto:Sergey.Kabashnyuk@gmail.com">Sergey Kabashnyuk</a>
* @version $Id: ReducibleInMemoryIndexDataKeeper.java 2 2010-02-04 17:21:49Z andrew00x $
*/
public class ReducibleInMemoryIndexDataKeeper implements TransactionableLuceneIndexDataManager
{
/**
* Class logger.g
*/
private static final Logger LOG = Logger.getLogger(ReducibleInMemoryIndexDataKeeper.class);
/**
* Index storage.
*/
public final RAMDirectory indexDirectiry;
public long lastModifedTime;
/**
* pending committed flag
*/
boolean isPendingCommited;
/**
* Started flag.
*/
boolean isStarted;
/**
* Stopped flag.
*/
boolean isStoped;
/**
* Documents map
*/
private final Map<String, Document> documentsBuffer;
/**
* Index storage.
*/
private IndexReader indexReader;
/**
* Pending document buffer.
*/
private final Map<String, Document> pendingDocumentsBuffer;
/**
* Data keeper transaction log.
*/
private final TransactionLog transactionLog;
/**
* @param changes
* @throws IndexException
*/
public ReducibleInMemoryIndexDataKeeper(final LoggedIndexTransaction<Document> changes) throws IndexException
{
// this.changes = changes;
this.indexDirectiry = new RAMDirectory();
this.transactionLog = changes.getTransactionLog();
this.isStarted = false;
this.isPendingCommited = false;
this.lastModifedTime = System.currentTimeMillis();
this.documentsBuffer = new HashMap<String, Document>(changes.getAddedDocuments());
this.pendingDocumentsBuffer = new HashMap<String, Document>(changes.getAddedDocuments());
initDirectory();
}
public ReducibleInMemoryIndexDataKeeper(final RAMDirectory indexDirectiry,
final Map<String, Document> documentsBuffer, final Map<String, Document> pendingDocumentsBuffer,
final TransactionLog transactionLog)
{
this.indexDirectiry = indexDirectiry;
this.documentsBuffer = documentsBuffer;
this.pendingDocumentsBuffer = pendingDocumentsBuffer;
this.transactionLog = transactionLog;
this.isStarted = false;
// this.changes = null;
this.isPendingCommited = pendingDocumentsBuffer.size() == 0;
this.lastModifedTime = System.currentTimeMillis();
}
/**
* @return
* @throws IndexException
*/
public Directory getDirectory() throws IndexException
{
if (!isPendingCommited)
{
commitPending();
}
return indexDirectiry;
}
/**
* {@inheritDoc}
*/
public long getDirectorySize(final boolean includeInherited)
{
return this.indexDirectiry.sizeInBytes();
}
/**
* {@inheritDoc}
*/
public Document getDocument(final String uuid) throws IndexException
{
return this.documentsBuffer.get(uuid);
}
/**
* @return the documentsCount
*/
public long getDocumentCount()
{
return this.documentsBuffer.size();
}
/**
* @return the documentsUuids
*/
public Map<String, Document> getDocumentsBuffer()
{
return this.documentsBuffer;
}
/**
* {@inheritDoc}
*
* @throws IndexException
*/
public IndexReader getIndexReader() throws IndexException
{
// TODO check is stated;
if (this.documentsBuffer.size() < 1)
{
return null;
}
if (!this.isPendingCommited)
{
this.commitPending();
}
try
{
if (this.indexReader == null)
{
this.indexReader = IndexReader.open(this.indexDirectiry);
}
else if (!this.indexReader.isCurrent())
{
this.indexReader = this.indexReader.reopen();
}
}
catch (final CorruptIndexException e)
{
throw new IndexException(e.getLocalizedMessage(), e);
}
catch (final IOException e)
{
// e.printStackTrace()
throw new IndexException(e.getLocalizedMessage(), e);
}
return this.indexReader;
}
public long getLastModifedTime()
{
return this.lastModifedTime;
}
/**
* @return the pendingDocumentsBuffer
*/
public Map<String, Document> getPendingDocumentsBuffer()
{
return this.pendingDocumentsBuffer;
}
/**
* {@inheritDoc}
*/
public TransactionLog getTransactionLog()
{
return this.transactionLog;
}
/**
* @return the isPendingCommited
*/
public boolean isPendingCommited()
{
return this.isPendingCommited;
}
/**
* @return the isStarted
*/
public boolean isStarted()
{
return this.isStarted;
}
/**
* @return the isStoped
*/
public boolean isStoped()
{
return this.isStoped;
}
/**
* {@inheritDoc}
*/
public IndexTransactionModificationReport save(final IndexTransaction<Document> changes) throws IndexException,
IndexTransactionException
{
// Accepting only deletes , and removing for updates.
final Set<String> addedDocuments = new HashSet<String>();
final Set<String> removedDocuments = new HashSet<String>();
final Set<String> updatedDocuments = new HashSet<String>();
try
{
// index already started
synchronized (indexDirectiry)
{
final Set<String> removed = changes.getRemovedDocuments();
// int numDoc = 0;
IndexWriter writer = null;
for (final String removedUuid : removed)
{
if (this.documentsBuffer.remove(removedUuid) != null)
{
removedDocuments.add(removedUuid);
if (this.isPendingCommited || this.pendingDocumentsBuffer.remove(removedUuid) == null)
{
if (writer == null)
{
writer = new IndexWriter(this.indexDirectiry,
new StandardAnalyzer(Version.LUCENE_35), MaxFieldLength.UNLIMITED);
//to avoid deadlock
writer.setMergeScheduler(new SerialMergeScheduler());
}
writer.deleteDocuments(new Term(FieldNames.UUID, removedUuid));
}
}
}
if (writer != null)
{
writer.commit();
writer.close();
this.lastModifedTime = System.currentTimeMillis();
}
}
}
catch (final CorruptIndexException e)
{
throw new IndexException(e.getLocalizedMessage(), e);
}
catch (final IOException e)
{
throw new IndexException(e.getLocalizedMessage(), e);
}
return new IndexTransactionModificationReportImpl(addedDocuments, removedDocuments, updatedDocuments);
}
/**
* {@inheritDoc}
*
* @throws IndexException
*/
public void start() throws IndexException
{
this.isStarted = true;
}
/**
* {@inheritDoc}
*
* @throws IndexException
*/
public void stop() throws IndexException
{
this.indexDirectiry.close();
this.isStoped = true;
}
/**
* @throws CorruptIndexException
* @throws LockObtainFailedException
* @throws IOException
*/
private void commitPending() throws IndexException
{
IndexWriter writer = null;
try
{
if (this.pendingDocumentsBuffer.size() > 0)
{
synchronized (this.indexDirectiry)
{
writer = new IndexWriter(this.indexDirectiry, new StandardAnalyzer(Version.LUCENE_35), MaxFieldLength.UNLIMITED);
for (final Entry<String, Document> addedDocument : this.pendingDocumentsBuffer.entrySet())
{
writer.addDocument(addedDocument.getValue());
}
this.pendingDocumentsBuffer.clear();
// write changes
writer.commit();
this.isPendingCommited = true;
writer.close();
}
}
}
catch (final CorruptIndexException e)
{
throw new IndexException(e.getLocalizedMessage(), e);
}
catch (final LockObtainFailedException e)
{
throw new IndexException(e.getLocalizedMessage(), e);
}
catch (final IOException e)
{
throw new IndexException(e.getLocalizedMessage(), e);
}
finally
{
if (writer != null)
{
try
{
writer.close();
}
catch (CorruptIndexException e)
{
throw new IndexException(e.getLocalizedMessage(), e);
}
catch (IOException e)
{
throw new IndexException(e.getLocalizedMessage(), e);
}
}
}
}
/**
* @throws CorruptIndexException
* @throws LockObtainFailedException
* @throws IOException
*/
private void initDirectory() throws IndexException
{
try
{
IndexWriter.MaxFieldLength fieldLength = new IndexWriter.MaxFieldLength(IndexWriter.DEFAULT_MAX_FIELD_LENGTH);
IndexWriter iw = new IndexWriter(indexDirectiry, new SimpleAnalyzer(), true, fieldLength);
iw.close();
}
catch (IOException e)
{
throw new IndexException(e.getLocalizedMessage(), e);
}
}
}