/**
* Copyright (c) 2002-2011 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.index.impl.lucene;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriter.MaxFieldLength;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Searcher;
import org.apache.lucene.search.Sort;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.RAMDirectory;
import org.neo4j.helpers.Pair;
class FullTxData extends ExactTxData
{
private Directory directory;
private IndexWriter writer;
private boolean modified;
private IndexReader reader;
private IndexSearcher searcher;
private final Map<Long, Document> cachedDocuments = new HashMap<Long, Document>();
FullTxData( LuceneIndex index )
{
super( index );
}
@Override
TxData add( Object entityId, String key, Object value )
{
super.add( entityId, key, value );
try
{
ensureLuceneDataInstantiated();
long id = entityId instanceof Long ? (Long) entityId : ((RelationshipId)entityId).id;
Document document = findDocument( id );
if ( document != null )
{
index.type.addToDocument( document, key, value );
writer.updateDocument( index.type.idTerm( id ), document );
}
else
{
document = index.getIdentifier().entityType.newDocument( entityId );
cachedDocuments.put( id, document );
index.type.addToDocument( document, key, value );
writer.addDocument( document );
}
invalidateSearcher();
return this;
}
catch ( IOException e )
{
throw new RuntimeException( e );
}
}
private Document findDocument( long id )
{
return cachedDocuments.get( id );
}
private void ensureLuceneDataInstantiated()
{
if ( this.directory == null )
{
try
{
this.directory = new RAMDirectory();
this.writer = new IndexWriter( directory, index.type.analyzer,
MaxFieldLength.UNLIMITED );
}
catch ( IOException e )
{
throw new RuntimeException( e );
}
}
}
@Override
TxData remove( Object entityId, String key, Object value )
{
super.remove( entityId, key, value );
try
{
ensureLuceneDataInstantiated();
long id = entityId instanceof Long ? (Long) entityId : ((RelationshipId)entityId).id;
Document document = findDocument( id );
if ( document != null )
{
index.type.removeFromDocument( document, key, value );
if ( LuceneDataSource.documentIsEmpty( document ) )
{
writer.deleteDocuments( index.type.idTerm( id ) );
}
else
{
writer.updateDocument( index.type.idTerm( id ), document );
}
}
invalidateSearcher();
return this;
}
catch ( IOException e )
{
throw new RuntimeException( e );
}
}
@Override
Pair<Collection<Long>, TxData> query( Query query, QueryContext contextOrNull )
{
return internalQuery( query, contextOrNull );
}
private Pair<Collection<Long>, TxData> internalQuery( Query query, QueryContext contextOrNull )
{
if ( this.directory == null )
{
return Pair.<Collection<Long>, TxData>of( Collections.<Long>emptySet(), this );
}
try
{
Sort sorting = contextOrNull != null ? contextOrNull.sorting : null;
boolean prioritizeCorrectness = contextOrNull == null || !contextOrNull.tradeCorrectnessForSpeed;
Hits hits = new Hits( searcher( prioritizeCorrectness ), query, null, sorting, prioritizeCorrectness );
Collection<Long> result = new ArrayList<Long>();
for ( int i = 0; i < hits.length(); i++ )
{
result.add( Long.parseLong( hits.doc( i ).getField(
LuceneIndex.KEY_DOC_ID ).stringValue() ) );
}
return Pair.<Collection<Long>, TxData>of( result, this );
}
catch ( IOException e )
{
throw new RuntimeException( e );
}
}
@Override
void close()
{
safeClose( this.writer );
safeClose( this.reader );
safeClose( this.searcher );
}
private void invalidateSearcher()
{
this.modified = true;
}
private IndexSearcher searcher( boolean allowRefreshSearcher )
{
if ( this.searcher != null && (!modified || !allowRefreshSearcher) )
{
return this.searcher;
}
try
{
IndexReader newReader = this.reader == null ? this.writer.getReader() : this.reader.reopen();
if ( newReader == this.reader )
{
return this.searcher;
}
safeClose( reader );
this.reader = newReader;
safeClose( searcher );
searcher = new IndexSearcher( reader );
}
catch ( IOException e )
{
throw new RuntimeException( e );
}
finally
{
if ( allowRefreshSearcher )
{
this.modified = false;
}
}
return this.searcher;
}
private static void safeClose( Object object )
{
if ( object == null )
{
return;
}
try
{
if ( object instanceof IndexWriter )
{
( ( IndexWriter ) object ).close();
}
else if ( object instanceof IndexSearcher )
{
( ( IndexSearcher ) object ).close();
}
else if ( object instanceof IndexReader )
{
( ( IndexReader ) object ).close();
}
}
catch ( IOException e )
{
// Ok
}
}
@Override
Pair<Searcher, TxData> asSearcher( QueryContext context )
{
boolean refresh = context == null || !context.tradeCorrectnessForSpeed;
return Pair.of( (Searcher) searcher( refresh ), (TxData) this );
}
}