12.4. Traversal

12.4.1. The Matrix
12.4.2. User roles
12.4.3. New traversal framework
12.4.4. Social network

12.4.1. The Matrix

This is the first node space we want to traverse into:

Figure 12.2. Matrix node space view


Friends and friends of friends. 

private static Traverser getFriends( final Node person )
{
    return person.traverse( Order.BREADTH_FIRST,
            StopEvaluator.END_OF_GRAPH,
            ReturnableEvaluator.ALL_BUT_START_NODE, RelTypes.KNOWS,
            Direction.OUTGOING );
}

Let’s perform the actual traversal and print the results:

Traverser friendsTraverser = getFriends( neoNode );
int numberOfFriends = 0;
for ( Node friendNode : friendsTraverser )
{
    System.out.println( "At depth "
            + friendsTraverser.currentPosition()
            .depth() + " => "
            + friendNode.getProperty( "name" ) );
}

Who coded the Matrix? 

private static Traverser findHackers( final Node startNode )
{
    return startNode.traverse( Order.BREADTH_FIRST,
            StopEvaluator.END_OF_GRAPH, new ReturnableEvaluator()
    {
        @Override
        public boolean isReturnableNode(
                final TraversalPosition currentPos )
        {
            return !currentPos.isStartNode()
            && currentPos.lastRelationshipTraversed()
            .isType( RelTypes.CODED_BY );
        }
    }, RelTypes.CODED_BY, Direction.OUTGOING, RelTypes.KNOWS,
    Direction.OUTGOING );
}

Print out the result:

Traverser traverser = findHackers( getNeoNode() );
int numberOfHackers = 0;
for ( Node hackerNode : traverser )
{
    System.out.println( "At depth " + traverser.currentPosition()
            .depth() + " => " + hackerNode.getProperty( "name" ) );
}

Full source code: MatrixTest.java

12.4.2. User roles

Here we have users assigned to groups, and groups containing other groups. This is the full node space of our example:

Figure 12.3. User roles node space view


Get the admins. 

Node admins = getGroupByName( "Admins" );
Traverser traverser = admins.traverse(
        Traverser.Order.BREADTH_FIRST, StopEvaluator.END_OF_GRAPH,
        ReturnableEvaluator.ALL_BUT_START_NODE, RoleRels.PART_OF,
        Direction.INCOMING, RoleRels.MEMBER_OF, Direction.INCOMING );
for ( Node part : traverser )
{
    System.out.println( part.getProperty( NAME )
                        + " "
                        + ( traverser.currentPosition().depth() - 1 ) );
}

Get the group memberships of a user. 

Node jale = getUserByName( "Jale" );
Traverser traverser = jale.traverse( Traverser.Order.DEPTH_FIRST,
        StopEvaluator.END_OF_GRAPH,
        ReturnableEvaluator.ALL_BUT_START_NODE, RoleRels.MEMBER_OF,
        Direction.OUTGOING, RoleRels.PART_OF, Direction.OUTGOING );
for ( Node membership : traverser )
{
    System.out.println( membership.getProperty( NAME )
                        + " "
                        + ( traverser.currentPosition().depth() - 1 ) );
}

Get all groups. 

Node referenceNode = graphDb.getReferenceNode();
Traverser traverser = referenceNode.traverse(
        Traverser.Order.BREADTH_FIRST, StopEvaluator.END_OF_GRAPH,
        ReturnableEvaluator.ALL_BUT_START_NODE, RoleRels.ROOT,
        Direction.INCOMING, RoleRels.PART_OF, Direction.INCOMING );
for ( Node group : traverser )
{
    System.out.println( group.getProperty( NAME ) );
}

Get all members of all groups. 

Node referenceNode = graphDb.getReferenceNode();
Traverser traverser = referenceNode.traverse(
        Traverser.Order.BREADTH_FIRST, StopEvaluator.END_OF_GRAPH,
        new ReturnableEvaluator()
        {
            @Override
            public boolean isReturnableNode(
                    TraversalPosition currentPos )
            {
                if ( currentPos.isStartNode() )
                {
                    return false;
                }
                Relationship rel = currentPos.lastRelationshipTraversed();
                return rel.isType( RoleRels.MEMBER_OF );
            }
        }, RoleRels.ROOT,
        Direction.INCOMING, RoleRels.PART_OF, Direction.INCOMING,
        RoleRels.MEMBER_OF, Direction.INCOMING );
for ( Node group : traverser )
{
    System.out.println( group.getProperty( NAME ) );
}

Full source code: RolesTest.java

12.4.3. New traversal framework

[Note]Note

The following examples use a new experimental traversal API. It shares the underlying implementation with the old traversal API, so performance-wise they should be equal. However, expect the new API to evolve and thus undergo changes.

The Matrix

The traversals from the Matrix example above, this time using the new traversal API:

Friends and friends of friends. 

private static Traverser getFriends( final Node person )
{
    TraversalDescription td = Traversal.description()
            .breadthFirst()
            .relationships( RelTypes.KNOWS, Direction.OUTGOING )
            .evaluator( Evaluators.excludeStartPosition() );
    return td.traverse( person );
}

Let’s perform the actual traversal and print the results:

Traverser friendsTraverser = getFriends( neoNode );
int numberOfFriends = 0;
for ( Path friendPath : friendsTraverser )
{
    System.out.println( "At depth " + friendPath.length() + " => "
                        + friendPath.endNode()
                                .getProperty( "name" ) );
}

Who coded the Matrix? 

private static Traverser findHackers( final Node startNode )
{
    TraversalDescription td = Traversal.description()
            .breadthFirst()
            .relationships( RelTypes.CODED_BY, Direction.OUTGOING )
            .relationships( RelTypes.KNOWS, Direction.OUTGOING )
            .evaluator(
                    Evaluators.returnWhereLastRelationshipTypeIs( RelTypes.CODED_BY ) );
    return td.traverse( startNode );
}

Print out the result:

Traverser traverser = findHackers( getNeoNode() );
int numberOfHackers = 0;
for ( Path hackerPath : traverser )
{
    System.out.println( "At depth " + hackerPath.length() + " => "
                        + hackerPath.endNode()
                                .getProperty( "name" ) );
}

Full source code: MatrixNewTravTest.java

User roles

The traversals from the User roles example above, this time using the new traversal API:

Get the admins. 

Node admins = getGroupByName( "Admins" );
TraversalDescription td = Traversal.description()
        .breadthFirst()
        .relationships( RoleRels.PART_OF, Direction.INCOMING )
        .relationships( RoleRels.MEMBER_OF, Direction.INCOMING )
        .evaluator( Evaluators.excludeStartPosition() );
for ( Path path : td.traverse( admins ) )
{
    Node part = path.endNode();
    System.out.println( part.getProperty( NAME ) + " "
                        + ( path.length() - 1 ) );
}

Get the group memberships of a user. 

Node jale = getUserByName( "Jale" );
TraversalDescription td = Traversal.description()
        .depthFirst()
        .relationships( RoleRels.MEMBER_OF, Direction.OUTGOING )
        .relationships( RoleRels.PART_OF, Direction.OUTGOING )
        .evaluator( Evaluators.excludeStartPosition() );
for ( Path path : td.traverse( jale ) )
{
    Node membership = path.endNode();
    System.out.println( membership.getProperty( NAME ) + " "
                        + ( path.length() - 1 ) );
}

Get all groups. 

Node referenceNode = graphDb.getReferenceNode();
TraversalDescription td = Traversal.description()
        .breadthFirst()
        .relationships( RoleRels.ROOT, Direction.INCOMING )
        .relationships( RoleRels.PART_OF, Direction.INCOMING )
        .evaluator( Evaluators.excludeStartPosition() );
for ( Node group : td.traverse( referenceNode )
        .nodes() )
{
    System.out.println( group.getProperty( NAME ) );
}

Get all members of all groups. 

Node referenceNode = graphDb.getReferenceNode();
TraversalDescription td = Traversal.description()
        .breadthFirst()
        .relationships( RoleRels.ROOT, Direction.INCOMING )
        .relationships( RoleRels.MEMBER_OF, Direction.INCOMING )
        .relationships( RoleRels.PART_OF, Direction.INCOMING )
        .evaluator(
                Evaluators.pruneWhereLastRelationshipTypeIs( RoleRels.MEMBER_OF ) );
for ( Node group : td.traverse( referenceNode ).nodes() )
{
    System.out.println( group.getProperty( NAME ) );
}

Full source code: RolesNewTravTest.java

Walking an ordered path

This example shows how to use a path context holding a representation of a path.

Create a toy graph. 

Node A = db.createNode();
Node B = db.createNode();
Node C = db.createNode();
Node D = db.createNode();
A.createRelationshipTo( B, REL1 );
B.createRelationshipTo( C, REL2 );
C.createRelationshipTo( D, REL3 );
A.createRelationshipTo( C, REL2 );

Now, the order of relationships (REL1REL2REL3) is stored in an ArrayList. Upon traversal, the Evaluator can check against it to ensure that only paths are included and returned that have the predefined order of relationships:

Walk the path. 

final ArrayList<RelationshipType> orderedPathContext = new ArrayList<RelationshipType>();
orderedPathContext.add( REL1 );
orderedPathContext.add( withName( "REL2" ) );
orderedPathContext.add( withName( "REL3" ) );
TraversalDescription td = Traversal.description().evaluator(
        new Evaluator()
        {
            @Override
            public Evaluation evaluate( final Path path )
            {
                if ( path.length() == 0 )
                {
                    return Evaluation.EXCLUDE_AND_CONTINUE;
                }
                String currentName = path.lastRelationship().getType().name();
                String relationshipType = orderedPathContext.get(
                        path.length() - 1 ).name();
                if ( path.length() == orderedPathContext.size() )
                {
                    if ( currentName.equals( relationshipType ) )
                    {
                        return Evaluation.INCLUDE_AND_PRUNE;
                    }
                    else
                    {
                        return Evaluation.EXCLUDE_AND_PRUNE;
                    }
                }
                else
                {
                    if ( currentName.equals( relationshipType ) )
                    {
                        return Evaluation.EXCLUDE_AND_CONTINUE;
                    }
                    else
                    {
                        return Evaluation.EXCLUDE_AND_PRUNE;
                    }
                }
            }
        } );
Traverser t = td.traverse( db.getNodeById( 1 ) );
for ( Path path : t )
{
    System.out.println( path );
}

Full source code: OrderedPathTest.java

12.4.4. Social network

[Note]Note

The following example uses the new experimental traversal API.

Social networks (know as social graphs out on the web) are natural to model with a graph. This example shows a very simple social model that connects friends and keeps track of status updates.

Simple social model

Figure 12.4. Social network data model


The data model for a social network is pretty simple: Persons with names and StatusUpdates with timestamped text. These entities are then connected by specific relationships.

  • Person

    • friend: relates two distinct Person instances (no self-reference)
    • status: connects to the most recent StatusUpdate
  • StatusUpdate

    • next: points to the next StatusUpdate in the chain, which was posted before the current one

Status graph instance

The StatusUpdate list for a Person is a linked list. The head of the list (the most recent status) is found by following status. Each subsequent StatusUpdate is connected by next.

Here’s an example where Andreas Kollegger micro-blogged his way to work in the morning:

To read the status updates, we can create a traversal, like so:

TraversalDescription traversal = Traversal.description().
        depthFirst().
        relationships( NEXT ).
        filter( Traversal.returnAll() );

This gives us a traverser that will start at one StatusUpdate, and will follow the chain of updates until they run out. Traversers are lazy loading, so it’s performant even when dealing with thousands of statuses - they are not loaded until we actually consume them.

Activity stream

Once we have friends, and they have status messages, we might want to read our friends status' messages, in reverse time order - latest first. To do this, we go through these steps:

  1. Gather all friend’s status update iterators in a list - latest date first.
  2. Sort the list.
  3. Return the first item in the list.
  4. If the first iterator is exhausted, remove it from the list. Otherwise, get the next item in that iterator.
  5. Go to step 2 until there are no iterators left in the list.

Animated, the sequence looks like this.

The code looks like:

PositionedIterator<StatusUpdate> first = statuses.get(0);
StatusUpdate returnVal = first.current();

if ( !first.hasNext() )
{
    statuses.remove( 0 );
}
else
{
    first.next();
    sort();
}

return returnVal;

Full source code: socnet