package org.neo4j.util;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import javax.transaction.TransactionManager;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.PropertyContainer;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.graphdb.Transaction;
import org.neo4j.kernel.EmbeddedGraphDatabase;
import org.neo4j.kernel.impl.event.Event;
import org.neo4j.kernel.impl.event.EventData;
import org.neo4j.kernel.impl.event.EventListenerAlreadyRegisteredException;
import org.neo4j.kernel.impl.event.EventListenerNotRegisteredException;
import org.neo4j.kernel.impl.event.EventManager;
import org.neo4j.kernel.impl.event.ProActiveEventListener;
import org.neo4j.kernel.impl.event.ReActiveEventListener;
import org.neo4j.kernel.impl.transaction.LockManager;
/**
* Contains some convenience methods for f.ex. set/get/remove one property
* wrapped in a transaction.
*
* Some event helper methods.
*
* Reference nodes helper methods.
*
* EventManager register/unregister methods are a pain in the ass, one throws
* two exceptions and the other throws one. Added some helper methods here.
*
* @author mathew
*
*/
public class GraphDatabaseUtil
{
/**
* The type of event, Neo4j supports pro-active and re-active.
*/
public static enum EventType
{
/**
* A pro-active event, which means that the event reaches its targets
* synchronously.
*/
PRO_ACTIVE,
/**
* A re-active event, which means that the event may reach its targets
* at a later time (a separate thread).
*/
RE_ACTIVE,
}
private GraphDatabaseService graphDb;
/**
* @param graphDb the {@link GraphDatabaseService} to use in methods
* which needs it.
*/
public GraphDatabaseUtil( GraphDatabaseService graphDb )
{
this.graphDb = graphDb;
}
/**
* @return the {@link GraphDatabaseService} from the constructor.
*/
public GraphDatabaseService graphDb()
{
return this.graphDb;
}
private void assertPropertyKeyNotNull( String key )
{
if ( key == null )
{
throw new IllegalArgumentException( "Property key can't be null" );
}
}
/**
* Wraps a single {@link PropertyContainer#hasProperty(String)}
* in a transaction.
* @param container the {@link PropertyContainer}.
* @param key the property key.
* @return the result from {@link PropertyContainer#hasProperty(String)}.
*/
public boolean hasProperty( PropertyContainer container, String key )
{
assertPropertyKeyNotNull( key );
Transaction tx = graphDb().beginTx();
try
{
boolean result = container.hasProperty( key );
tx.success();
return result;
}
finally
{
tx.finish();
}
}
/**
* Wraps a single {@link PropertyContainer#getProperty(String)}
* in a transaction.
* @param container the {@link PropertyContainer}.
* @param key the property key.
* @return the result from {@link PropertyContainer#getProperty(String)}.
*/
public Object getProperty( PropertyContainer container, String key )
{
assertPropertyKeyNotNull( key );
Transaction tx = graphDb().beginTx();
try
{
Object result = container.getProperty( key );
tx.success();
return result;
}
finally
{
tx.finish();
}
}
/**
* Wraps a single {@link PropertyContainer#getProperty(String, Object)}
* in a transaction.
* @param container the {@link PropertyContainer}.
* @param key the property key.
* @param defaultValue the value to return if the property doesn't exist.
* @return the result from
* {@link PropertyContainer#getProperty(String, Object)}.
*/
public Object getProperty( PropertyContainer container,
String key, Object defaultValue )
{
assertPropertyKeyNotNull( key );
Transaction tx = graphDb().beginTx();
try
{
Object result = container.getProperty( key, defaultValue );
tx.success();
return result;
}
finally
{
tx.finish();
}
}
/**
* Wraps a single {@link PropertyContainer#setProperty(String, Object)}
* in a transaction.
* @param container the {@link PropertyContainer}.
* @param key the property key.
* @param value the property value.
*/
public void setProperty( PropertyContainer container,
String key, Object value )
{
assertPropertyKeyNotNull( key );
if ( value == null )
{
throw new IllegalArgumentException( "Value for property '" +
key + "' can't be null" );
}
Transaction tx = graphDb().beginTx();
try
{
container.setProperty( key, value );
tx.success();
}
finally
{
tx.finish();
}
}
public List<Object> getPropertyValues( PropertyContainer container,
String key )
{
Transaction tx = graphDb.beginTx();
try
{
Object value = container.getProperty( key, null );
List<Object> result = value == null ?
new ArrayList<Object>() : propertyValueAsList( value );
tx.success();
return result;
}
finally
{
tx.finish();
}
}
public boolean addValueToArray( PropertyContainer container,
String key, Object value )
{
Transaction tx = graphDb.beginTx();
try
{
Collection<Object> values = getPropertyValues( container, key );
boolean changed = values.contains( value ) ? false :
values.add( value );
if ( changed )
{
container.setProperty( key, asPropertyValue( values ) );
}
tx.success();
return changed;
}
finally
{
tx.finish();
}
}
public boolean removeValueFromArray( PropertyContainer container,
String key, Object value )
{
Transaction tx = graphDb.beginTx();
try
{
Collection<Object> values = getPropertyValues( container, key );
boolean changed = values.remove( value );
if ( changed )
{
if ( values.isEmpty() )
{
container.removeProperty( key );
}
else
{
container.setProperty( key, asPropertyValue( values ) );
}
}
tx.success();
return changed;
}
finally
{
tx.finish();
}
}
/**
* Wraps a single {@link PropertyContainer#removeProperty(String)}
* in a transaction.
* @param container the {@link PropertyContainer}.
* @param key the property key.
* @return the old value of the property or null if the property didn't
* exist
*/
public Object removeProperty( PropertyContainer container, String key )
{
assertPropertyKeyNotNull( key );
Transaction tx = graphDb().beginTx();
try
{
Object oldValue = container.removeProperty( key );
tx.success();
return oldValue;
}
finally
{
tx.finish();
}
}
/**
* Wraps a {@link Node#getSingleRelationship(RelationshipType, Direction)}
* in a transaction.
* @param node the {@link Node}.
* @param type the {@link RelationshipType}
* @param direction the {@link Direction}.
* @return the result from
* {@link Node#getSingleRelationship(RelationshipType, Direction)}.
*/
public Relationship getSingleRelationship( Node node, RelationshipType type,
Direction direction )
{
Transaction tx = graphDb().beginTx();
try
{
Relationship singleRelationship =
node.getSingleRelationship( type, direction );
tx.success();
return singleRelationship;
}
finally
{
tx.finish();
}
}
public Node getSingleOtherNode( Node node, RelationshipType type,
Direction direction )
{
Transaction tx = graphDb().beginTx();
try
{
Relationship rel = getSingleRelationship( node, type, direction );
Node result = rel == null ? null : rel.getOtherNode( node );
tx.success();
return result;
}
finally
{
tx.finish();
}
}
public Relationship getSingleRelationship( Node node,
RelationshipType type )
{
Transaction tx = graphDb().beginTx();
try
{
Iterator<Relationship> itr =
node.getRelationships( type ).iterator();
Relationship rel = null;
if ( itr.hasNext() )
{
rel = itr.next();
if ( itr.hasNext() )
{
throw new RuntimeException( node + " has more than one " +
"relationship of type '" + type.name() + "'" );
}
}
tx.success();
return rel;
}
finally
{
tx.finish();
}
}
public Node getSingleOtherNode( Node node, RelationshipType type )
{
Transaction tx = graphDb().beginTx();
try
{
Relationship rel = getSingleRelationship( node, type );
Node result = rel == null ? null : rel.getOtherNode( node );
tx.success();
return result;
}
finally
{
tx.finish();
}
}
/**
* Wraps a {@link GraphDatabaseService#getReferenceNode()} in a transaction.
* @return the result from {@link GraphDatabaseService#getReferenceNode()}.
*/
public Node getReferenceNode()
{
Transaction tx = graphDb().beginTx();
try
{
Node referenceNode = graphDb().getReferenceNode();
tx.success();
return referenceNode;
}
finally
{
tx.finish();
}
}
/**
* @see #getOrCreateSubReferenceNode(RelationshipType, Direction) .
* @param type the relationship type.
* @return the sub-reference node for {@code type}.
*/
public Node getOrCreateSubReferenceNode( RelationshipType type )
{
return this.getOrCreateSubReferenceNode( type, Direction.OUTGOING );
}
/**
* Tries to get a sub reference node with relationship type
* <code>type</code>. If it doesn't exist, it is created. There can be
* only one of any given type.
*
* [NodeSpaceReferenceNode] -- type --> [SubReferenceNode]
*
* @param type the relationship type.
* @param direction the direction of the relationship.
* @return the sub-reference node.
*/
public Node getOrCreateSubReferenceNode( RelationshipType type,
Direction direction )
{
Transaction tx = graphDb().beginTx();
try
{
Node referenceNode = getReferenceNode();
Node node = null;
Relationship singleRelationship =
referenceNode.getSingleRelationship( type, direction );
if ( singleRelationship != null )
{
node = singleRelationship.getOtherNode( referenceNode );
}
else
{
node = graphDb().createNode();
referenceNode.createRelationshipTo( node, type );
}
tx.success();
return node;
}
finally
{
tx.finish();
}
}
/**
* Returns the sub-reference node for a relationship type as a collection.
* @param <T> the instance class for objects in the result collection.
* @param type the relationship type for the sub-reference node.
* @param clazz the instance class for objects in the result collection.
* @return the sub-reference node for a relationship type as a collection.
*/
public <T extends NodeWrapper> Collection<T>
getSubReferenceNodeCollection( RelationshipType type, Class<T> clazz )
{
return new NodeWrapperRelationshipSet<T>( graphDb(),
getOrCreateSubReferenceNode( type ), type, clazz );
}
public EventManager getEventManager()
{
return ( ( EmbeddedGraphDatabase )
graphDb() ).getConfig().getEventModule().getEventManager();
}
public LockManager getLockManager()
{
return ( ( EmbeddedGraphDatabase ) graphDb() ).getConfig().getLockManager();
}
public TransactionManager getTransactionManager()
{
return ( ( EmbeddedGraphDatabase )
graphDb() ).getConfig().getTxModule().getTxManager();
}
/**
* Convenience method for registering a re-active event listener.
* Instead of throwing declared exceptions it throws runtime exceptions.
* @param listener the listener to register with the event.
* @param event the event to register the listener with.
*/
public void registerReActiveEventListener( ReActiveEventListener listener,
Event event )
{
try
{
getEventManager().registerReActiveEventListener( listener, event );
}
catch ( EventListenerAlreadyRegisteredException e )
{
throw new RuntimeException( e );
}
catch ( EventListenerNotRegisteredException e )
{
throw new RuntimeException( e );
}
}
/**
* Convenience method for unregistering a re-active event listener.
* Instead of throwing declared exceptions it throws runtime exceptions.
* @param listener the listener to unregister from the event.
* @param event the event to unregister the listener from.
*/
public void unregisterReActiveEventListener(
ReActiveEventListener listener, Event event )
{
try
{
getEventManager().unregisterReActiveEventListener(
listener, event );
}
catch ( EventListenerNotRegisteredException e )
{
throw new RuntimeException( e );
}
}
/**
* Convenience method for registering a pro-active event listener.
* Instead of throwing declared exceptions it throws runtime exceptions.
* @param listener the listener to register with the event.
* @param event the event to register the listener with.
*/
public void registerProActiveEventListener(
ProActiveEventListener listener, Event event )
{
try
{
getEventManager().registerProActiveEventListener(
listener, event );
}
catch ( EventListenerAlreadyRegisteredException e )
{
throw new RuntimeException( e );
}
catch ( EventListenerNotRegisteredException e )
{
throw new RuntimeException( e );
}
}
/**
* Convenience method for unregistering a re-active event listener.
* Instead of throwing declared exceptions it throws runtime exceptions.
* @param listener the listener to unregister from the event.
* @param event the event to unregister the listener from.
*/
public void unregisterProActiveEventListener(
ProActiveEventListener listener, Event event )
{
try
{
getEventManager().unregisterProActiveEventListener(
listener, event );
}
catch ( EventListenerNotRegisteredException e )
{
throw new RuntimeException( e );
}
}
/**
* Convenience method for generating a pro-active event.
* @param event the event type.
* @param data the event data to send (it gets wrapped in an
* {@link EventData}).
* @return the result of
* {@link EventManager#generateProActiveEvent(Event, EventData)}.
*/
public boolean proActiveEvent( Event event, Object data )
{
return event( event, data, EventType.PRO_ACTIVE );
}
/**
* Convenience method for generating a re-active event.
* @param event the event type.
* @param data the event data to send (it gets wrapped in an
* {@link EventData}).
*/
public void reActiveEvent( Event event, Object data )
{
event( event, data, EventType.RE_ACTIVE );
}
/**
* Convenience method for generating an event (pro-active, re-active or
* both)
* @param event the event type.
* @param data the event data to send (it gets wrapped in an
* {@link EventData}).
* @param types the types of event to send.
* @return the result of
* {@link EventManager#generateProActiveEvent(Event, EventData)} if any
* of the types is {@link EventType#PRO_ACTIVE}, else {@code true}.
*/
public boolean event( Event event, Object data, EventType... types )
{
boolean result = true;
EventData eventData = new EventData( data );
EventManager eventManager = getEventManager();
for ( EventType type : types )
{
if ( type == EventType.PRO_ACTIVE )
{
result = eventManager.generateProActiveEvent(
event, eventData );
}
else
{
eventManager.generateReActiveEvent( event, eventData );
}
}
return result;
}
public Object[] propertyValueAsArray( Object propertyValue )
{
if ( propertyValue.getClass().isArray() )
{
int length = Array.getLength( propertyValue );
Object[] result = new Object[ length ];
for ( int i = 0; i < length; i++ )
{
result[ i ] = Array.get( propertyValue, i );
}
return result;
}
else
{
return new Object[] { propertyValue };
}
}
public List<Object> propertyValueAsList( Object propertyValue )
{
return new ArrayList<Object>(
Arrays.asList( propertyValueAsArray( propertyValue ) ) );
}
public Object asPropertyValue( Collection<?> values )
{
if ( values.isEmpty() )
{
return null;
}
if ( values.size() == 1 )
{
return values.iterator().next();
}
Object array = Array.newInstance( values.iterator().next().getClass(),
values.size() );
int index = 0;
for ( Object value : values )
{
Array.set( array, index++, value );
}
return array;
}
public Integer incrementAndGetCounter( Node node, String propertyKey )
{
Transaction tx = graphDb.beginTx();
getLockManager().getWriteLock( node );
try
{
int value = ( Integer ) node.getProperty( propertyKey, 0 );
value++;
node.setProperty( propertyKey, value );
tx.success();
return value;
}
finally
{
getLockManager().releaseWriteLock( node );
tx.finish();
}
}
public Integer decrementAndGetCounter( Node node, String propertyKey,
int notLowerThan )
{
Transaction tx = graphDb.beginTx();
getLockManager().getWriteLock( node );
try
{
int value = ( Integer ) node.getProperty( propertyKey, 0 );
value--;
value = value < notLowerThan ? notLowerThan : value;
node.setProperty( propertyKey, value );
tx.success();
return value;
}
finally
{
getLockManager().releaseWriteLock( node );
tx.finish();
}
}
public String sumNodeContents( Node node )
{
StringBuffer result = new StringBuffer();
for ( Relationship rel : node.getRelationships() )
{
if ( rel.getStartNode().equals( node ) )
{
result.append( rel.getStartNode() + " ---[" +
rel.getType().name() + "]--> " + rel.getEndNode() );
}
else
{
result.append( rel.getStartNode() + " <--[" +
rel.getType().name() + "]--- " + rel.getEndNode() );
}
result.append( "\n" );
}
for ( String key : node.getPropertyKeys() )
{
for ( Object value : propertyValueAsArray(
node.getProperty( key ) ) )
{
result.append( "*" + key + "=[" + value + "]" );
result.append( "\n" );
}
}
return result.toString();
}
}