package org.neo4j.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.NotFoundException;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
/**
* Wraps several {@link NodeQueue} instances (per transaction).
* See {@link TransactionNodeQueueWorker} for usage.
* @author mattias
*/
public class TransactionNodeQueue
{
public static enum QueueRelTypes implements RelationshipType
{
UPDATE_QUEUE,
INTERNAL_QUEUE,
}
private static final String INDEX_TX_ID = "txid";
private static Map<Integer, TxQueue> queueNodes =
Collections.synchronizedMap( new HashMap<Integer, TxQueue>() );
private final Node rootNode;
public TransactionNodeQueue( Node rootNode )
{
this.rootNode = rootNode;
initialize();
}
private void initialize()
{
Collection<Relationship> toDelete = new ArrayList<Relationship>();
for ( Relationship rel : getRefNode().getRelationships(
QueueRelTypes.UPDATE_QUEUE, Direction.OUTGOING ) )
{
TxQueue queue = new TxQueue( rel.getEndNode() );
if ( queue.peek() != null )
{
queueNodes.put( queue.getTxId(), queue );
}
else
{
toDelete.add( rel );
}
}
for ( Relationship rel : toDelete )
{
Node node = rel.getEndNode();
rel.delete();
node.delete();
}
}
public void add( int txId, Map<String, Object> values )
{
// We must be in a transaction, else the calling code isn't right
TxQueue queue = findQueue( txId, true );
queue.add( values );
}
private void remove( TxQueue queue )
{
int txId = queue.getTxId();
queueNodes.remove( txId );
}
protected Node getRefNode()
{
return this.rootNode;
}
private TxQueue findQueue( int txId, boolean allowCreate )
{
TxQueue queue = queueNodes.get( txId );
if ( queue != null )
{
return queue;
}
if ( allowCreate )
{
Node queueNode = rootNode.getGraphDatabase().createNode();
queueNode.setProperty( INDEX_TX_ID, txId );
getRefNode().createRelationshipTo( queueNode,
QueueRelTypes.UPDATE_QUEUE );
queue = new TxQueue( queueNode );
queueNodes.put( txId, queue );
return queue;
}
return null;
}
public Map<Integer, TxQueue> getQueues()
{
Map<Integer, TxQueue> map = new HashMap<Integer, TxQueue>();
Map.Entry<Integer, TxQueue>[] entries = queueNodes.entrySet().
toArray( new Map.Entry[ queueNodes.size() ] );
for ( Map.Entry<Integer, TxQueue> entry : entries )
{
TxQueue queue = entry.getValue();
try
{
if ( queue.peek() != null )
{
map.put( queue.getTxId(), queue );
}
}
catch ( NotFoundException e )
{
// Since queueNodes is a cache which is filled inside
// transactions it may be the case that a queue is created
// and then the transaction is rolled back... then the
// queueNodes map would still have a queue instance for
// the node which was created in the transaction, but
// not committed.
queueNodes.remove( entry.getKey() );
}
}
return Collections.unmodifiableMap( map );
}
public class TxQueue
{
private final NodeQueue queue;
private final Node node;
private boolean deleted;
public TxQueue( Node rootNode )
{
queue = new NodeQueue( rootNode, QueueRelTypes.INTERNAL_QUEUE );
node = rootNode;
}
Node getRootNode()
{
return node;
}
public int getTxId()
{
return ( Integer ) node.getProperty( INDEX_TX_ID );
}
private void add( Map<String, Object> values )
{
Node node = queue.add();
for ( Map.Entry<String, Object> entry : values.entrySet() )
{
node.setProperty( entry.getKey(), entry.getValue() );
}
}
public Map<String, Object> peek()
{
Collection<Map<String, Object>> result = peek( 1 );
return result.isEmpty() ? null : result.iterator().next();
}
public Collection<Map<String, Object>> peek( int max )
{
if ( deleted )
{
return null;
}
Collection<Map<String, Object>> result =
new ArrayList<Map<String,Object>>( max );
Node[] nodes = queue.peek( max );
for ( Node node : nodes )
{
result.add( readEntry( node ) );
}
return result;
}
private Map<String, Object> readEntry( Node node )
{
Map<String, Object> result = new HashMap<String, Object>();
for ( String key : node.getPropertyKeys() )
{
result.put( key, node.getProperty( key ) );
}
return result;
}
public void remove()
{
remove( 1 );
}
public void remove( int max )
{
if ( deleted )
{
throw new IllegalStateException( "Deleted" );
}
queue.remove( max );
if ( queue.peek() == null )
{
TransactionNodeQueue.this.remove( this );
deleted = true;
}
}
}
}