/** * Copyright (c) 2002-2013 "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 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.neo4j.kernel.impl.core; import static org.neo4j.kernel.impl.util.RelIdArray.empty; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import java.util.List; 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.Traverser; import org.neo4j.graphdb.Traverser.Order; import org.neo4j.helpers.Triplet; import org.neo4j.kernel.impl.nioneo.store.PropertyData; import org.neo4j.kernel.impl.nioneo.store.Record; import org.neo4j.kernel.impl.transaction.LockType; import org.neo4j.kernel.impl.traversal.OldTraverserWrapper; import org.neo4j.kernel.impl.util.ArrayMap; import org.neo4j.kernel.impl.util.CombinedRelIdIterator; import org.neo4j.kernel.impl.util.RelIdArray; import org.neo4j.kernel.impl.util.RelIdArray.DirectionWrapper; import org.neo4j.kernel.impl.util.RelIdIterator; class NodeImpl extends Primitive { private static final RelIdArray[] NO_RELATIONSHIPS = new RelIdArray[0]; private volatile RelIdArray[] relationships; private long relChainPosition = Record.NO_NEXT_RELATIONSHIP.intValue(); private long id; NodeImpl( long id ) { this( id, false ); } // newNode will only be true for NodeManager.createNode NodeImpl( long id, boolean newNode ) { super( newNode ); this.id = id; if ( newNode ) { relationships = NO_RELATIONSHIPS; } } @Override public long getId() { return id; } @Override public int hashCode() { return (int) (( id >>> 32 ) ^ id ); } @Override public boolean equals( Object obj ) { return this == obj || ( obj instanceof NodeImpl && ( (NodeImpl) obj ).id == id ); } @Override protected PropertyData changeProperty( NodeManager nodeManager, PropertyData property, Object value ) { return nodeManager.nodeChangeProperty( this, property, value ); } @Override protected PropertyData addProperty( NodeManager nodeManager, PropertyIndex index, Object value ) { return nodeManager.nodeAddProperty( this, index, value ); } @Override protected void removeProperty( NodeManager nodeManager, PropertyData property ) { nodeManager.nodeRemoveProperty( this, property ); } @Override protected ArrayMap<Integer, PropertyData> loadProperties( NodeManager nodeManager, boolean light ) { return nodeManager.loadProperties( this, light ); } List<RelIdIterator> getAllRelationships( NodeManager nodeManager, DirectionWrapper direction ) { ensureRelationshipMapNotNull( nodeManager ); List<RelIdIterator> relTypeList = new LinkedList<RelIdIterator>(); boolean hasModifications = nodeManager.getLockReleaser().hasRelationshipModifications( this ); ArrayMap<String,RelIdArray> addMap = null; if ( hasModifications ) { addMap = nodeManager.getCowRelationshipAddMap( this ); } for ( RelIdArray src : relationships ) { String type = src.getType(); Collection<Long> remove = null; RelIdArray add = null; RelIdIterator iterator = null; if ( hasModifications ) { remove = nodeManager.getCowRelationshipRemoveMap( this, type ); if ( addMap != null ) { add = addMap.get( type ); } iterator = new CombinedRelIdIterator( type, direction, src, add, remove ); } else { iterator = src.iterator( direction ); } relTypeList.add( iterator ); } if ( addMap != null ) { for ( String type : addMap.keySet() ) { if ( getRelIdArray( type ) == null ) { Collection<Long> remove = nodeManager.getCowRelationshipRemoveMap( this, type ); RelIdArray add = addMap.get( type ); relTypeList.add( new CombinedRelIdIterator( type, direction, null, add, remove ) ); } } } return relTypeList; } List<RelIdIterator> getAllRelationshipsOfType( NodeManager nodeManager, DirectionWrapper direction, RelationshipType... types) { ensureRelationshipMapNotNull( nodeManager ); List<RelIdIterator> relTypeList = new LinkedList<RelIdIterator>(); boolean hasModifications = nodeManager.getLockReleaser().hasRelationshipModifications( this ); for ( RelationshipType type : types ) { String typeName = type.name(); RelIdArray src = getRelIdArray( typeName ); Collection<Long> remove = null; RelIdArray add = null; RelIdIterator iterator = null; if ( hasModifications ) { remove = nodeManager.getCowRelationshipRemoveMap( this, typeName ); add = nodeManager.getCowRelationshipAddMap( this, typeName ); iterator = new CombinedRelIdIterator( typeName, direction, src, add, remove ); } else { iterator = src != null ? src.iterator( direction ) : empty( typeName ).iterator( direction ); } relTypeList.add( iterator ); } return relTypeList; } public Iterable<Relationship> getRelationships( NodeManager nodeManager ) { return new IntArrayIterator( getAllRelationships( nodeManager, DirectionWrapper.BOTH ), this, DirectionWrapper.BOTH, nodeManager, new RelationshipType[0], !hasMoreRelationshipsToLoad() ); } public Iterable<Relationship> getRelationships( NodeManager nodeManager, Direction dir ) { DirectionWrapper direction = RelIdArray.wrap( dir ); return new IntArrayIterator( getAllRelationships( nodeManager, direction ), this, direction, nodeManager, new RelationshipType[0], !hasMoreRelationshipsToLoad() ); } public Iterable<Relationship> getRelationships( NodeManager nodeManager, RelationshipType type ) { RelationshipType types[] = new RelationshipType[] { type }; return new IntArrayIterator( getAllRelationshipsOfType( nodeManager, DirectionWrapper.BOTH, types ), this, DirectionWrapper.BOTH, nodeManager, types, !hasMoreRelationshipsToLoad() ); } public Iterable<Relationship> getRelationships( NodeManager nodeManager, RelationshipType... types ) { return new IntArrayIterator( getAllRelationshipsOfType( nodeManager, DirectionWrapper.BOTH, types ), this, DirectionWrapper.BOTH, nodeManager, types, !hasMoreRelationshipsToLoad() ); } public Iterable<Relationship> getRelationships( NodeManager nodeManager, Direction direction, RelationshipType... types ) { DirectionWrapper dir = RelIdArray.wrap( direction ); return new IntArrayIterator( getAllRelationshipsOfType( nodeManager, dir, types ), this, dir, nodeManager, types, !hasMoreRelationshipsToLoad() ); } public Relationship getSingleRelationship( NodeManager nodeManager, RelationshipType type, Direction dir ) { DirectionWrapper direction = RelIdArray.wrap( dir ); RelationshipType types[] = new RelationshipType[] { type }; Iterator<Relationship> rels = new IntArrayIterator( getAllRelationshipsOfType( nodeManager, direction, types ), this, direction, nodeManager, types, !hasMoreRelationshipsToLoad() ); if ( !rels.hasNext() ) { return null; } Relationship rel = rels.next(); if ( rels.hasNext() ) { throw new NotFoundException( "More than one relationship[" + type + ", " + dir + "] found for " + this ); } return rel; } public Iterable<Relationship> getRelationships( NodeManager nodeManager, RelationshipType type, Direction dir ) { RelationshipType types[] = new RelationshipType[] { type }; DirectionWrapper direction = RelIdArray.wrap( dir ); return new IntArrayIterator( getAllRelationshipsOfType( nodeManager, direction, types ), this, direction, nodeManager, types, !hasMoreRelationshipsToLoad() ); } public void delete( NodeManager nodeManager ) { nodeManager.acquireLock( this, LockType.WRITE ); boolean success = false; try { ArrayMap<Integer,PropertyData> skipMap = nodeManager.getCowPropertyRemoveMap( this, true ); ArrayMap<Integer,PropertyData> removedProps = nodeManager.deleteNode( this ); if ( removedProps.size() > 0 ) { for ( int index : removedProps.keySet() ) { skipMap.put( index, removedProps.get( index ) ); } } success = true; } finally { nodeManager.releaseLock( this, LockType.WRITE ); if ( !success ) { nodeManager.setRollbackOnly(); } } } /** * Returns this node's string representation. * * @return the string representation of this node */ @Override public String toString() { return "NodeImpl#" + this.getId(); } // caller is responsible for acquiring lock // this method is only called when a relationship is created or // a relationship delete is undone or when the full node is loaded void addRelationship( NodeManager nodeManager, RelationshipType type, long relId, DirectionWrapper dir ) { RelIdArray relationshipSet = nodeManager.getCowRelationshipAddMap( this, type.name(), true ); relationshipSet.add( relId, dir ); } // caller is responsible for acquiring lock // this method is only called when a undo create relationship or // a relationship delete is invoked. void removeRelationship( NodeManager nodeManager, RelationshipType type, long relId ) { Collection<Long> relationshipSet = nodeManager.getCowRelationshipRemoveMap( this, type.name(), true ); relationshipSet.add( relId ); } private void ensureRelationshipMapNotNull( NodeManager nodeManager ) { if ( relationships == null ) { loadInitialRelationships( nodeManager ); } } private void loadInitialRelationships( NodeManager nodeManager ) { Triplet<ArrayMap<String, RelIdArray>, Map<Long, RelationshipImpl>, Long> rels = null; synchronized ( this ) { if ( relationships == null ) { this.relChainPosition = nodeManager.getRelationshipChainPosition( this ); ArrayMap<String,RelIdArray> tmpRelMap = new ArrayMap<String,RelIdArray>(); rels = getMoreRelationships( nodeManager, tmpRelMap ); this.relationships = toRelIdArray( tmpRelMap ); if ( rels != null ) { setRelChainPosition( rels.third() ); } } } if ( rels != null ) { nodeManager.putAllInRelCache( rels.second() ); } } private RelIdArray[] toRelIdArray( ArrayMap<String, RelIdArray> tmpRelMap ) { if ( tmpRelMap == null || tmpRelMap.size() == 0 ) { return NO_RELATIONSHIPS; } RelIdArray[] result = new RelIdArray[tmpRelMap.size()]; int i = 0; for ( RelIdArray array : tmpRelMap.values() ) { result[i++] = array; } return result; } private Triplet<ArrayMap<String,RelIdArray>,Map<Long,RelationshipImpl>,Long> getMoreRelationships( NodeManager nodeManager, ArrayMap<String,RelIdArray> tmpRelMap ) { if ( !hasMoreRelationshipsToLoad() ) { return null; } Triplet<ArrayMap<String,RelIdArray>,Map<Long,RelationshipImpl>,Long> rels = nodeManager.getMoreRelationships( this ); ArrayMap<String,RelIdArray> addMap = rels.first(); if ( addMap.size() == 0 ) { return null; } for ( String type : addMap.keySet() ) { RelIdArray addRels = addMap.get( type ); RelIdArray srcRels = tmpRelMap.get( type ); if ( srcRels == null ) { tmpRelMap.put( type, addRels ); } else { RelIdArray newSrcRels = srcRels.addAll( addRels ); // This can happen if srcRels gets upgraded to a RelIdArrayWithLoops if ( newSrcRels != srcRels ) { tmpRelMap.put( type, newSrcRels ); } } } return rels; // nodeManager.putAllInRelCache( pair.other() ); } boolean hasMoreRelationshipsToLoad() { return relChainPosition != Record.NO_NEXT_RELATIONSHIP.intValue(); } boolean getMoreRelationships( NodeManager nodeManager ) { Triplet<ArrayMap<String,RelIdArray>,Map<Long,RelationshipImpl>,Long> rels; if ( !hasMoreRelationshipsToLoad() ) { return false; } synchronized ( this ) { if ( !hasMoreRelationshipsToLoad() ) { return false; } rels = nodeManager.getMoreRelationships( this ); ArrayMap<String,RelIdArray> addMap = rels.first(); if ( addMap.size() == 0 ) { return false; } for ( String type : addMap.keySet() ) { RelIdArray addRels = addMap.get( type ); // IntArray srcRels = tmpRelMap.get( type ); RelIdArray srcRels = getRelIdArray( type ); if ( srcRels == null ) { putRelIdArray( addRels ); } else { RelIdArray newSrcRels = srcRels.addAll( addRels ); // This can happen if srcRels gets upgraded to a RelIdArrayWithLoops if ( newSrcRels != srcRels ) { putRelIdArray( newSrcRels ); } } } setRelChainPosition( rels.third() ); } nodeManager.putAllInRelCache( rels.second() ); return true; } private RelIdArray getRelIdArray( String type ) { // Concurrency-wise it's ok even if the relationships variable // gets rebound to something else (in putRelIdArray) since for-each // stashes the reference away and uses that for ( RelIdArray array : relationships ) { if ( array.getType().equals( type ) ) { return array; } } return null; } private void putRelIdArray( RelIdArray addRels ) { // Try to overwrite it if it's already set // // Make sure the same array is looped all the way through, that's why // a safe reference is kept to it. If the real array has changed when // we're about to set it then redo the loop. A kind of lock-free synchronization String expectedType = addRels.getType(); for ( int i = 0; i < relationships.length; i++ ) { if ( relationships[i].getType().equals( expectedType ) ) { relationships[i] = addRels; return; } } RelIdArray[] newArray = new RelIdArray[relationships.length+1]; System.arraycopy( relationships, 0, newArray, 0, relationships.length ); newArray[relationships.length] = addRels; relationships = newArray; } public Relationship createRelationshipTo( NodeManager nodeManager, Node otherNode, RelationshipType type ) { return nodeManager.createRelationship( this, otherNode, type ); } /* Tentative expansion API public Expansion<Relationship> expandAll() { return Traversal.expanderForAllTypes().expand( this ); } public Expansion<Relationship> expand( RelationshipType type ) { return expand( type, Direction.BOTH ); } public Expansion<Relationship> expand( RelationshipType type, Direction direction ) { return Traversal.expanderForTypes( type, direction ).expand( this ); } public Expansion<Relationship> expand( Direction direction ) { return Traversal.expanderForAllTypes( direction ).expand( this ); } public Expansion<Relationship> expand( RelationshipExpander expander ) { return Traversal.expander( expander ).expand( this ); } */ public Traverser traverse( NodeManager nodeManager, Order traversalOrder, StopEvaluator stopEvaluator, ReturnableEvaluator returnableEvaluator, RelationshipType relationshipType, Direction direction ) { return OldTraverserWrapper.traverse( new NodeProxy( id, nodeManager ), traversalOrder, stopEvaluator, returnableEvaluator, relationshipType, direction ); } public Traverser traverse( NodeManager nodeManager, Order traversalOrder, StopEvaluator stopEvaluator, ReturnableEvaluator returnableEvaluator, RelationshipType firstRelationshipType, Direction firstDirection, RelationshipType secondRelationshipType, Direction secondDirection ) { return OldTraverserWrapper.traverse( new NodeProxy( id, nodeManager ), traversalOrder, stopEvaluator, returnableEvaluator, firstRelationshipType, firstDirection, secondRelationshipType, secondDirection ); } public Traverser traverse( NodeManager nodeManager, Order traversalOrder, StopEvaluator stopEvaluator, ReturnableEvaluator returnableEvaluator, Object... relationshipTypesAndDirections ) { return OldTraverserWrapper.traverse( new NodeProxy( id, nodeManager ), traversalOrder, stopEvaluator, returnableEvaluator, relationshipTypesAndDirections ); } public boolean hasRelationship( NodeManager nodeManager ) { return getRelationships( nodeManager ).iterator().hasNext(); } public boolean hasRelationship( NodeManager nodeManager, RelationshipType... types ) { return getRelationships( nodeManager, types ).iterator().hasNext(); } public boolean hasRelationship( NodeManager nodeManager, Direction direction, RelationshipType... types ) { return getRelationships( nodeManager, direction, types ).iterator().hasNext(); } public boolean hasRelationship( NodeManager nodeManager, Direction dir ) { return getRelationships( nodeManager, dir ).iterator().hasNext(); } public boolean hasRelationship( NodeManager nodeManager, RelationshipType type, Direction dir ) { return getRelationships( nodeManager, type, dir ).iterator().hasNext(); } protected void commitRelationshipMaps( ArrayMap<String,RelIdArray> cowRelationshipAddMap, ArrayMap<String,Collection<Long>> cowRelationshipRemoveMap ) { if ( relationships == null ) { // we will load full in some other tx return; } synchronized ( this ) { if ( cowRelationshipAddMap != null ) { for ( String type : cowRelationshipAddMap.keySet() ) { RelIdArray add = cowRelationshipAddMap.get( type ); Collection<Long> remove = null; if ( cowRelationshipRemoveMap != null ) { remove = cowRelationshipRemoveMap.get( type ); } RelIdArray src = getRelIdArray( type ); putRelIdArray( RelIdArray.from( src, add, remove ) ); } } if ( cowRelationshipRemoveMap != null ) { for ( String type : cowRelationshipRemoveMap.keySet() ) { if ( cowRelationshipAddMap != null && cowRelationshipAddMap.get( type ) != null ) { continue; } RelIdArray src = getRelIdArray( type ); if ( src != null ) { Collection<Long> remove = cowRelationshipRemoveMap.get( type ); putRelIdArray( RelIdArray.from( src, null, remove ) ); } } } } } long getRelChainPosition() { return relChainPosition; } void setRelChainPosition( long position ) { this.relChainPosition = position; if ( !hasMoreRelationshipsToLoad() ) { // Shrink arrays for ( int i = 0; i < relationships.length; i++ ) { relationships[i] = relationships[i].shrink(); } } } RelIdArray getRelationshipIds( String type ) { return getRelIdArray( type ); } RelIdArray[] getRelationshipIds() { return relationships; } }