package org.neo4j.util; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.Transaction; public abstract class NodeQueueWorker extends Thread { private final GraphDatabaseService graphDb; private final NodeQueue queue; private boolean halted; private boolean requestedToPause; private boolean paused; private int batchSize; public NodeQueueWorker( GraphDatabaseService graphDb, NodeQueue queue, int batchSize, String name ) { super( name ); this.graphDb = graphDb; this.queue = queue; this.batchSize = batchSize; } public NodeQueueWorker( GraphDatabaseService graphDb, NodeQueue queue, int batchSize ) { this( graphDb, queue, batchSize, "NodeQueueWorker" ); } public NodeQueue getQueue() { return this.queue; } public void setPaused( boolean paused ) { if ( this.paused == paused ) { return; } if ( paused && this.requestedToPause ) { waitUntilReallyPaused(); return; } this.requestedToPause = paused; if ( paused ) { waitUntilReallyPaused(); } else { this.paused = false; } } private void waitUntilReallyPaused() { while ( !this.paused ) { try { Thread.sleep( 100 ); } catch ( InterruptedException e ) { // OK } } } public boolean isPaused() { return this.paused; } private void sleepQuiet( long millis ) { try { Thread.sleep( millis ); } catch ( InterruptedException e ) { // Ok } } @Override public void run() { while ( !this.halted ) { if ( this.requestedToPause || this.paused ) { this.paused = true; this.requestedToPause = false; sleepQuiet( 1000 ); continue; } if ( !executeOneBatch() ) { sleepQuiet( 100 ); } } } public void add( Map<String, Object> values ) { Node entry = this.queue.add(); for ( Map.Entry<String, Object> value : values.entrySet() ) { entry.setProperty( value.getKey(), value.getValue() ); } } protected void beforeBatch() { } protected void afterBatch() { } private boolean executeOneBatch() { int entrySize = 0; Collection<Map<String, Object>> entries = null; Transaction tx = graphDb.beginTx(); try { Node[] nodes = this.queue.peek( batchSize ); if ( nodes.length == 0 ) { return false; } entrySize = nodes.length; entries = new ArrayList<Map<String,Object>>( entrySize ); for ( Node node : nodes ) { entries.add( readNode( node ) ); } beforeBatch(); try { for ( Map<String, Object> entry : entries ) { doOne( entry ); } final int size = entrySize; new DeadlockCapsule<Object>( "remover" ) { @Override public Object tryOnce() { queue.remove( size ); return null; } }.run(); } catch ( Exception e ) { // We got an exception, just do nothing and the tx will roll // back so that we can try next time instead. } finally { afterBatch(); } tx.success(); } finally { tx.finish(); } return true; } private Map<String, Object> readNode( Node node ) { Map<String, Object> result = new HashMap<String, Object>(); for ( String key : node.getPropertyKeys() ) { result.put( key, node.getProperty( key ) ); } return result; } private void doOne( Map<String, Object> entry ) throws Exception { // Try a max of ten times if it fails. Exception exception = null; for ( int i = 0; !this.halted && i < 10; i++ ) { try { doHandleEntry( entry ); return; } catch ( Exception e ) { exception = e; } sleepQuiet( 500 ); } handleEntryError( entry, exception ); } protected void handleEntryError( Map<String, Object> entry, Exception exception ) throws Exception { // Add it to the end of the queue add( entry ); } private void doHandleEntry( Map<String, Object> entry ) { handleEntry( entry ); } protected abstract void handleEntry( Map<String, Object> entry ); public void startUp() { this.start(); } public void shutDown() { this.halted = true; while ( this.isAlive() ) { sleepQuiet( 200 ); } } }