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 }