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.GraphDatabaseService; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.NotFoundException; import org.neo4j.graphdb.Relationship; import org.neo4j.graphdb.RelationshipType; import org.neo4j.graphdb.Transaction; /** * 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 GraphDatabaseService graphDb; private GraphDatabaseUtil graphDbUtil; private Node rootNode; public TransactionNodeQueue( GraphDatabaseService graphDb, Node rootNode ) { this.graphDb = graphDb; this.graphDbUtil = new GraphDatabaseUtil( graphDb ); this.rootNode = rootNode; initialize(); } private void initialize() { Transaction tx = graphDb.beginTx(); try { 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(); } tx.success(); } finally { tx.finish(); } } 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 = graphDb.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() { Transaction tx = graphDb.beginTx(); try { 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() ); } } tx.success(); return Collections.unmodifiableMap( map ); } finally { tx.finish(); } } public class TxQueue { private NodeQueue queue; private Node node; private boolean deleted; public TxQueue( Node rootNode ) { queue = new NodeQueue( graphDb, rootNode, QueueRelTypes.INTERNAL_QUEUE ); this.node = rootNode; } Node getRootNode() { return node; } public int getTxId() { return ( Integer ) graphDbUtil.getProperty( node, 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; } Transaction tx = graphDb.beginTx(); try { Collection<Map<String, Object>> result = new ArrayList<Map<String,Object>>( max ); Node[] nodes = queue.peek( max ); for ( Node node : nodes ) { result.add( readEntry( node ) ); } tx.success(); return result; } finally { tx.finish(); } } 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" ); } Transaction tx = graphDb.beginTx(); try { queue.remove( max ); if ( queue.peek() == null ) { TransactionNodeQueue.this.remove( this ); deleted = true; } tx.success(); } finally { tx.finish(); } } } }