/*
* Copyright (c) 2002-2009 "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.lucene;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexWriter;
import org.neo4j.graphdb.Node;
import org.neo4j.index.lucene.LuceneCommand.AddCommand;
import org.neo4j.index.lucene.LuceneCommand.RemoveCommand;
import org.neo4j.kernel.impl.transaction.xaframework.XaCommand;
import org.neo4j.kernel.impl.transaction.xaframework.XaLogicalLog;
import org.neo4j.kernel.impl.transaction.xaframework.XaTransaction;
class LuceneTransaction extends XaTransaction
{
private final Map<String,Map<Object,Set<Long>>> txIndexed =
new HashMap<String,Map<Object,Set<Long>>>();
private final Map<String,Map<Object,Set<Long>>> txRemoved =
new HashMap<String,Map<Object,Set<Long>>>();
private final LuceneDataSource luceneDs;
private final Map<String,List<LuceneCommand>> commandMap =
new HashMap<String,List<LuceneCommand>>();
LuceneTransaction( int identifier, XaLogicalLog xaLog,
LuceneDataSource luceneDs )
{
super( identifier, xaLog );
this.luceneDs = luceneDs;
}
void index( Node node, String key, Object value )
{
insert( node, key, value, txRemoved, txIndexed );
}
void removeIndex( Node node, String key, Object value )
{
insert( node, key, value, txIndexed, txRemoved );
}
private void insert( Node node, String key, Object value,
Map<String,Map<Object,Set<Long>>> toRemoveFrom,
Map<String,Map<Object,Set<Long>>> toInsertInto )
{
delFromIndex( node, key, value, toRemoveFrom );
Map<Object,Set<Long>> keyIndex = toInsertInto.get( key );
if ( keyIndex == null )
{
keyIndex = new HashMap<Object,Set<Long>>();
toInsertInto.put( key, keyIndex );
}
Set<Long> nodeIds = keyIndex.get( value );
if ( nodeIds == null )
{
nodeIds = new HashSet<Long>();
}
nodeIds.add( node.getId() );
keyIndex.put( value, nodeIds );
}
private boolean delFromIndex( Node node, String key, Object value,
Map<String,Map<Object,Set<Long>>> map )
{
Map<Object,Set<Long>> keyIndex = map.get( key );
if ( keyIndex == null )
{
return false;
}
Set<Long> nodeIds = keyIndex.get( value );
if ( nodeIds != null )
{
return nodeIds.remove( node.getId() );
}
return false;
}
Set<Long> getDeletedNodesFor( String key, Object value )
{
Map<Object,Set<Long>> keyIndex = txRemoved.get( key );
if ( keyIndex != null )
{
Set<Long> nodeIds = keyIndex.get( value );
if ( nodeIds != null )
{
return nodeIds;
}
}
return Collections.emptySet();
}
Set<Long> getNodesFor( String key, Object value )
{
Map<Object,Set<Long>> keyIndex = txIndexed.get( key );
if ( keyIndex != null )
{
Set<Long> nodeIds = keyIndex.get( value );
if ( nodeIds != null )
{
return nodeIds;
}
}
return Collections.emptySet();
}
protected LuceneDataSource getDataSource()
{
return this.luceneDs;
}
private void indexWriter( IndexWriter writer, long nodeId, String key,
Object value )
{
Document document = new Document();
this.luceneDs.fillDocument( document, nodeId, key, value );
try
{
writer.addDocument( document );
}
catch ( IOException e )
{
throw new RuntimeException( e );
}
}
@Override
protected void doAddCommand( XaCommand command )
{
LuceneCommand luceneCommand = ( LuceneCommand ) command;
String key = luceneCommand.getKey();
List<LuceneCommand> list = commandMap.get( key );
if ( list == null )
{
list = new ArrayList<LuceneCommand>();
commandMap.put( key, list );
}
list.add( luceneCommand );
}
@Override
protected void doCommit()
{
luceneDs.getWriteLock();
try
{
for ( Map.Entry<String, List<LuceneCommand>> entry :
this.commandMap.entrySet() )
{
String key = entry.getKey();
IndexWriter writer = luceneDs.getIndexWriter( key );
for ( LuceneCommand command : entry.getValue() )
{
if ( command instanceof AddCommand )
{
indexWriter( writer, command.getNodeId(), key,
command.getValue() );
}
else if ( command instanceof RemoveCommand )
{
luceneDs.deleteDocumentsUsingWriter(
writer, command.getNodeId(), command.getValue() );
}
else
{
throw new RuntimeException( "Unknown command type " +
command + ", " + command.getClass() );
}
luceneDs.invalidateCache( key, command.getValue() );
}
luceneDs.removeWriter( key, writer );
luceneDs.invalidateIndexSearcher( key );
}
}
finally
{
luceneDs.releaseWriteLock();
}
}
@Override
protected void doPrepare()
{
for ( String key : txIndexed.keySet() )
{
Map<Object,Set<Long>> addIndex = txIndexed.get( key );
for ( Object object : addIndex.keySet() )
{
for ( long id : addIndex.get( object ) )
{
addCommand( new AddCommand( id, key, object.toString() ) );
}
}
}
for ( String key : txRemoved.keySet() )
{
Map<Object,Set<Long>> removeIndex = txRemoved.get( key );
for ( Object object : removeIndex.keySet() )
{
for ( long id : removeIndex.get( object ) )
{
addCommand( new RemoveCommand( id, key,
object.toString() ) );
}
}
}
}
@Override
protected void doRollback()
{
// TODO Auto-generated method stub
commandMap.clear();
txIndexed.clear();
txRemoved.clear();
}
@Override
public boolean isReadOnly()
{
return false;
}
}