/** * Copyright (c) 2002-2010 "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.remote; import java.util.Iterator; 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; import org.neo4j.graphdb.ReturnableEvaluator; import org.neo4j.graphdb.StopEvaluator; import org.neo4j.graphdb.Transaction; import org.neo4j.graphdb.TraversalPosition; import org.neo4j.graphdb.Traverser.Order; import org.neo4j.remote.RemoteGraphDbEngine.BatchIterable; import org.neo4j.remote.RemoteGraphDbEngine.CloseableIteratorWithSize; import org.neo4j.index.IndexHits; class RemoteTransaction implements Transaction { private enum State { /** * The state of this transaction is not defined yet. This means failure. */ UNDEFINED( false ) { @Override void update( RemoteTransaction tx ) { } }, /** * The state of this transaction is success. */ SUCCESS( false ) { @Override void update( RemoteTransaction tx ) { if ( !tx.state.terminal ) { tx.state = this; } else { tx.state.update( tx ); } } @Override void finish( RemoteTransaction tx ) { tx.state = COMPLETED; tx.commit(); } }, /** * The state of this transaction is failure and can never be success. */ FAILURE( true ) { @Override void update( RemoteTransaction tx ) { tx.state = this; } }, /** * The state of this transaction is completed. */ COMPLETED( true ) { @Override void update( RemoteTransaction tx ) { this.finish( tx ); } @Override void finish( RemoteTransaction tx ) { throw new IllegalStateException( "The transaction " + tx + " is completed." ); } }; private final boolean terminal; private State( boolean terminal ) { this.terminal = terminal; } abstract void update( RemoteTransaction tx ); void finish( RemoteTransaction tx ) { tx.state = COMPLETED; tx.rollback(); } } private State state = State.UNDEFINED; public void failure() { State.FAILURE.update( this ); } public void success() { State.SUCCESS.update( this ); } public void finish() { state.finish( this ); engine.endTx( null ); } // Internal state private final RemoteGraphDbEngine engine; final int id; private final Map<Long, RemoteNode> nodeCache = null; private final Map<Long, RemoteRelationship> relationshipCache = null; @Override public String toString() { return "RemoteTransaction[" + engine + ", id=" + id + "]"; } RemoteTransaction( RemoteGraphDbEngine txService, int id ) { this.engine = txService; this.id = id; } RemoteTransaction createPlaceboTransaction() { final RemoteTransaction previous = this; return new RemoteTransaction( engine, id ) { @Override public void failure() { State.FAILURE.update( previous ); } @Override public void success() { State.SUCCESS.update( previous ); } @Override public void finish() { } @Override RemoteTransaction createPlaceboTransaction() { return previous.createPlaceboTransaction(); } @Override public String toString() { return "Placebo" + super.toString(); } }; } // Implementation internals // Transaction management private void commit() { engine.commit( id ); } private void rollback() { engine.rollback( id ); } // Node management private RemoteNode newNode( long nodeId ) { RemoteNode node = new RemoteNode( engine, nodeId ); if ( nodeCache != null ) { nodeCache.put( nodeId, node ); } return node; } private RemoteNode getNode( long nodeId, boolean verified ) { RemoteNode node = null; if ( nodeCache != null ) { node = nodeCache.get( nodeId ); } if ( node == null ) { if ( verified || engine.hasNodeWithId( id, nodeId ) ) { node = newNode( nodeId ); } else { throw new NotFoundException( "TODO: exception message. No node with id=" + nodeId ); } } return node; } RemoteNode createNode() { return newNode( engine.createNode( id ) ); } RemoteNode getNodeById( @SuppressWarnings( "hiding" ) long id ) { return getNode( id, false ); } RemoteNode getReferenceNode() { return getNode( engine.getReferenceNode( id ), true ); } void deleteNode( RemoteNode node ) { engine.deleteNode( id, node.id ); } Iterable<Node> getAllNodes() { return new ConversionIterable<NodeSpecification, Node>( engine .getAllNodes( id ) ) { @Override Node convert( NodeSpecification source ) { return getNode( source.id, true ); } }; } Iterable<RelationshipType> getRelationshipTypes() { return new ConversionIterable<String, RelationshipType>( engine .getRelationshipTypes( id ) ) { @Override RelationshipType convert( String source ) { return engine.type( source ); } }; } // Relationship management private RemoteRelationship newRelationship( RelationshipSpecification spec ) { RemoteRelationship relationship = new RemoteRelationship( engine, spec.relationshipId, engine.type( spec.name ), getNode( spec.startNodeId, true ), getNode( spec.endNodeId, true ) ); if ( relationshipCache != null ) { relationshipCache.put( spec.relationshipId, relationship ); } return relationship; } private RemoteRelationship getRelationship( long relationshipId, RelationshipSpecification spec ) { RemoteRelationship relationship = null; if ( relationshipCache != null ) { relationship = relationshipCache.get( relationshipId ); } if ( relationship == null ) { if ( spec == null ) { spec = engine.getRelationshipById( id, relationshipId ); } relationship = newRelationship( spec ); } return relationship; } RemoteRelationship createRelationship( RelationshipType type, RemoteNode startNode, RemoteNode endNode ) { return newRelationship( engine.createRelationship( id, type.name(), startNode.id, endNode.id ) ); } RemoteRelationship getRelationshipById( @SuppressWarnings( "hiding" ) long id ) { return getRelationship( id, null ); } Iterable<Relationship> getRelationships( RemoteNode node, Direction dir, RelationshipType... types ) { final Iterable<RelationshipSpecification> spec; if ( types == null ) { spec = engine.getAllRelationships( id, node.id, dir ); } else { String[] typeNames = new String[ types.length ]; for ( int i = 0; i < types.length; i++ ) { typeNames[ i ] = types[ i ].name(); } spec = engine.getRelationships( id, node.id, dir, typeNames ); } return new ConversionIterable<RelationshipSpecification, Relationship>( spec ) { @Override Relationship convert( RelationshipSpecification source ) { return getRelationship( source.relationshipId, source ); } }; } void deleteRelationship( RemoteRelationship relationship ) { engine.deleteRelationship( id, relationship.id ); } // Traversal Iterable<TraversalPosition> traverse( RemoteNode startNode, Order order, StopEvaluator stopEvaluator, ReturnableEvaluator returnableEvaluator, RelationshipType[] relationshipTypes, Direction[] directions ) { return engine.traverse( id, startNode, order, stopEvaluator, returnableEvaluator, relationshipTypes, directions ); } // Property management Object getProperty( RemoteNode owner, String key ) { return engine.getNodeProperty( id, owner.id, key ); } Object getProperty( RemoteRelationship owner, String key ) { return engine.getRelationshipProperty( id, owner.id, key ); } void setProperty( RemoteNode owner, String key, Object value ) { engine.setNodeProperty( id, owner.id, key, value ); } void setProperty( RemoteRelationship owner, String key, Object value ) { engine.setRelationshipProperty( id, owner.id, key, value ); } Iterable<String> getPropertyKeys( RemoteNode owner ) { return engine.getNodePropertyKeys( id, owner.id ); } Iterable<String> getPropertyKeys( RemoteRelationship owner ) { return engine.getRelationshipPropertyKeys( id, owner.id ); } boolean hasProperty( RemoteNode owner, String key ) { return engine.hasNodeProperty( id, owner.id, key ); } boolean hasProperty( RemoteRelationship owner, String key ) { return engine.hasRelationshipProperty( id, owner.id, key ); } Object removeProperty( RemoteNode owner, String key ) { return engine.removeNodeProperty( id, owner.id, key ); } Object removeProperty( RemoteRelationship owner, String key ) { return engine.removeRelationshipProperty( id, owner.id, key ); } // Index operations IndexHits<Node> getIndexNodes( int indexId, String key, Object value ) { BatchIterable<NodeSpecification> iter = engine.getIndexNodes( id, indexId, key, value ); return new IndexHitsImpl( iter ) { @Override Node convert( NodeSpecification source ) { return getNode( source.id, true ); } }; } void indexNode( int indexId, RemoteNode node, String key, Object value ) { engine.indexNode( id, indexId, node.id, key, value ); } void removeIndexNode( int indexId, RemoteNode node, String key, Object value ) { engine.removeIndexNode( id, indexId, node.id, key, value ); } void removeIndexNode( int indexId, RemoteNode node, String key ) { engine.removeIndexNode( id, indexId, node.id, key ); } void removeIndexNode( int indexId, String key ) { engine.removeIndexNode( id, indexId, key ); } private static abstract class ConversionIterable<F, T> implements Iterable<T> { private final Iterable<F> source; ConversionIterable( Iterable<F> source ) { this.source = source; } abstract T convert( @SuppressWarnings( "hiding" ) F source ); public Iterator<T> iterator() { return new CloseableIterator<T>() { Iterator<F> iter = source.iterator(); public boolean hasNext() { return iter.hasNext(); } public T next() { return convert( iter.next() ); } public void remove() { iter.remove(); } @SuppressWarnings( "unchecked" ) public void close() { if ( source instanceof CloseableIterator ) { ( ( CloseableIterator ) source ).close(); } } }; } } private static abstract class IndexHitsImpl implements IndexHits<Node>, CloseableIterator<Node> { private final CloseableIteratorWithSize<NodeSpecification> nodes; IndexHitsImpl( BatchIterable<NodeSpecification> nodes ) { this.nodes = nodes.iterator(); } abstract Node convert( NodeSpecification source ); public final Iterator<Node> iterator() { return this; } @SuppressWarnings( "unchecked" ) public final void close() { if ( nodes instanceof CloseableIterator ) { ( ( CloseableIterator ) nodes ).close(); } } public final int size() { return ( int ) nodes.size(); } public final boolean hasNext() { return nodes.hasNext(); } public final Node next() { return convert( nodes.next() ); } public final void remove() { nodes.remove(); } } }