/*
* Copyright (c) 2002-2009 "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.kernel.impl.core;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.Map.Entry;
import java.util.logging.Logger;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import org.neo4j.graphdb.NotInTransactionException;
import org.neo4j.graphdb.TransactionFailureException;
import org.neo4j.graphdb.event.TransactionData;
import org.neo4j.kernel.impl.nioneo.store.PropertyData;
import org.neo4j.kernel.impl.nioneo.store.PropertyIndexData;
import org.neo4j.kernel.impl.nioneo.store.RelationshipTypeData;
import org.neo4j.kernel.impl.transaction.LockManager;
import org.neo4j.kernel.impl.transaction.LockType;
import org.neo4j.kernel.impl.util.ArrayMap;
import org.neo4j.kernel.impl.util.IntArray;
/**
* Manages object version diffs and locks for each transaction.
*/
public class LockReleaser
{
private static Logger log = Logger.getLogger( LockReleaser.class.getName() );
private final ArrayMap<Transaction,List<LockElement>> lockMap =
new ArrayMap<Transaction,List<LockElement>>( 5, true, true );
private final ArrayMap<Transaction,PrimitiveElement> cowMap =
new ArrayMap<Transaction,PrimitiveElement>( 5, true, true );
private NodeManager nodeManager;
private final LockManager lockManager;
private final TransactionManager transactionManager;
private PropertyIndexManager propertyIndexManager;
private static class PrimitiveElement
{
PrimitiveElement()
{
}
final ArrayMap<Integer,CowNodeElement> nodes =
new ArrayMap<Integer,CowNodeElement>();
final ArrayMap<Integer,CowRelElement> relationships =
new ArrayMap<Integer,CowRelElement>();
}
private static class CowNodeElement
{
CowNodeElement()
{
}
boolean deleted = false;
ArrayMap<String,IntArray> relationshipAddMap = null;
ArrayMap<String,IntArray> relationshipRemoveMap = null;
ArrayMap<Integer,PropertyData> propertyAddMap = null;
ArrayMap<Integer,PropertyData> propertyRemoveMap = null;
}
private static class CowRelElement
{
CowRelElement()
{
}
boolean deleted = false;
ArrayMap<Integer,PropertyData> propertyAddMap = null;
ArrayMap<Integer,PropertyData> propertyRemoveMap = null;
}
public LockReleaser( LockManager lockManager,
TransactionManager transactionManager )
{
this.lockManager = lockManager;
this.transactionManager = transactionManager;
}
void setNodeManager( NodeManager nodeManager )
{
this.nodeManager = nodeManager;
}
void setPropertyIndexManager( PropertyIndexManager propertyIndexManager )
{
this.propertyIndexManager = propertyIndexManager;
}
private static class LockElement
{
Object resource;
LockType lockType;
LockElement( Object resource, LockType type )
{
this.resource = resource;
this.lockType = type;
}
}
/**
* Invoking this method with no transaction running will cause the lock to
* be released right away.
*
* @param resource
* the resource on which the lock is taken
* @param type
* type of lock (READ or WRITE)
* @throws NotInTransactionException
*/
public void addLockToTransaction( Object resource, LockType type )
throws NotInTransactionException
{
Transaction tx = getTransaction();
List<LockElement> lockElements = lockMap.get( tx );
if ( lockElements != null )
{
lockElements.add( new LockElement( resource, type ) );
}
else
{
if ( tx == null )
{
// no transaction we release lock right away
if ( type == LockType.WRITE )
{
lockManager.releaseWriteLock( resource );
}
else if ( type == LockType.READ )
{
lockManager.releaseReadLock( resource );
}
return;
}
lockElements = new ArrayList<LockElement>();
lockMap.put( tx, lockElements );
lockElements.add( new LockElement( resource, type ) );
// we have to have a syncrhonization hook for read only transaction,
// write locks can be taken in read only transactions (ex:
// transactions that peform write operations that cancel each other
// out). This sync hook will only release locks if they exist and
// tx was read only
try
{
tx.registerSynchronization( new ReadOnlyTxReleaser( tx ) );
}
catch ( Exception e )
{
throw new TransactionFailureException(
"Failed to register lock release synchronization hook", e );
}
}
}
private Transaction getTransaction()
{
try
{
return transactionManager.getTransaction();
}
catch ( SystemException e )
{
throw new TransactionFailureException(
"Failed to get current transaction.", e );
}
}
public IntArray getCowRelationshipRemoveMap( NodeImpl node, String type )
{
PrimitiveElement primitiveElement = cowMap.get( getTransaction() );
if ( primitiveElement != null )
{
ArrayMap<Integer,CowNodeElement> cowElements =
primitiveElement.nodes;
CowNodeElement element = cowElements.get( node.id );
if ( element != null && element.relationshipRemoveMap != null )
{
return element.relationshipRemoveMap.get( type );
}
}
return null;
}
public IntArray getCowRelationshipRemoveMap( NodeImpl node, String type,
boolean create )
{
if ( !create )
{
return getCowRelationshipRemoveMap( node, type );
}
PrimitiveElement primitiveElement = getAndSetupPrimitiveElement();
ArrayMap<Integer,CowNodeElement> cowElements =
primitiveElement.nodes;
CowNodeElement element = cowElements.get( node.id );
if ( element == null )
{
element = new CowNodeElement();
cowElements.put( node.id, element );
}
if ( element.relationshipRemoveMap == null )
{
element.relationshipRemoveMap = new ArrayMap<String,IntArray>();
}
IntArray set = element.relationshipRemoveMap.get( type );
if ( set == null )
{
set = new IntArray();
element.relationshipRemoveMap.put( type, set );
}
return set;
}
public ArrayMap<String,IntArray> getCowRelationshipAddMap( NodeImpl node )
{
PrimitiveElement primitiveElement = cowMap.get( getTransaction() );
if ( primitiveElement != null )
{
ArrayMap<Integer,CowNodeElement> cowElements =
primitiveElement.nodes;
CowNodeElement element = cowElements.get( node.id );
if ( element != null )
{
return element.relationshipAddMap;
}
}
return null;
}
public IntArray getCowRelationshipAddMap( NodeImpl node, String type )
{
PrimitiveElement primitiveElement = cowMap.get( getTransaction() );
if ( primitiveElement != null )
{
ArrayMap<Integer,CowNodeElement> cowElements =
primitiveElement.nodes;
CowNodeElement element = cowElements.get( node.id );
if ( element != null && element.relationshipAddMap != null )
{
return element.relationshipAddMap.get( type );
}
}
return null;
}
public IntArray getCowRelationshipAddMap( NodeImpl node, String type,
boolean create )
{
if ( !create )
{
return getCowRelationshipRemoveMap( node, type );
}
PrimitiveElement primitiveElement = getAndSetupPrimitiveElement();
ArrayMap<Integer,CowNodeElement> cowElements =
primitiveElement.nodes;
CowNodeElement element = cowElements.get( node.id );
if ( element == null )
{
element = new CowNodeElement();
cowElements.put( node.id, element );
}
if ( element.relationshipAddMap == null )
{
element.relationshipAddMap = new ArrayMap<String,IntArray>();
}
IntArray set = element.relationshipAddMap.get( type );
if ( set == null )
{
set = new IntArray();
element.relationshipAddMap.put( type, set );
}
return set;
}
public void commit()
{
Transaction tx = getTransaction();
// propertyIndex
propertyIndexManager.commit( tx );
releaseCows( tx, Status.STATUS_COMMITTED );
releaseLocks( tx );
}
public void rollback()
{
Transaction tx = getTransaction();
// propertyIndex
propertyIndexManager.rollback( tx );
releaseCows( tx, Status.STATUS_ROLLEDBACK );
releaseLocks( tx );
}
void releaseLocks( Transaction tx )
{
List<LockElement> lockElements = lockMap.remove( tx );
if ( lockElements != null )
{
for ( LockElement lockElement : lockElements )
{
try
{
if ( lockElement.lockType == LockType.READ )
{
lockManager.releaseReadLock( lockElement.resource );
}
else if ( lockElement.lockType == LockType.WRITE )
{
lockManager.releaseWriteLock( lockElement.resource );
}
}
catch ( Exception e )
{
e.printStackTrace();
log.severe( "Unable to release lock[" +
lockElement.lockType + "] on resource[" +
lockElement.resource + "]" );
}
}
}
}
void releaseCows( Transaction cowTxId, int param )
{
PrimitiveElement element = cowMap.remove( cowTxId );
if ( element == null )
{
return;
}
ArrayMap<Integer,CowNodeElement> cowNodeElements = element.nodes;
Set<Entry<Integer,CowNodeElement>> nodeEntrySet =
cowNodeElements.entrySet();
for ( Entry<Integer,CowNodeElement> entry : nodeEntrySet )
{
NodeImpl node = nodeManager.getNodeIfCached( entry.getKey() );
if ( node != null )
{
CowNodeElement nodeElement = entry.getValue();
if ( param == Status.STATUS_COMMITTED )
{
node.commitRelationshipMaps( nodeElement.relationshipAddMap,
nodeElement.relationshipRemoveMap );
node.commitPropertyMaps( nodeElement.propertyAddMap,
nodeElement.propertyRemoveMap );
}
else if ( param != Status.STATUS_ROLLEDBACK )
{
throw new TransactionFailureException(
"Unknown transaction status: " + param );
}
}
}
ArrayMap<Integer,CowRelElement> cowRelElements = element.relationships;
Set<Entry<Integer,CowRelElement>> relEntrySet =
cowRelElements.entrySet();
for ( Entry<Integer,CowRelElement> entry : relEntrySet )
{
RelationshipImpl rel = nodeManager.getRelIfCached( entry.getKey() );
if ( rel != null )
{
CowRelElement relElement = entry.getValue();
if ( param == Status.STATUS_COMMITTED )
{
rel.commitPropertyMaps( relElement.propertyAddMap,
relElement.propertyRemoveMap );
}
else if ( param != Status.STATUS_ROLLEDBACK )
{
throw new TransactionFailureException(
"Unknown transaction status: " + param );
}
}
}
cowMap.remove( cowTxId );
}
public synchronized void dumpLocks()
{
System.out.print( "Locks held: " );
java.util.Iterator<?> itr = lockMap.keySet().iterator();
if ( !itr.hasNext() )
{
System.out.println( "NONE" );
}
else
{
System.out.println();
}
while ( itr.hasNext() )
{
Transaction transaction = (Transaction) itr.next();
System.out.println( "" + transaction + "->" +
lockMap.get( transaction ).size() );
}
}
public ArrayMap<Integer,PropertyData> getCowPropertyRemoveMap(
Primitive primitive )
{
PrimitiveElement primitiveElement = cowMap.get( getTransaction() );
if ( primitiveElement != null && primitive instanceof NodeImpl )
{
ArrayMap<Integer,CowNodeElement> cowElements =
primitiveElement.nodes;
CowNodeElement element = cowElements.get( primitive.id );
if ( element != null )
{
if ( element.deleted )
{
throw new IllegalStateException( "Node[" +
primitive.id + "] has been deleted in this tx" );
}
return element.propertyRemoveMap;
}
}
else if ( primitiveElement != null &&
primitive instanceof RelationshipImpl )
{
ArrayMap<Integer,CowRelElement> cowElements =
primitiveElement.relationships;
CowRelElement element = cowElements.get( primitive.id );
if ( element != null )
{
if ( element.deleted )
{
throw new IllegalStateException( "Relationship[" +
primitive.id + "] has been deleted in this tx" );
}
return element.propertyRemoveMap;
}
}
return null;
}
public ArrayMap<Integer,PropertyData> getCowPropertyAddMap(
Primitive primitive )
{
PrimitiveElement primitiveElement = cowMap.get( getTransaction() );
if ( primitiveElement != null && primitive instanceof NodeImpl )
{
ArrayMap<Integer,CowNodeElement> cowElements =
primitiveElement.nodes;
CowNodeElement element = cowElements.get( primitive.id );
if ( element != null )
{
if ( element.deleted )
{
throw new IllegalStateException( "Node[" +
primitive.id + "] has been deleted in this tx" );
}
return element.propertyAddMap;
}
}
else if ( primitiveElement != null &&
primitive instanceof RelationshipImpl )
{
ArrayMap<Integer,CowRelElement> cowElements =
primitiveElement.relationships;
CowRelElement element = cowElements.get( primitive.id );
if ( element != null )
{
if ( element.deleted )
{
throw new IllegalStateException( "Relationship[" +
primitive.id + "] has been deleted in this tx" );
}
return element.propertyAddMap;
}
}
return null;
}
private PrimitiveElement getAndSetupPrimitiveElement()
{
Transaction tx = getTransaction();
if ( tx == null )
{
throw new NotInTransactionException();
}
PrimitiveElement primitiveElement = cowMap.get( tx );
if ( primitiveElement == null )
{
primitiveElement = new PrimitiveElement();
cowMap.put( tx, primitiveElement );
}
return primitiveElement;
}
public ArrayMap<Integer,PropertyData> getCowPropertyAddMap(
Primitive primitive, boolean create )
{
if ( !create )
{
return getCowPropertyAddMap( primitive );
}
PrimitiveElement primitiveElement = getAndSetupPrimitiveElement();
if ( primitive instanceof NodeImpl )
{
ArrayMap<Integer,CowNodeElement> cowElements =
primitiveElement.nodes;
CowNodeElement element = cowElements.get( primitive.id );
if ( element != null && element.deleted )
{
throw new IllegalStateException( "Node[" +
primitive.id + "] has been deleted in this tx" );
}
if ( element == null )
{
element = new CowNodeElement();
cowElements.put( primitive.id, element );
}
if ( element.propertyAddMap == null )
{
element.propertyAddMap = new ArrayMap<Integer,PropertyData>();
}
return element.propertyAddMap;
}
else if ( primitive instanceof RelationshipImpl )
{
ArrayMap<Integer,CowRelElement> cowElements =
primitiveElement.relationships;
CowRelElement element = cowElements.get( primitive.id );
if ( element != null && element.deleted )
{
throw new IllegalStateException( "Relationship[" +
primitive.id + "] has been deleted in this tx" );
}
if ( element == null )
{
element = new CowRelElement();
cowElements.put( primitive.id, element );
}
if ( element.propertyAddMap == null )
{
element.propertyAddMap = new ArrayMap<Integer,PropertyData>();
}
return element.propertyAddMap;
}
return null;
}
public ArrayMap<Integer,PropertyData> getCowPropertyRemoveMap(
Primitive primitive, boolean create )
{
if ( !create )
{
return getCowPropertyRemoveMap( primitive );
}
PrimitiveElement primitiveElement = getAndSetupPrimitiveElement();
if ( primitive instanceof NodeImpl )
{
ArrayMap<Integer,CowNodeElement> cowElements =
primitiveElement.nodes;
CowNodeElement element = cowElements.get( primitive.id );
if ( element != null && element.deleted )
{
throw new IllegalStateException( "Node[" +
primitive.id + "] has been deleted in this tx" );
}
if ( element == null )
{
element = new CowNodeElement();
cowElements.put( primitive.id, element );
}
if ( element.propertyRemoveMap == null )
{
element.propertyRemoveMap = new ArrayMap<Integer,PropertyData>();
}
return element.propertyRemoveMap;
}
else if ( primitive instanceof RelationshipImpl )
{
ArrayMap<Integer,CowRelElement> cowElements =
primitiveElement.relationships;
CowRelElement element = cowElements.get( primitive.id );
if ( element != null && element.deleted )
{
throw new IllegalStateException( "Relationship[" +
primitive.id + "] has been deleted in this tx" );
}
if ( element == null )
{
element = new CowRelElement();
cowElements.put( primitive.id, element );
}
if ( element.propertyRemoveMap == null )
{
element.propertyRemoveMap = new ArrayMap<Integer,PropertyData>();
}
return element.propertyRemoveMap;
}
return null;
}
public void deletePrimitive( Primitive primitive )
{
PrimitiveElement primitiveElement = getAndSetupPrimitiveElement();
if ( primitive instanceof NodeImpl )
{
ArrayMap<Integer,CowNodeElement> cowElements =
primitiveElement.nodes;
CowNodeElement element = cowElements.get( primitive.id );
if ( element != null && element.deleted )
{
throw new IllegalStateException( "Node[" +
primitive.id + "] has already been deleted in this tx" );
}
if ( element == null )
{
element = new CowNodeElement();
cowElements.put( primitive.id, element );
}
element.deleted = true;
}
else if ( primitive instanceof RelationshipImpl )
{
ArrayMap<Integer,CowRelElement> cowElements =
primitiveElement.relationships;
CowRelElement element = cowElements.get( primitive.id );
if ( element != null && element.deleted )
{
throw new IllegalStateException( "Relationship[" +
primitive.id + "] has already been deleted in this tx" );
}
if ( element == null )
{
element = new CowRelElement();
cowElements.put( primitive.id, element );
}
element.deleted = true;
}
}
public void removeNodeFromCache( int nodeId )
{
if ( nodeManager != null )
{
nodeManager.removeNodeFromCache( nodeId );
}
}
public void addRelationshipType( RelationshipTypeData type )
{
if ( nodeManager != null )
{
nodeManager.addRelationshipType( type );
}
}
public void addPropertyIndex( PropertyIndexData index )
{
if ( nodeManager != null )
{
nodeManager.addPropertyIndex( index );
}
}
public void removeRelationshipFromCache( int id )
{
if ( nodeManager != null )
{
nodeManager.removeRelationshipFromCache( id );
}
}
public void removeRelationshipTypeFromCache( int id )
{
if ( nodeManager != null )
{
nodeManager.removeRelationshipTypeFromCache( id );
}
}
private class ReadOnlyTxReleaser implements Synchronization
{
private final Transaction tx;
ReadOnlyTxReleaser( Transaction tx )
{
this.tx = tx;
}
public void afterCompletion( int status )
{
releaseLocks( tx );
}
public void beforeCompletion()
{
}
}
public void clearCache()
{
if ( nodeManager != null )
{
nodeManager.clearCache();
}
}
public TransactionData getTransactionData()
{
TransactionDataImpl result = new TransactionDataImpl();
PrimitiveElement element = cowMap.get( getTransaction() );
populateCreatedNodes( element, result );
if ( element == null )
{
return result;
}
if ( element.nodes != null )
{
populateNodeRelEvent( element, result );
}
if ( element.relationships != null )
{
populateRelationshipPropertyEvents( element, result );
}
return result;
}
private void populateRelationshipPropertyEvents( PrimitiveElement element,
TransactionDataImpl result )
{
for ( int relId : element.relationships.keySet() )
{
CowRelElement relElement = element.relationships.get( relId );
RelationshipProxy rel = new RelationshipProxy( relId, nodeManager );
RelationshipImpl relImpl = nodeManager.getRelForProxy( relId );
if ( relElement.deleted )
{
if ( nodeManager.relCreated( relId ) )
{
continue;
}
// note: this is done in node populate data
// result.deleted( rel );
}
if ( relElement.propertyAddMap != null && !relElement.deleted )
{
for ( PropertyData data : relElement.propertyAddMap.values() )
{
String key = nodeManager.getKeyForProperty( data.getId() );
Object oldValue = relImpl.getCommittedPropertyValue( key );
Object newValue = data.getValue();
result.assignedProperty( rel, key, newValue, oldValue );
}
}
if ( relElement.propertyRemoveMap != null )
{
for ( PropertyData data : relElement.propertyRemoveMap.values() )
{
String key = nodeManager.getKeyForProperty( data.getId() );
Object oldValue = data.getValue();
if ( oldValue != null && !relElement.deleted )
{
relImpl.getCommittedPropertyValue( key );
}
result.removedProperty( rel, key, oldValue );
}
}
}
}
private void populateNodeRelEvent( PrimitiveElement element,
TransactionDataImpl result )
{
for ( int nodeId : element.nodes.keySet() )
{
CowNodeElement nodeElement = element.nodes.get( nodeId );
NodeProxy node = new NodeProxy( nodeId, nodeManager );
NodeImpl nodeImpl = nodeManager.getNodeForProxy( nodeId );
if ( nodeElement.deleted )
{
if ( nodeManager.nodeCreated( nodeId ) )
{
continue;
}
result.deleted( node );
}
if ( nodeElement.relationshipAddMap != null && !nodeElement.deleted )
{
for ( String type : nodeElement.relationshipAddMap.keySet() )
{
IntArray createdRels =
nodeElement.relationshipAddMap.get( type );
for ( int i = 0; i < createdRels.length(); i++ )
{
int relId = createdRels.get( i );
CowRelElement relElement =
element.relationships.get( relId );
if ( relElement != null && relElement.deleted )
{
continue;
}
RelationshipProxy rel = new RelationshipProxy( relId, nodeManager );
if ( rel.getStartNode().getId() == nodeId )
{
result.created( new RelationshipProxy( relId, nodeManager ) );
}
}
}
}
if ( nodeElement.relationshipRemoveMap != null )
{
for ( String type : nodeElement.relationshipRemoveMap.keySet() )
{
IntArray deletedRels =
nodeElement.relationshipRemoveMap.get( type );
for ( int i = 0; i < deletedRels.length(); i++ )
{
int relId = deletedRels.get( i );
if ( nodeManager.relCreated( relId ) )
{
continue;
}
RelationshipProxy rel = new RelationshipProxy( relId, nodeManager );
if ( rel.getStartNode().getId() == nodeId )
{
result.deleted( new RelationshipProxy( relId, nodeManager ) );
}
}
}
}
if ( nodeElement.propertyAddMap != null && !nodeElement.deleted )
{
for ( PropertyData data : nodeElement.propertyAddMap.values() )
{
String key = nodeManager.getKeyForProperty( data.getId() );
Object oldValue = nodeImpl.getCommittedPropertyValue( key );
Object newValue = data.getValue();
result.assignedProperty( node, key, newValue, oldValue );
}
}
if ( nodeElement.propertyRemoveMap != null )
{
for ( PropertyData data : nodeElement.propertyRemoveMap.values() )
{
String key = nodeManager.getKeyForProperty( data.getId() );
Object oldValue = data.getValue();
if ( oldValue == null && !nodeElement.deleted )
{
nodeImpl.getCommittedPropertyValue( key );
}
result.removedProperty( node, key, oldValue );
}
}
}
}
private void populateCreatedNodes( PrimitiveElement element,
TransactionDataImpl result )
{
IntArray createdNodes = nodeManager.getCreatedNodes();
for ( int i = 0; i < createdNodes.length(); i++ )
{
int nodeId = createdNodes.get( i );
if ( element != null && element.nodes != null )
{
CowNodeElement nodeElement = element.nodes.get( nodeId );
if ( nodeElement != null && nodeElement.deleted )
{
continue;
}
}
result.created( new NodeProxy( nodeId, nodeManager ) );
}
}
}