    1   /**
    2    * Licensed to Neo Technology under one or more contributor
    3    * license agreements. See the NOTICE file distributed with
    4    * this work for additional information regarding copyright
    5    * ownership. Neo Technology licenses this file to you under
    6    * the Apache License, Version 2.0 (the "License"); you may
    7    * not use this file except in compliance with the License.
    8    * You may obtain a copy of the License at
    9    *
    10   * http://www.apache.org/licenses/LICENSE-2.0
    11   *
    12   * Unless required by applicable law or agreed to in writing,
    13   * software distributed under the License is distributed on an
    14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    15   * KIND, either express or implied. See the License for the
    16   * specific language governing permissions and limitations
    17   * under the License.
    18   */
    19  package org.neo4j.examples.socnet;
    20
    21  import org.neo4j.graphalgo.GraphAlgoFactory;
    22  import org.neo4j.graphalgo.PathFinder;
    23  import org.neo4j.graphdb.*;
    24  import org.neo4j.graphdb.traversal.*;
    25  import org.neo4j.graphdb.traversal.Traverser;
    26  import org.neo4j.helpers.collection.IterableWrapper;
    27  import org.neo4j.helpers.collection.IteratorUtil;
    28  import org.neo4j.kernel.Traversal;
    29  import org.neo4j.kernel.Uniqueness;
    30
    31  import java.util.*;
    32
    33  import static org.neo4j.examples.socnet.RelTypes.STATUS;
    34  import static org.neo4j.examples.socnet.RelTypes.FRIEND;
    35  import static org.neo4j.examples.socnet.RelTypes.NEXT;
    36
    37  public class Person
    38  {
    39      static final String NAME = "name";
    40
    41      // START SNIPPET: the-node
    42      private final Node underlyingNode;
    43
    44      Person( Node personNode )
    45      {
    46          this.underlyingNode = personNode;
    47      }
    48
    49      protected Node getUnderlyingNode()
    50      {
    51          return underlyingNode;
    52      }
    53
    54      // END SNIPPET: the-node
    55
    56      // START SNIPPET: delegate-to-the-node
    57      public String getName()
    58      {
    59          return (String)underlyingNode.getProperty( NAME );
    60      }
    61
    62      // END SNIPPET: delegate-to-the-node
    63
    64      // START SNIPPET: override
    65      @Override
    66      public int hashCode()
    67      {
    68          return underlyingNode.hashCode();
    69      }
    70
    71      @Override
    72      public boolean equals( Object o )
    73      {
    74          return o instanceof Person &&
    75                  underlyingNode.equals( ( (Person)o ).getUnderlyingNode() );
    76      }
    77
    78      @Override
    79      public String toString()
    80      {
    81          return "Person[" + getName() + "]";
    82      }
    83
    84      // END SNIPPET: override
    85
    86      public void addFriend( Person otherPerson )
    87      {
    88          Transaction tx = underlyingNode.getGraphDatabase().beginTx();
    89          try
    90          {
    91              if ( !this.equals( otherPerson ) )
    92              {
    93                  Relationship friendRel = getFriendRelationshipTo( otherPerson );
    94                  if ( friendRel == null )
    95                  {
    96                      underlyingNode.createRelationshipTo( otherPerson.getUnderlyingNode(), FRIEND );
    97                  }
    98                  tx.success();
    99              }
    100         }
    101         finally
    102         {
    103             tx.finish();
    104         }
    105     }
    106
    107     public int getNrOfFriends()
    108     {
    109         return IteratorUtil.count( getFriends() );
    110     }
    111
    112     public Iterable<Person> getFriends()
    113     {
    114         return getFriendsByDepth( 1 );
    115     }
    116
    117     public void removeFriend( Person otherPerson )
    118     {
    119         Transaction tx = underlyingNode.getGraphDatabase().beginTx();
    120         try
    121         {
    122             if ( !this.equals( otherPerson ) )
    123             {
    124                 Relationship friendRel = getFriendRelationshipTo( otherPerson );
    125                 if ( friendRel != null )
    126                 {
    127                     friendRel.delete();
    128                 }
    129                 tx.success();
    130             }
    131         }
    132         finally
    133         {
    134             tx.finish();
    135         }
    136     }
    137
    138     public Iterable<Person> getFriendsOfFriends()
    139     {
    140         return getFriendsByDepth( 2 );
    141     }
    142
    143     public Iterable<Person> getShortestPathTo( Person otherPerson,
    144                                                int maxDepth )
    145     {
    146         // use graph algo to calculate a shortest path
    147         PathFinder<Path> finder = GraphAlgoFactory.shortestPath(
    148                 Traversal.expanderForTypes( FRIEND, Direction.BOTH ), maxDepth );
    149
    150         Path path = finder.findSinglePath( underlyingNode,
    151                 otherPerson.getUnderlyingNode() );
    152         return createPersonsFromNodes( path );
    153     }
    154
    155     public Iterable<Person> getFriendRecommendation(
    156             int numberOfFriendsToReturn )
    157     {
    158         HashSet<Person> friends = new HashSet<Person>();
    159         IteratorUtil.addToCollection( getFriends(), friends );
    160
    161         HashSet<Person> friendsOfFriends = new HashSet<Person>();
    162         IteratorUtil.addToCollection( getFriendsOfFriends(), friendsOfFriends );
    163
    164         friendsOfFriends.removeAll( friends );
    165
    166         ArrayList<RankedPerson> rankedFriends = new ArrayList<RankedPerson>();
    167         for ( Person friend : friendsOfFriends )
    168         {
    169             int rank = getNumberOfPathsToPerson( friend );
    170             rankedFriends.add( new RankedPerson( friend, rank ) );
    171         }
    172
    173         Collections.sort( rankedFriends, new RankedComparer() );
    174         trimTo( rankedFriends, numberOfFriendsToReturn );
    175
    176         return onlyFriend( rankedFriends );
    177     }
    178
    179     public Iterable<StatusUpdate> getStatus()
    180     {
    181         Relationship firstStatus = underlyingNode.getSingleRelationship(
    182                 STATUS, Direction.OUTGOING );
    183         if ( firstStatus == null )
    184         {
    185             return Collections.emptyList();
    186         }
    187
    188         // START SNIPPET: getStatusTraversal
    189         TraversalDescription traversal = Traversal.description().
    190                 depthFirst().
    191                 relationships( NEXT ).
    192                 filter( Traversal.returnAll() );
    193         // END SNIPPET: getStatusTraversal
    194
    195
    196         return new IterableWrapper<StatusUpdate, Path>(
    197                 traversal.traverse( firstStatus.getEndNode() ) )
    198         {
    199             @Override
    200             protected StatusUpdate underlyingObjectToObject( Path path )
    201             {
    202                 return new StatusUpdate( path.endNode() );
    203             }
    204         };
    205     }
    206
    207     public Iterator<StatusUpdate> friendStatuses()
    208     {
    209         return new FriendsStatusUpdateIterator( this );
    210     }
    211
    212     public void addStatus( String text )
    213     {
    214         Transaction tx = graphDb().beginTx();
    215         try
    216         {
    217             StatusUpdate oldStatus;
    218             if ( getStatus().iterator().hasNext() )
    219             {
    220                 oldStatus = getStatus().iterator().next();
    221             } else
    222             {
    223                 oldStatus = null;
    224             }
    225
    226             Node newStatus = createNewStatusNode( text );
    227
    228             if ( oldStatus != null )
    229             {
    230                 underlyingNode.getSingleRelationship( RelTypes.STATUS, Direction.OUTGOING ).delete();
    231                 newStatus.createRelationshipTo( oldStatus.getUnderlyingNode(), RelTypes.NEXT );
    232             }
    233
    234             underlyingNode.createRelationshipTo( newStatus, RelTypes.STATUS );
    235             tx.success();
    236         }
    237         finally
    238         {
    239             tx.finish();
    240         }
    241     }
    242
    243     private GraphDatabaseService graphDb()
    244     {
    245         return underlyingNode.getGraphDatabase();
    246     }
    247
    248     private Node createNewStatusNode( String text )
    249     {
    250         Node newStatus = graphDb().createNode();
    251         newStatus.setProperty( StatusUpdate.TEXT, text );
    252         newStatus.setProperty( StatusUpdate.DATE, new Date().getTime() );
    253         return newStatus;
    254     }
    255
    256     private final class RankedPerson
    257     {
    258         final Person person;
    259
    260         final int rank;
    261
    262         private RankedPerson( Person person, int rank )
    263         {
    264
    265             this.person = person;
    266             this.rank = rank;
    267         }
    268
    269         public Person getPerson()
    270         {
    271             return person;
    272         }
    273         public int getRank()
    274         {
    275             return rank;
    276         }
    277
    278     }
    279
    280     private class RankedComparer implements Comparator<RankedPerson>
    281     {
    282         public int compare( RankedPerson a, RankedPerson b )
    283         {
    284             return b.getRank() - a.getRank();
    285         }
    286
    287     }
    288
    289     private void trimTo( ArrayList<RankedPerson> rankedFriends,
    290                          int numberOfFriendsToReturn )
    291     {
    292         while ( rankedFriends.size() > numberOfFriendsToReturn )
    293         {
    294             rankedFriends.remove( rankedFriends.size() - 1 );
    295         }
    296     }
    297
    298     private Iterable<Person> onlyFriend( Iterable<RankedPerson> rankedFriends )
    299     {
    300         ArrayList<Person> retVal = new ArrayList<Person>();
    301         for ( RankedPerson person : rankedFriends )
    302         {
    303             retVal.add( person.getPerson() );
    304         }
    305         return retVal;
    306     }
    307
    308     private Relationship getFriendRelationshipTo( Person otherPerson )
    309     {
    310         Node otherNode = otherPerson.getUnderlyingNode();
    311         for ( Relationship rel : underlyingNode.getRelationships( FRIEND ) )
    312         {
    313             if ( rel.getOtherNode( underlyingNode ).equals( otherNode ) )
    314             {
    315                 return rel;
    316             }
    317         }
    318         return null;
    319     }
    320
    321     private Iterable<Person> getFriendsByDepth( int depth )
    322     {
    323         // return all my friends and their friends using new traversal API
    324         TraversalDescription travDesc = Traversal.description()
    325                 .breadthFirst()
    326                 .relationships( FRIEND )
    327                 .uniqueness( Uniqueness.NODE_GLOBAL )
    328                 .prune( Traversal.pruneAfterDepth( depth ) )
    329                 .filter( Traversal.returnAllButStartNode() );
    330
    331         return createPersonsFromPath( travDesc.traverse( underlyingNode ) );
    332     }
    333
    334     private IterableWrapper<Person, Path> createPersonsFromPath(
    335             Traverser iterableToWrap )
    336     {
    337         return new IterableWrapper<Person, Path>( iterableToWrap )
    338         {
    339             @Override
    340             protected Person underlyingObjectToObject( Path path )
    341             {
    342                 return new Person( path.endNode() );
    343             }
    344         };
    345     }
    346
    347     private int getNumberOfPathsToPerson( Person otherPerson )
    348     {
    349         PathFinder<Path> finder = GraphAlgoFactory.allPaths( Traversal.expanderForTypes( FRIEND, Direction.BOTH ), 2 );
    350         Iterable<Path> paths = finder.findAllPaths( getUnderlyingNode(), otherPerson.getUnderlyingNode() );
    351         return IteratorUtil.count( paths );
    352     }
    353
    354     private Iterable<Person> createPersonsFromNodes( final Path path )
    355     {
    356         return new IterableWrapper<Person, Node>( path.nodes() )
    357         {
    358             @Override
    359             protected Person underlyingObjectToObject( Node node )
    360             {
    361                 return new Person( node );
    362             }
    363         };
    364     }
    365
    366 }