├── README.md ├── cypher_query_timing.py ├── java ├── pom.xml └── src │ └── main │ └── java │ ├── META-INF │ └── services │ │ └── org.neo4j.server.plugins.ServerPlugin │ └── it │ └── isi │ └── neo4j │ └── dynanets │ ├── BaseTimeline.java │ ├── StructuredTimeline.java │ └── StructuredTimelinePlugin.java └── load_gexf_to_neo4j.py /README.md: -------------------------------------------------------------------------------- 1 | neo4j-dynanets 2 | ============== 3 | 4 | Representing and querying dynamic graphs in Neo4j 5 | -------------------------------------------------------------------------------- /cypher_query_timing.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Perform a number of Cypher queries and compute median execution times 4 | 5 | # Copyright (C) 2013 ISI Foundation 6 | # written by Ciro Cattuto 7 | # and Andre' Panisson 8 | # 9 | # This program is free software: you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License as published by 11 | # the Free Software Foundation, either version 3 of the License, or 12 | # (at your option) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License 20 | # along with this program. If not, see . 21 | # 22 | 23 | import sys, time 24 | from neo4jrestclient import client 25 | 26 | NEO4J_URL = sys.argv[1] 27 | RUN_NAME = sys.argv[2] 28 | 29 | gdb = client.GraphDatabase(NEO4J_URL) 30 | 31 | # get the IDs of a few fixed nodes to be used for test queries below 32 | 33 | ret = gdb.query(q="""START root=node(0) MATCH root-[:HAS_RUN]->run WHERE run.name = "%s" RETURN run""" % RUN_NAME, returns=client.Node)[0] 34 | RUN_ID = ret[0]._get_id() 35 | 36 | ret = gdb.query(q="""START run=node(%d) MATCH run-[:RUN_FRAME]->frame WHERE frame.frame_id = %d RETURN frame""" % (RUN_ID, 8084), returns=client.Node)[0] 37 | FRAME_ID = ret[0]._get_id() 38 | 39 | ret = gdb.query(q="""START run=node(%d) MATCH run-[:RUN_ACTOR]->actor WHERE actor.actor = %d RETURN actor""" % (RUN_ID, 1138), returns=client.Node)[0] 40 | ACTOR_ID = ret[0]._get_id() 41 | ACTOR1_ID = ACTOR_ID 42 | 43 | ret = gdb.query(q="""START run=node(%d) MATCH run-[:RUN_ACTOR]->actor WHERE actor.actor = %d RETURN actor""" % (RUN_ID, 1146), returns=client.Node)[0] 44 | ACTOR2_ID = ret[0]._get_id() 45 | 46 | ret = gdb.query(q="""START run=node(%d) MATCH run-[:HAS_TIMELINE]->()-[y:NEXT_LEVEL]->()-[m:NEXT_LEVEL]->()-[d:NEXT_LEVEL]->()-[h:NEXT_LEVEL]->hour WHERE d.day = 29 and h.hour = 10 47 | RETURN hour""" % RUN_ID, returns=client.Node)[0] 48 | HOUR_ID = ret[0]._get_id() 49 | 50 | 51 | # ========================================= 52 | 53 | QUERY1 = """ 54 | START root = node(0) 55 | MATCH root-[:HAS_RUN]->run-[:HAS_TIMELINE]->()-[y:NEXT_LEVEL]->()-[m:NEXT_LEVEL]->()-[d:NEXT_LEVEL]->()-[h:NEXT_LEVEL]->()-[:TIMELINE_INSTANCE]->frame 56 | WHERE run.name="%s" and y.year=2009 and m.month=7 and d.day=1 and h.hour>=9 and h.hour<13 57 | RETURN frame ORDER BY frame.timestamp 58 | """ % RUN_NAME 59 | 60 | 61 | QUERY2 = """ 62 | START frame = node(%d) 63 | MATCH frame-[:FRAME_ACTOR]-actor 64 | RETURN actor.name 65 | """ % FRAME_ID 66 | 67 | 68 | QUERY3 = """ 69 | START frame = node(%s) 70 | MATCH frame-[r:FRAME_INTERACTION]-interaction 71 | WHERE r.weight > %d 72 | RETURN interaction.actor1, interaction.actor2, r.weight; 73 | """ % (FRAME_ID, 0) 74 | 75 | 76 | QUERY4 = """ 77 | START run = node(%d) 78 | MATCH run-[:RUN_ACTOR]->actor<-[r:FRAME_ACTOR]-() 79 | RETURN actor.name, count(r) 80 | """ % RUN_ID 81 | 82 | 83 | QUERY5 = """ 84 | START run = node(%d) 85 | MATCH run-[:RUN_ACTOR]->actor<-[r:FRAME_ACTOR]-() 86 | WITH actor.name as name, COUNT(r) as freq 87 | WHERE freq > 1000 88 | RETURN name, freq ORDER BY freq DESC 89 | """ % RUN_ID 90 | 91 | 92 | QUERY5b = """ 93 | START run = node(%d) 94 | MATCH run-[:RUN_ACTOR]-actor 95 | WITH actor 96 | MATCH ()-[r:FRAME_ACTOR]-actor 97 | WITH actor.name as name, COUNT(r) as freq 98 | WHERE freq > 1000 99 | RETURN name, freq ORDER BY freq DESC; 100 | """ % RUN_ID 101 | 102 | 103 | QUERY6 = """ 104 | START actor = node(%d) 105 | MATCH ()-[d:NEXT_LEVEL]->()-[:NEXT_LEVEL]->()-[:TIMELINE_INSTANCE]-()-[:FRAME_ACTOR]-actor 106 | RETURN DISTINCT(d.day) 107 | """ % ACTOR_ID 108 | 109 | 110 | QUERY6b = """ 111 | START actor = node(%d) 112 | MATCH frame-[:FRAME_ACTOR]-actor 113 | RETURN DISTINCT(frame.day) 114 | """ % ACTOR_ID 115 | 116 | 117 | QUERY7 = """ 118 | START actor1 = node(%d) 119 | MATCH actor1<-[:INTERACTION_ACTOR]-()-[:INTERACTION_ACTOR]->actor2 120 | RETURN actor2.name ORDER BY actor2.name 121 | """ % ACTOR_ID 122 | 123 | 124 | QUERY8 = """ 125 | START actor1 = node(%d) 126 | MATCH actor1<-[:INTERACTION_ACTOR]-interaction-[:INTERACTION_ACTOR]->actor2 127 | WITH interaction, actor2 128 | MATCH ()-[d:NEXT_LEVEL]->()-[:NEXT_LEVEL]->()-[:TIMELINE_INSTANCE]-()-[:FRAME_INTERACTION]-interaction 129 | WHERE d.day = 7 130 | RETURN DISTINCT(actor2.name) 131 | """ % ACTOR_ID 132 | 133 | 134 | QUERY9 = """ 135 | START actor1 = node(%d), actor2 = node(%d) 136 | MATCH actor1<-[:INTERACTION_ACTOR]-()-[:INTERACTION_ACTOR]->actor 137 | WITH COLLECT(actor) as neighs1, actor2 138 | MATCH actor2<-[:INTERACTION_ACTOR]-()-[:INTERACTION_ACTOR]->actor 139 | WHERE actor IN neighs1 140 | RETURN actor 141 | """ % (ACTOR1_ID, ACTOR2_ID) 142 | 143 | 144 | QUERY9b = """ 145 | START actor1 = node(%d), actor2 = node(%d) 146 | MATCH actor1<-[:INTERACTION_ACTOR]-()-[:INTERACTION_ACTOR]->actor<-[:INTERACTION_ACTOR]-()-[:INTERACTION_ACTOR]->actor2 147 | RETURN actor 148 | """ % (ACTOR1_ID, ACTOR2_ID) 149 | 150 | 151 | QUERY10 = """ 152 | START run = node(%d) 153 | MATCH run-[:RUN_ACTOR]-actor-[r:INTERACTION_ACTOR]-() 154 | RETURN actor.name, COUNT(r) ORDER BY COUNT(r) DESC 155 | """ % RUN_ID 156 | 157 | 158 | QUERY11a = """ 159 | START actor = node(%d) 160 | MATCH neigh1<-[:INTERACTION_ACTOR]-interaction1-[:INTERACTION_ACTOR]->actor, 161 | ()-[d1:NEXT_LEVEL]->()-[h1:NEXT_LEVEL]->()-[:TIMELINE_INSTANCE]-frame-[:FRAME_INTERACTION]-interaction1 162 | WHERE d1.day = 29 and h1.hour = 10 163 | WITH DISTINCT neigh1, actor 164 | MATCH neigh2<-[:INTERACTION_ACTOR]-interaction2-[:INTERACTION_ACTOR]->actor, 165 | ()-[d2:NEXT_LEVEL]->()-[h2:NEXT_LEVEL]->()-[:TIMELINE_INSTANCE]-frame-[:FRAME_INTERACTION]-interaction2 166 | WHERE d2.day = 29 and h2.hour = 10 167 | WITH distinct neigh2, neigh1 168 | MATCH neigh1<-[:INTERACTION_ACTOR]-interaction3-[:INTERACTION_ACTOR]->neigh2, 169 | ()-[d3:NEXT_LEVEL]->()-[h3:NEXT_LEVEL]->()-[:TIMELINE_INSTANCE]-frame-[:FRAME_INTERACTION]-interaction3 170 | WHERE d3.day = 29 and h3.hour = 10 171 | RETURN DISTINCT neigh1.actor, neigh2.actor ORDER BY neigh1.actor, neigh2.actor; 172 | """ % ACTOR_ID 173 | 174 | 175 | QUERY11b = """ 176 | START actor = node(%d), hour = node(%d) 177 | MATCH neigh1<-[:INTERACTION_ACTOR]-interaction1-[:INTERACTION_ACTOR]->actor, 178 | hour-[:TIMELINE_INSTANCE]->frame-[:FRAME_INTERACTION]->interaction1 179 | WITH DISTINCT hour, neigh1, actor 180 | MATCH neigh2<-[:INTERACTION_ACTOR]-interaction2-[:INTERACTION_ACTOR]->actor, 181 | hour-[:TIMELINE_INSTANCE]->frame-[:FRAME_INTERACTION]->interaction2 182 | WITH DISTINCT hour, neigh1, neigh2 183 | MATCH neigh1<-[:INTERACTION_ACTOR]-interaction3-[:INTERACTION_ACTOR]->neigh2, 184 | hour-[:TIMELINE_INSTANCE]->frame-[:FRAME_INTERACTION]->interaction3 185 | RETURN DISTINCT neigh1.actor, neigh2.actor ORDER BY neigh1.actor, neigh2.actor; 186 | """ % (ACTOR_ID, HOUR_ID) 187 | 188 | 189 | QUERY11c = """ 190 | START actor = node(%d) 191 | MATCH neigh1<-[:INTERACTION_ACTOR]-interaction1-[:INTERACTION_ACTOR]->actor, 192 | frame1-[:FRAME_INTERACTION]->interaction1 193 | WHERE frame1.day = 29 and frame1.hour = 10 194 | WITH DISTINCT neigh1, actor 195 | MATCH neigh2<-[:INTERACTION_ACTOR]-interaction2-[:INTERACTION_ACTOR]->actor, 196 | frame2-[:FRAME_INTERACTION]->interaction2 197 | WHERE frame2.day = 29 and frame2.hour = 10 198 | WITH DISTINCT neigh1, neigh2 199 | MATCH neigh1<-[:INTERACTION_ACTOR]-interaction3-[:INTERACTION_ACTOR]->neigh2, 200 | frame3-[:FRAME_INTERACTION]->interaction3 201 | WHERE frame3.day = 29 and frame3.hour = 10 202 | RETURN DISTINCT neigh1.actor, neigh2.actor ORDER BY neigh1.actor, neigh2.actor; 203 | """ % ACTOR_ID 204 | 205 | 206 | 207 | QLIST = [ 208 | ('QUERY1', QUERY1), ('QUERY2', QUERY2), ('QUERY3', QUERY3), \ 209 | ('QUERY4', QUERY4), ('QUERY5', QUERY5), ('QUERY6', QUERY6), ('QUERY6b', QUERY6b),\ 210 | ('QUERY7', QUERY7), ('QUERY8', QUERY8), ('QUERY9', QUERY9), \ 211 | ('QUERY10', QUERY10), 212 | ('QUERY11a', QUERY11a), ('QUERY11b', QUERY11b), ('QUERY11c', QUERY11c) ] 213 | 214 | 215 | # ========================================= 216 | 217 | def time_query(gdb, query, N=10): 218 | tlist = [] 219 | 220 | for i in range(N): 221 | t1 = time.time() 222 | dummy = list(gdb.query(q=query)) 223 | # print "#" + str(i), len(dummy), "rows" 224 | t2 = time.time() 225 | 226 | tlist.append(t2-t1) 227 | 228 | # sys.stdout.write(".") 229 | # sys.stdout.flush() 230 | 231 | tlist.sort() 232 | 233 | return tuple([int(tlist[int(x)] * 1000) for x in (N/2, N*0.05, N*0.95)]) 234 | 235 | # ========================================= 236 | 237 | for (qname, Q) in QLIST: 238 | (median, quantile5, quantile95) = time_query(gdb, Q) 239 | print "%s\t%dms\t(%dms - %dms)" % (qname, median, quantile5, quantile95) 240 | 241 | -------------------------------------------------------------------------------- /java/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | it.isi 6 | neo4j-dynanets 7 | 0.0.1-SNAPSHOT 8 | jar 9 | 10 | neo4j-dynanets 11 | http://maven.apache.org 12 | 13 | 14 | UTF-8 15 | 3.5.0 16 | 17 | 18 | 19 | 20 | org.neo4j 21 | neo4j 22 | ${neo4j.version} 23 | 24 | 25 | org.neo4j 26 | neo4j-lucene-index 27 | ${neo4j.version} 28 | 29 | 30 | org.neo4j 31 | server-api 32 | ${neo4j.version} 33 | 34 | 35 | org.neo4j 36 | neo4j-kernel 37 | ${neo4j.version} 38 | test-jar 39 | test 40 | 41 | 42 | junit 43 | junit 44 | 3.8.1 45 | test 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /java/src/main/java/META-INF/services/org.neo4j.server.plugins.ServerPlugin: -------------------------------------------------------------------------------- 1 | org.neo4j.collections.timeline.StructuredTimelinePlugin -------------------------------------------------------------------------------- /java/src/main/java/it/isi/neo4j/dynanets/BaseTimeline.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2002-2012 "Neo Technology," 3 | * Network Engine for Objects in Lund AB [http://neotechnology.com] 4 | * 5 | * This file is part of Neo4j. 6 | * 7 | * Neo4j is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program. If not, see . 19 | */ 20 | package it.isi.neo4j.dynanets; 21 | 22 | import java.util.ArrayList; 23 | import java.util.Iterator; 24 | import java.util.List; 25 | 26 | import org.neo4j.graphdb.Direction; 27 | import org.neo4j.graphdb.GraphDatabaseService; 28 | import org.neo4j.graphdb.Node; 29 | import org.neo4j.graphdb.Relationship; 30 | import org.neo4j.graphdb.RelationshipType; 31 | import org.neo4j.graphdb.ReturnableEvaluator; 32 | import org.neo4j.graphdb.StopEvaluator; 33 | import org.neo4j.graphdb.Transaction; 34 | import org.neo4j.graphdb.TraversalPosition; 35 | import org.neo4j.graphdb.Traverser; 36 | import org.neo4j.graphdb.Traverser.Order; 37 | //import org.neo4j.kernel.AbstractGraphDatabase; 38 | 39 | 40 | /** 41 | * An implementation of {@link TimelineIndex} on top of Neo4j, using 42 | * {@link BTree} for indexing. Note: this implementation is not thread-safe 43 | * (yet). 44 | * 45 | * Nodes added to a timeline will get a {@link Relationship} created to it so if 46 | * you delete such a node later on you'll have to remove it from the timeline 47 | * first (or in the same transaction at least). 48 | * 49 | * This source is based on the 50 | * Timeline 51 | * implementation of Neo4j Collections. 52 | */ 53 | public class BaseTimeline //implements TimelineIndex 54 | { 55 | protected static enum RelTypes implements RelationshipType 56 | { 57 | TIMELINE_INSTANCE, 58 | TIMELINE_NEXT_ENTRY, 59 | } 60 | 61 | protected static final String TIMESTAMP = "timestamp"; 62 | protected static final String TIMELINE_NAME = "timeline_name"; 63 | 64 | 65 | protected final Node underlyingNode; 66 | protected final String name; 67 | protected final GraphDatabaseService graphDb; 68 | 69 | // lazy init cache holders for first and last 70 | protected Node firstNode; 71 | protected Node lastNode; 72 | 73 | /** 74 | * Creates/loads a timeline. The underlyingNode can either be a 75 | * new (just created) node or a node that already represents a previously 76 | * timeline. 77 | * 78 | * @param name The unique name of the timeline or null if 79 | * timeline already exist 80 | * @param underlyingNode The underlying node representing the timeline 81 | * @param indexed Set to true if this timeline is indexed 82 | * @param graphDb the {@link GraphDatabaseService} 83 | */ 84 | public BaseTimeline( String name, Node underlyingNode, 85 | GraphDatabaseService graphDb ) 86 | { 87 | if ( underlyingNode == null || graphDb == null ) 88 | { 89 | throw new IllegalArgumentException( 90 | "Null parameter underlyingNode=" + underlyingNode 91 | + " graphDb=" + graphDb ); 92 | } 93 | this.underlyingNode = underlyingNode; 94 | this.graphDb = graphDb; 95 | Transaction tx = graphDb.beginTx(); 96 | try 97 | { 98 | assertPropertyIsSame( TIMELINE_NAME, name ); 99 | this.name = name; 100 | tx.success(); 101 | } 102 | finally 103 | { 104 | tx.finish(); 105 | } 106 | } 107 | 108 | protected void assertPropertyIsSame( String key, Object value ) 109 | { 110 | Object storedValue = underlyingNode.getProperty( key, null ); 111 | if ( storedValue != null ) 112 | { 113 | if ( !storedValue.equals( value ) ) 114 | { 115 | throw new IllegalArgumentException( "Timeline(" 116 | + underlyingNode 117 | + ") property '" + key 118 | + "' is " + storedValue 119 | + ", passed in " + value ); 120 | } 121 | } 122 | else 123 | { 124 | underlyingNode.setProperty( key, value ); 125 | } 126 | } 127 | 128 | /** 129 | * Returns the underlying node representing this timeline. 130 | * 131 | * @return The underlying node representing this timeline 132 | */ 133 | public Node getUnderlyingNode() 134 | { 135 | return underlyingNode; 136 | } 137 | 138 | public Node getLastNode() 139 | { 140 | if ( lastNode != null ) 141 | { 142 | return lastNode; 143 | } 144 | Relationship rel = underlyingNode.getSingleRelationship( 145 | RelTypes.TIMELINE_NEXT_ENTRY, Direction.INCOMING ); 146 | if ( rel == null ) 147 | { 148 | return null; 149 | } 150 | lastNode = rel.getStartNode().getRelationships( 151 | RelTypes.TIMELINE_INSTANCE, Direction.OUTGOING ).iterator().next().getEndNode(); 152 | return lastNode; 153 | } 154 | 155 | public Node getFirstNode() 156 | { 157 | if ( firstNode != null ) 158 | { 159 | return firstNode; 160 | } 161 | Relationship rel = underlyingNode.getSingleRelationship( 162 | RelTypes.TIMELINE_NEXT_ENTRY, Direction.OUTGOING ); 163 | if ( rel == null ) 164 | { 165 | return null; 166 | } 167 | firstNode = rel.getEndNode().getRelationships( 168 | RelTypes.TIMELINE_INSTANCE, Direction.OUTGOING ).iterator().next().getEndNode(); 169 | return firstNode; 170 | } 171 | 172 | public void addNode( Node nodeToAdd, long timestamp ) 173 | { 174 | if ( nodeToAdd == null ) 175 | { 176 | throw new IllegalArgumentException( "Null node" ); 177 | } 178 | Transaction tx = graphDb.beginTx(); 179 | try 180 | { 181 | for ( Relationship rel : nodeToAdd.getRelationships( RelTypes.TIMELINE_INSTANCE ) ) 182 | { 183 | if ( rel.getProperty( TIMELINE_NAME, "" ).equals( name ) ) 184 | { 185 | throw new IllegalArgumentException( 186 | "Node[" + nodeToAdd.getId() 187 | + "] already connected to Timeline[" + name 188 | + "]" ); 189 | } 190 | } 191 | Relationship rel = underlyingNode.getSingleRelationship( 192 | RelTypes.TIMELINE_NEXT_ENTRY, Direction.INCOMING ); 193 | if ( rel == null ) 194 | { 195 | // timeline was empty 196 | Node node = createNewTimeNode( timestamp, nodeToAdd ); 197 | underlyingNode.createRelationshipTo( node, 198 | RelTypes.TIMELINE_NEXT_ENTRY ); 199 | node.createRelationshipTo( underlyingNode, 200 | RelTypes.TIMELINE_NEXT_ENTRY ); 201 | firstNode = nodeToAdd; 202 | lastNode = nodeToAdd; 203 | } 204 | else 205 | { 206 | Node previousLast = rel.getStartNode(); 207 | long previousTime = (Long) previousLast.getProperty( TIMESTAMP ); 208 | if ( timestamp > previousTime ) 209 | { 210 | // add it last in chain 211 | Node node = createNewTimeNode( timestamp, nodeToAdd ); 212 | rel.delete(); 213 | previousLast.createRelationshipTo( node, 214 | RelTypes.TIMELINE_NEXT_ENTRY ); 215 | node.createRelationshipTo( underlyingNode, 216 | RelTypes.TIMELINE_NEXT_ENTRY ); 217 | lastNode = nodeToAdd; 218 | } 219 | else if ( timestamp == previousTime ) 220 | { 221 | Relationship instanceRel = previousLast.createRelationshipTo( nodeToAdd, 222 | RelTypes.TIMELINE_INSTANCE ); 223 | instanceRel.setProperty( TIMELINE_NAME, name ); 224 | } 225 | else 226 | { 227 | // find where to insert 228 | Iterator itr = getAllTimeNodesAfter( timestamp ).iterator(); 229 | assert itr.hasNext(); 230 | Node next = itr.next(); 231 | rel = next.getSingleRelationship( 232 | RelTypes.TIMELINE_NEXT_ENTRY, Direction.INCOMING ); 233 | assert rel != null; 234 | Node previous = rel.getStartNode(); 235 | long previousTimestamp = Long.MIN_VALUE; 236 | if ( !previous.equals( underlyingNode ) ) 237 | { 238 | previousTimestamp = (Long) previous.getProperty( TIMESTAMP ); 239 | } 240 | if ( previousTimestamp == timestamp ) 241 | { 242 | // just connect previous with node to add 243 | Relationship instanceRel = previous.createRelationshipTo( nodeToAdd, 244 | RelTypes.TIMELINE_INSTANCE ); 245 | instanceRel.setProperty( TIMELINE_NAME, name ); 246 | return; 247 | } 248 | long nextTimestamp = (Long) next.getProperty( TIMESTAMP ); 249 | if ( nextTimestamp == timestamp ) 250 | { 251 | // just connect next with node to add 252 | Relationship instanceRel = next.createRelationshipTo( nodeToAdd, 253 | RelTypes.TIMELINE_INSTANCE ); 254 | instanceRel.setProperty( TIMELINE_NAME, name ); 255 | return; 256 | } 257 | 258 | assert previousTimestamp < timestamp; 259 | assert nextTimestamp > timestamp; 260 | 261 | Node node = createNewTimeNode( timestamp, nodeToAdd ); 262 | rel.delete(); 263 | previous.createRelationshipTo( node, 264 | RelTypes.TIMELINE_NEXT_ENTRY ); 265 | node.createRelationshipTo( next, 266 | RelTypes.TIMELINE_NEXT_ENTRY ); 267 | if ( previous.equals( underlyingNode ) ) 268 | { 269 | firstNode = nodeToAdd; 270 | } 271 | } 272 | } 273 | tx.success(); 274 | } 275 | finally 276 | { 277 | tx.finish(); 278 | } 279 | } 280 | 281 | private Node createNewTimeNode( long timestamp, Node nodeToAdd ) 282 | { 283 | Node node = graphDb.createNode(); 284 | node.setProperty( TIMESTAMP, timestamp ); 285 | Relationship instanceRel = node.createRelationshipTo( nodeToAdd, 286 | RelTypes.TIMELINE_INSTANCE ); 287 | instanceRel.setProperty( TIMELINE_NAME, name ); 288 | return node; 289 | } 290 | 291 | public long getTimestampForNode( Node node ) 292 | { 293 | Transaction tx = graphDb.beginTx(); 294 | try 295 | { 296 | Traverser traverser = node.traverse( Traverser.Order.DEPTH_FIRST, 297 | StopEvaluator.END_OF_GRAPH, new ReturnableEvaluator() 298 | { 299 | public boolean isReturnableNode( 300 | TraversalPosition position ) 301 | { 302 | Node currentNode = position.currentNode(); 303 | return currentNode != null 304 | && !currentNode.hasRelationship( 305 | RelTypes.TIMELINE_INSTANCE, 306 | Direction.INCOMING ); 307 | } 308 | }, RelTypes.TIMELINE_INSTANCE, Direction.INCOMING ); 309 | 310 | Iterator hits = traverser.iterator(); 311 | Long result = null; 312 | if ( hits.hasNext() ) 313 | { 314 | Node hit = hits.next(); 315 | result = (Long) hit.getProperty( TIMESTAMP ); 316 | } 317 | else 318 | { 319 | throw new RuntimeException( 320 | "No timpestamp found for '" + node 321 | + "' maybe it's not in the timeline?" ); 322 | } 323 | tx.success(); 324 | return result; 325 | } 326 | finally 327 | { 328 | tx.finish(); 329 | } 330 | } 331 | 332 | public void removeNode(Node nodeToRemove, boolean transactional) 333 | { 334 | if ( nodeToRemove == null ) 335 | { 336 | throw new IllegalArgumentException( "Null parameter." ); 337 | } 338 | if ( nodeToRemove.equals( underlyingNode ) ) 339 | { 340 | throw new IllegalArgumentException( "Cannot remove underlying node" ); 341 | } 342 | Transaction tx = graphDb.beginTx(); 343 | try 344 | { 345 | Relationship instanceRel = null; 346 | for ( Relationship rel : nodeToRemove.getRelationships( RelTypes.TIMELINE_INSTANCE ) ) 347 | { 348 | if ( rel.getProperty( TIMELINE_NAME, "" ).equals( name ) ) 349 | { 350 | assert instanceRel == null; 351 | instanceRel = rel; 352 | } 353 | } 354 | if ( instanceRel == null ) 355 | { 356 | throw new IllegalArgumentException( 357 | "Node[" + nodeToRemove.getId() 358 | + "] not added to Timeline[" + name + "]" ); 359 | } 360 | Node node = instanceRel.getStartNode(); 361 | instanceRel.delete(); 362 | if ( firstNode != null && firstNode.equals( nodeToRemove ) ) 363 | { 364 | firstNode = null; 365 | } 366 | if ( lastNode != null && lastNode.equals( nodeToRemove ) ) 367 | { 368 | lastNode = null; 369 | } 370 | if ( node.getRelationships( RelTypes.TIMELINE_INSTANCE ).iterator().hasNext() ) 371 | { 372 | // still have instances connected to this time 373 | return; 374 | } 375 | Relationship incoming = node.getSingleRelationship( 376 | RelTypes.TIMELINE_NEXT_ENTRY, Direction.INCOMING ); 377 | if ( incoming == null ) 378 | { 379 | throw new RuntimeException( "No incoming relationship of " 380 | + RelTypes.TIMELINE_NEXT_ENTRY 381 | + " found" ); 382 | } 383 | Relationship outgoing = node.getSingleRelationship( 384 | RelTypes.TIMELINE_NEXT_ENTRY, Direction.OUTGOING ); 385 | if ( outgoing == null ) 386 | { 387 | throw new RuntimeException( "No outgoing relationship of " 388 | + RelTypes.TIMELINE_NEXT_ENTRY 389 | + " found" ); 390 | } 391 | Node previous = incoming.getStartNode(); 392 | Node next = outgoing.getEndNode(); 393 | incoming.delete(); 394 | outgoing.delete(); 395 | node.delete(); 396 | if ( !previous.equals( next ) ) 397 | { 398 | previous.createRelationshipTo( next, 399 | RelTypes.TIMELINE_NEXT_ENTRY ); 400 | } 401 | tx.success(); 402 | } 403 | finally 404 | { 405 | if(transactional) 406 | { 407 | tx.finish(); 408 | } 409 | } 410 | } 411 | 412 | public void removeNode( Node nodeToRemove ) 413 | { 414 | removeNode( nodeToRemove, true ); 415 | } 416 | 417 | public Iterable getAllNodes( Long afterTimestampOrNull, 418 | Long beforeTimestampOrNull ) 419 | { 420 | Iterable result = null; 421 | if ( afterTimestampOrNull == null && beforeTimestampOrNull == null ) 422 | { 423 | result = getAllNodes(); 424 | } 425 | else if ( afterTimestampOrNull == null ) 426 | { 427 | result = getAllNodesBefore( beforeTimestampOrNull ); 428 | } 429 | else if ( beforeTimestampOrNull == null ) 430 | { 431 | result = getAllNodesAfter( afterTimestampOrNull ); 432 | } 433 | else 434 | { 435 | result = getAllNodesBetween( afterTimestampOrNull, 436 | beforeTimestampOrNull ); 437 | } 438 | return result; 439 | } 440 | 441 | public Iterable getAllNodes() 442 | { 443 | return underlyingNode.traverse( 444 | Order.BREADTH_FIRST, 445 | StopEvaluator.END_OF_GRAPH, 446 | new ReturnableEvaluator() 447 | { 448 | public boolean isReturnableNode( TraversalPosition position ) 449 | { 450 | Relationship last = position.lastRelationshipTraversed(); 451 | if ( last != null 452 | && last.getType().equals( 453 | RelTypes.TIMELINE_INSTANCE ) ) 454 | { 455 | return true; 456 | } 457 | return false; 458 | } 459 | }, RelTypes.TIMELINE_INSTANCE, Direction.OUTGOING, 460 | RelTypes.TIMELINE_NEXT_ENTRY, Direction.OUTGOING ); 461 | } 462 | 463 | Iterable getAllTimeNodes() 464 | { 465 | return underlyingNode.traverse( Order.DEPTH_FIRST, 466 | StopEvaluator.END_OF_GRAPH, new ReturnableEvaluator() 467 | { 468 | public boolean isReturnableNode( TraversalPosition position ) 469 | { 470 | return position.depth() > 0; 471 | } 472 | }, RelTypes.TIMELINE_NEXT_ENTRY, Direction.OUTGOING ); 473 | } 474 | 475 | // from closest lower indexed start relationship 476 | protected Node getIndexedStartNode( long timestamp ) 477 | { 478 | return underlyingNode; 479 | } 480 | 481 | public Iterable getNodes( long timestamp ) 482 | { 483 | Node currentNode = getIndexedStartNode( timestamp ); 484 | List nodeList = new ArrayList(); 485 | if ( currentNode.equals( underlyingNode ) ) 486 | { 487 | if ( !currentNode.hasRelationship( RelTypes.TIMELINE_NEXT_ENTRY, 488 | Direction.OUTGOING ) ) 489 | { 490 | // empty timeline 491 | return nodeList; 492 | } 493 | // no index or best start node is underlying node 494 | currentNode = currentNode.getSingleRelationship( 495 | RelTypes.TIMELINE_NEXT_ENTRY, Direction.OUTGOING ).getEndNode(); 496 | } 497 | do 498 | { 499 | long currentTime = (Long) currentNode.getProperty( TIMESTAMP ); 500 | if ( currentTime == timestamp ) 501 | { 502 | for ( Relationship instanceRel : currentNode.getRelationships( 503 | RelTypes.TIMELINE_INSTANCE, Direction.OUTGOING ) ) 504 | { 505 | nodeList.add( instanceRel.getEndNode() ); 506 | } 507 | break; 508 | } 509 | if ( currentTime > timestamp ) 510 | { 511 | break; 512 | } 513 | Relationship rel = currentNode.getSingleRelationship( 514 | RelTypes.TIMELINE_NEXT_ENTRY, Direction.OUTGOING ); 515 | currentNode = rel.getEndNode(); 516 | } 517 | while ( !currentNode.equals( underlyingNode ) ); 518 | return nodeList; 519 | } 520 | 521 | public Iterable getAllNodesAfter( final long timestamp ) 522 | { 523 | Node startNode = getIndexedStartNode( timestamp ); 524 | return startNode.traverse( Order.DEPTH_FIRST, new StopEvaluator() 525 | { 526 | public boolean isStopNode( TraversalPosition position ) 527 | { 528 | if ( position.lastRelationshipTraversed() != null 529 | && position.currentNode().equals( underlyingNode ) ) 530 | { 531 | return true; 532 | } 533 | return false; 534 | } 535 | }, new ReturnableEvaluator() 536 | { 537 | private boolean timeOk = false; 538 | 539 | public boolean isReturnableNode( TraversalPosition position ) 540 | { 541 | if ( position.currentNode().equals( underlyingNode ) ) 542 | { 543 | return false; 544 | } 545 | Relationship last = position.lastRelationshipTraversed(); 546 | if ( !timeOk && last != null 547 | && last.getType().equals( RelTypes.TIMELINE_NEXT_ENTRY ) ) 548 | { 549 | Node node = position.currentNode(); 550 | long currentTime = (Long) node.getProperty( TIMESTAMP ); 551 | timeOk = currentTime > timestamp; 552 | return false; 553 | } 554 | if ( timeOk 555 | && last.getType().equals( RelTypes.TIMELINE_INSTANCE ) ) 556 | { 557 | return true; 558 | } 559 | return false; 560 | } 561 | }, RelTypes.TIMELINE_NEXT_ENTRY, Direction.OUTGOING, 562 | RelTypes.TIMELINE_INSTANCE, Direction.OUTGOING ); 563 | } 564 | 565 | Iterable getAllTimeNodesAfter( final long timestamp ) 566 | { 567 | Node startNode = getIndexedStartNode( timestamp ); 568 | return startNode.traverse( Order.DEPTH_FIRST, new StopEvaluator() 569 | { 570 | public boolean isStopNode( TraversalPosition position ) 571 | { 572 | if ( position.lastRelationshipTraversed() != null 573 | && position.currentNode().equals( underlyingNode ) ) 574 | { 575 | return true; 576 | } 577 | return false; 578 | } 579 | }, new ReturnableEvaluator() 580 | { 581 | private boolean timeOk = false; 582 | 583 | public boolean isReturnableNode( TraversalPosition position ) 584 | { 585 | if ( position.currentNode().equals( underlyingNode ) ) 586 | { 587 | return false; 588 | } 589 | Relationship last = position.lastRelationshipTraversed(); 590 | if ( !timeOk && last != null 591 | && last.getType().equals( RelTypes.TIMELINE_NEXT_ENTRY ) ) 592 | { 593 | Node node = position.currentNode(); 594 | long currentTime = (Long) node.getProperty( TIMESTAMP ); 595 | timeOk = currentTime > timestamp; 596 | } 597 | return timeOk; 598 | } 599 | }, RelTypes.TIMELINE_NEXT_ENTRY, Direction.OUTGOING ); 600 | } 601 | 602 | public Iterable getAllNodesBefore( final long timestamp ) 603 | { 604 | return underlyingNode.traverse( Order.DEPTH_FIRST, new StopEvaluator() 605 | { 606 | public boolean isStopNode( TraversalPosition position ) 607 | { 608 | Relationship last = position.lastRelationshipTraversed(); 609 | if ( last != null 610 | && last.getType().equals( RelTypes.TIMELINE_NEXT_ENTRY ) ) 611 | { 612 | Node node = position.currentNode(); 613 | long currentTime = (Long) node.getProperty( TIMESTAMP ); 614 | return currentTime >= timestamp; 615 | } 616 | return false; 617 | } 618 | }, new ReturnableEvaluator() 619 | { 620 | public boolean isReturnableNode( TraversalPosition position ) 621 | { 622 | Relationship last = position.lastRelationshipTraversed(); 623 | if ( last != null 624 | && last.getType().equals( RelTypes.TIMELINE_INSTANCE ) ) 625 | { 626 | return true; 627 | } 628 | return false; 629 | } 630 | }, RelTypes.TIMELINE_NEXT_ENTRY, Direction.OUTGOING, 631 | RelTypes.TIMELINE_INSTANCE, Direction.OUTGOING ); 632 | } 633 | 634 | public Iterable getAllNodesBetween( final long startTime, 635 | final long endTime ) 636 | { 637 | if ( startTime >= endTime ) 638 | { 639 | throw new IllegalArgumentException( 640 | "Start time greater or equal to end time" ); 641 | } 642 | Node startNode = getIndexedStartNode( startTime ); 643 | return startNode.traverse( Order.DEPTH_FIRST, new StopEvaluator() 644 | { 645 | public boolean isStopNode( TraversalPosition position ) 646 | { 647 | Relationship last = position.lastRelationshipTraversed(); 648 | if ( last != null 649 | && position.currentNode().equals( underlyingNode ) ) 650 | { 651 | return true; 652 | } 653 | if ( last != null 654 | && last.getType().equals( RelTypes.TIMELINE_NEXT_ENTRY ) ) 655 | { 656 | Node node = position.currentNode(); 657 | long currentTime = (Long) node.getProperty( TIMESTAMP ); 658 | return currentTime >= endTime; 659 | } 660 | return false; 661 | } 662 | }, new ReturnableEvaluator() 663 | { 664 | private boolean timeOk = false; 665 | 666 | public boolean isReturnableNode( TraversalPosition position ) 667 | { 668 | if ( position.currentNode().equals( underlyingNode ) ) 669 | { 670 | return false; 671 | } 672 | Relationship last = position.lastRelationshipTraversed(); 673 | if ( !timeOk && last != null 674 | && last.getType().equals( RelTypes.TIMELINE_NEXT_ENTRY ) ) 675 | { 676 | Node node = position.currentNode(); 677 | long currentTime = (Long) node.getProperty( TIMESTAMP ); 678 | timeOk = currentTime > startTime; 679 | return false; 680 | } 681 | if ( timeOk 682 | && last.getType().equals( RelTypes.TIMELINE_INSTANCE ) ) 683 | { 684 | return true; 685 | } 686 | return false; 687 | } 688 | }, RelTypes.TIMELINE_NEXT_ENTRY, Direction.OUTGOING, 689 | RelTypes.TIMELINE_INSTANCE, Direction.OUTGOING ); 690 | } 691 | 692 | public void delete() 693 | { 694 | Relationship rel = underlyingNode.getSingleRelationship( 695 | RelTypes.TIMELINE_NEXT_ENTRY, Direction.OUTGOING ); 696 | while ( rel != null ) 697 | { 698 | Node node = rel.getEndNode(); 699 | if ( !node.equals( underlyingNode ) ) 700 | { 701 | for ( Relationship instance : node.getRelationships( RelTypes.TIMELINE_INSTANCE ) ) 702 | { 703 | instance.delete(); 704 | } 705 | rel.delete(); 706 | rel = node.getSingleRelationship( RelTypes.TIMELINE_NEXT_ENTRY, 707 | Direction.OUTGOING ); 708 | node.delete(); 709 | } 710 | else 711 | { 712 | rel.delete(); 713 | rel = null; 714 | } 715 | } 716 | } 717 | 718 | public void delete(int commitInterval) 719 | { 720 | int count = 0; 721 | while(this.getLastNode()!=null) { 722 | 723 | this.removeNode( this.getLastNode() ); 724 | count++; 725 | if ( count > commitInterval ) 726 | { 727 | System.out.print("."); 728 | // restartTx(); 729 | count = 0; 730 | } 731 | } 732 | } 733 | 734 | // private void restartTx() { 735 | // try 736 | // { 737 | // javax.transaction.Transaction tx = ((AbstractGraphDatabase)graphDb).getTxManager().getTransaction(); 738 | // if ( tx != null ) 739 | // { 740 | // tx.commit(); 741 | // } 742 | // } 743 | // catch ( Exception e ) 744 | // { 745 | // throw new RuntimeException( e ); 746 | // } 747 | // graphDb.beginTx(); 748 | // } 749 | } 750 | -------------------------------------------------------------------------------- /java/src/main/java/it/isi/neo4j/dynanets/StructuredTimeline.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2008-2010 Istituto per l'Interscambio Scientifico I.S.I. 3 | * You can contact us by email (isi@isi.it) or write to: 4 | * ISI Foundation, Viale S. Severo 65, 10133 Torino, Italy. 5 | * 6 | * This program was written by André Panisson 7 | * 8 | */ 9 | package it.isi.neo4j.dynanets; 10 | 11 | import java.util.ArrayList; 12 | import java.util.Calendar; 13 | import java.util.GregorianCalendar; 14 | import java.util.List; 15 | 16 | import org.neo4j.graphdb.Direction; 17 | import org.neo4j.graphdb.GraphDatabaseService; 18 | import org.neo4j.graphdb.Node; 19 | import org.neo4j.graphdb.Relationship; 20 | import org.neo4j.graphdb.RelationshipType; 21 | 22 | public class StructuredTimeline extends BaseTimeline { 23 | 24 | static enum StructuredRelTypes implements RelationshipType 25 | { 26 | NEXT_LEVEL 27 | } 28 | 29 | private final GraphDatabaseService graphDb; 30 | 31 | public StructuredTimeline( String name, Node underlyingNode, GraphDatabaseService graphDb ) { 32 | super(name, underlyingNode, graphDb); 33 | this.graphDb = graphDb; 34 | } 35 | 36 | public void addNode(Node nodeToAdd, long timestamp) { 37 | super.addNode(nodeToAdd, timestamp); 38 | Calendar c = new GregorianCalendar(); 39 | c.setTimeInMillis(timestamp*1000); 40 | 41 | Node underlyingNode = this.getUnderlyingNode(); 42 | Node nextLevel = underlyingNode; 43 | 44 | nextLevel.setProperty("next_level", "year"); 45 | nextLevel = createNextLevelNode(nextLevel, "year", c.get(Calendar.YEAR)); 46 | nextLevel.setProperty("next_level", "month"); 47 | nextLevel = createNextLevelNode(nextLevel, "month", c.get(Calendar.MONTH)+1); 48 | nextLevel.setProperty("next_level", "day"); 49 | nextLevel = createNextLevelNode(nextLevel, "day", c.get(Calendar.DAY_OF_MONTH)); 50 | nextLevel.setProperty("next_level", "hour"); 51 | nextLevel = createNextLevelNode(nextLevel, "hour", c.get(Calendar.HOUR_OF_DAY)); 52 | 53 | Relationship rel = nodeToAdd.getSingleRelationship(RelTypes.TIMELINE_INSTANCE, Direction.INCOMING); 54 | Node timeNode = rel.getStartNode(); 55 | nextLevel.setProperty("next_level", "timestamp"); 56 | rel = nextLevel.createRelationshipTo(timeNode, StructuredRelTypes.NEXT_LEVEL); 57 | rel.setProperty("timestamp", timestamp); 58 | 59 | } 60 | 61 | @Override 62 | public Iterable getNodes(long timestamp) { 63 | 64 | Calendar c = new GregorianCalendar(); 65 | c.setTimeInMillis(timestamp*1000); 66 | List nodeList = new ArrayList(); 67 | 68 | Node underlyingNode = this.getUnderlyingNode(); 69 | Node currentNode = underlyingNode; 70 | 71 | currentNode = getNextLevelNode(currentNode, "year", c.get(Calendar.YEAR)); 72 | if (currentNode == null) return nodeList; 73 | 74 | currentNode = getNextLevelNode(currentNode, "month", c.get(Calendar.MONTH)+1); 75 | if (currentNode == null) return nodeList; 76 | 77 | currentNode = getNextLevelNode(currentNode, "day", c.get(Calendar.DAY_OF_MONTH)); 78 | if (currentNode == null) return nodeList; 79 | 80 | currentNode = getNextLevelNode(currentNode, "hour", c.get(Calendar.HOUR_OF_DAY)); 81 | if (currentNode == null) return nodeList; 82 | 83 | currentNode = getNextLevelNode(currentNode, "timestamp", timestamp); 84 | if (currentNode == null) return nodeList; 85 | 86 | do 87 | { 88 | long currentTime = (Long) currentNode.getProperty( "timestamp" ); 89 | if ( currentTime == timestamp ) 90 | { 91 | for ( Relationship instanceRel : currentNode.getRelationships( 92 | RelTypes.TIMELINE_INSTANCE, Direction.OUTGOING ) ) 93 | { 94 | nodeList.add( instanceRel.getEndNode() ); 95 | } 96 | break; 97 | } 98 | if ( currentTime > timestamp ) 99 | { 100 | break; 101 | } 102 | Relationship rel = currentNode.getSingleRelationship( 103 | RelTypes.TIMELINE_NEXT_ENTRY, Direction.OUTGOING ); 104 | currentNode = rel.getEndNode(); 105 | } 106 | while ( !currentNode.equals( underlyingNode ) ); 107 | return nodeList; 108 | } 109 | 110 | private Node getNextLevelNode(Node parent, String propertyName, Object propertyValue) { 111 | Relationship rel = null; 112 | for (Relationship r: parent.getRelationships(Direction.OUTGOING, StructuredRelTypes.NEXT_LEVEL)) { 113 | if (r.getProperty(propertyName).equals(propertyValue)) { 114 | rel = r; 115 | break; 116 | } 117 | } 118 | 119 | return ( rel == null ) ? null : rel.getEndNode(); 120 | 121 | } 122 | 123 | private Node createNextLevelNode(Node parent, String propertyName, Object propertyValue) { 124 | Node nextLevel = getNextLevelNode(parent, propertyName, propertyValue); 125 | 126 | if ( nextLevel == null ) 127 | { 128 | nextLevel = graphDb.createNode(); 129 | Relationship rel = parent.createRelationshipTo( nextLevel, 130 | StructuredRelTypes.NEXT_LEVEL ); 131 | rel.setProperty(propertyName, propertyValue); 132 | } 133 | 134 | return nextLevel; 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /java/src/main/java/it/isi/neo4j/dynanets/StructuredTimelinePlugin.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2008-2010 Istituto per l'Interscambio Scientifico I.S.I. 3 | * You can contact us by email (isi@isi.it) or write to: 4 | * ISI Foundation, Viale S. Severo 65, 10133 Torino, Italy. 5 | * 6 | * This program was written by André Panisson 7 | * 8 | */ 9 | package it.isi.neo4j.dynanets; 10 | 11 | import java.text.ParseException; 12 | import java.text.SimpleDateFormat; 13 | import java.util.Date; 14 | 15 | import org.neo4j.graphdb.GraphDatabaseService; 16 | import org.neo4j.graphdb.Node; 17 | import org.neo4j.graphdb.Transaction; 18 | import org.neo4j.server.plugins.Description; 19 | import org.neo4j.server.plugins.Name; 20 | import org.neo4j.server.plugins.Parameter; 21 | import org.neo4j.server.plugins.PluginTarget; 22 | import org.neo4j.server.plugins.ServerPlugin; 23 | import org.neo4j.server.plugins.Source; 24 | 25 | @Description( "An extension to the Neo4j Server for accessing the structured timeline" ) 26 | public class StructuredTimelinePlugin extends ServerPlugin 27 | { 28 | 29 | @Name("create_timeline") 30 | @Description("") 31 | @PluginTarget( GraphDatabaseService.class ) 32 | public Node createTimeline( 33 | @Source GraphDatabaseService graphDb, 34 | @Description("The node that will represent the timeline.") @Parameter(name = "tnode") Node tnode, 35 | @Description("The timeline name.") @Parameter(name = "name") String name) { 36 | new StructuredTimeline( name, tnode, graphDb ); 37 | return tnode; 38 | } 39 | 40 | @Name("add_timeline_node") 41 | @Description("") 42 | @PluginTarget( GraphDatabaseService.class ) 43 | public Node addTimelineNode( 44 | @Source GraphDatabaseService graphDb, 45 | @Description("The node to add to timeline.") @Parameter(name = "node") Node node, 46 | @Description("The node representing the timeline.") @Parameter(name = "tnode") Node tnode, 47 | @Description("The timestamp.") @Parameter(name = "timestamp") Long timestamp) { 48 | 49 | Transaction tx = graphDb.beginTx(); 50 | try { 51 | String timelineName = tnode.getProperty( "timeline_name" ).toString(); 52 | StructuredTimeline timeline = new StructuredTimeline( timelineName, tnode, graphDb ); 53 | timeline.addNode(node, timestamp); 54 | tx.success(); 55 | } finally { 56 | tx.finish(); 57 | } 58 | 59 | return node; 60 | } 61 | 62 | @Name("get_timeline_nodes") 63 | @Description("") 64 | @PluginTarget( GraphDatabaseService.class ) 65 | public Iterable getTimelineNodes( 66 | @Source GraphDatabaseService graphDb, 67 | @Description("The node representing the timeline.") @Parameter(name = "tnode") Node tnode, 68 | @Description("The timestamp.") @Parameter(name = "timestamp") Long timestamp) { 69 | String timelineName = tnode.getProperty( "timeline_name" ).toString(); 70 | StructuredTimeline timeline = new StructuredTimeline( timelineName, tnode, graphDb ); 71 | return timeline.getNodes(timestamp); 72 | } 73 | 74 | @Name("get_timeline_nodes_by_date") 75 | @Description("") 76 | @PluginTarget( GraphDatabaseService.class ) 77 | public Iterable getTimelineNodesByDate( 78 | @Source GraphDatabaseService graphDb, 79 | @Description("The node representing the timeline.") @Parameter(name = "tnode") Node tnode, 80 | @Description("The date.") @Parameter(name = "date") String date) { 81 | try { 82 | Date d = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(date); 83 | return this.getTimelineNodes(graphDb, tnode, d.getTime()); 84 | } catch (ParseException e) { 85 | // TODO Auto-generated catch block 86 | e.printStackTrace(); 87 | return null; 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /load_gexf_to_neo4j.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Parse a dynamic GEXF file 4 | # and upload it to a Neo4j REST server 5 | # 6 | # LIMITATIONS: 7 | # - does not parse node/edge attributes or other metadata 8 | # - supports spells only -- not slices for dynamic attributes 9 | # - only 'integer' timeformat is supported. Time is assumed to be POSIX time. 10 | # 11 | # Copyright (C) 2012 ISI Foundation 12 | # written by Ciro Cattuto 13 | # and Andre' Panisson 14 | # 15 | # This program is free software: you can redistribute it and/or modify 16 | # it under the terms of the GNU General Public License as published by 17 | # the Free Software Foundation, either version 3 of the License, or 18 | # (at your option) any later version. 19 | # 20 | # This program is distributed in the hope that it will be useful, 21 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | # GNU General Public License for more details. 24 | # 25 | # You should have received a copy of the GNU General Public License 26 | # along with this program. If not, see . 27 | # 28 | 29 | import sys, time 30 | import argparse 31 | import xml.etree.ElementTree as xml 32 | from neo4jrestclient.client import GraphDatabase 33 | 34 | parser = argparse.ArgumentParser(description='Load a dynamic GEXF file into a Neo4j REST store.') 35 | 36 | parser.add_argument('gexf', metavar='', type=argparse.FileType('r'), \ 37 | default=sys.stdin, help='dynamic GEXF input file') 38 | 39 | parser.add_argument('name', metavar='', \ 40 | help='name of top-level "RUN" node of the Neo4J representation') 41 | 42 | parser.add_argument('tstart', metavar='', type=int, \ 43 | help='start time for loading GEXF data') 44 | 45 | parser.add_argument('delta', metavar='', type=int, \ 46 | default=20, help='duration in seconds of time frames') 47 | 48 | parser.add_argument('neo4j', metavar='', \ 49 | default="http://localhost:7474/db/data/", help='URL of Neo4j REST endpoint') 50 | 51 | args = parser.parse_args() 52 | 53 | GEXF_FILE = args.gexf 54 | RUN_NAME = args.name 55 | START_TIME = args.tstart 56 | DELTAT = args.delta 57 | NEO4J_REST = args.neo4j 58 | 59 | # ----------------------------------------------------- 60 | 61 | gexf = xml.parse(GEXF_FILE).getroot() 62 | 63 | graph = gexf.find('{http://www.gexf.net/1.2draft}graph') 64 | if graph.get('mode') != "dynamic": 65 | sys.exit("GEXF file is not dynamic") 66 | if graph.get('timeformat') != "integer": 67 | sys.exit('GEXF file does not have an "integer" timeformat') 68 | 69 | def get_intervals(tstart, tstop): 70 | delta = (tstart - START_TIME) % DELTAT 71 | return [(t, t+DELTAT) for t in range(tstart-delta, tstop, DELTAT)] 72 | 73 | NODE_TIMELINE = {} 74 | nodes = graph.find('{http://www.gexf.net/1.2draft}nodes') 75 | 76 | for node in nodes.findall('{http://www.gexf.net/1.2draft}node'): 77 | node_id = int(node.get('id')) 78 | if not node_id in NODE_TIMELINE: 79 | NODE_TIMELINE[node_id] = set() 80 | for spell in node.findall('{http://www.gexf.net/1.2draft}spells/{http://www.gexf.net/1.2draft}spell'): 81 | t1, t2 = int(spell.get('start')), int(spell.get('end')) 82 | NODE_TIMELINE[node_id].update( get_intervals(t1, t2) ) 83 | 84 | EDGE_TIMELINE = {} 85 | edges = graph.find('{http://www.gexf.net/1.2draft}edges') 86 | 87 | for edge in edges.findall('{http://www.gexf.net/1.2draft}edge'): 88 | node1, node2 = int(edge.get('source')), int(edge.get('target')) 89 | if not (node1,node2) in EDGE_TIMELINE: 90 | EDGE_TIMELINE[(node1,node2)] = set() 91 | for spell in edge.findall('{http://www.gexf.net/1.2draft}spells/{http://www.gexf.net/1.2draft}spell'): 92 | t1, t2 = int(spell.get('start')), int(spell.get('end')) 93 | EDGE_TIMELINE[(node1,node2)].update( get_intervals(t1, t2) ) 94 | 95 | FRAMES = set() 96 | for interval_list in NODE_TIMELINE.values() + EDGE_TIMELINE.values(): 97 | FRAMES.update(interval_list) 98 | STOP_TIME = max( [ t2 for (t1,t2) in FRAMES] ) 99 | 100 | # ----------------------------------------------------- 101 | 102 | TLINE_DICT = {} 103 | 104 | def add_to_timeline(root_node, node, timestamp): 105 | (year, month, day, hour, minute, second) = time.localtime(timestamp)[:6] 106 | 107 | if year in TLINE_DICT: 108 | (root_node, tline) = TLINE_DICT[year] 109 | else: 110 | TLINE_DICT[year] = (gdb.node(type="TIMELINE"), {}) 111 | root_node.relationships.create("NEXT_LEVEL", TLINE_DICT[year][0], year=year) 112 | (root_node, tline) = TLINE_DICT[year] 113 | 114 | if month in tline: 115 | (root_node, tline) = tline[month] 116 | else: 117 | tline[month] = (gdb.node(type="TIMELINE"), {}) 118 | root_node.relationships.create("NEXT_LEVEL", tline[month][0], month=month) 119 | (root_node, tline) = tline[month] 120 | 121 | if day in tline: 122 | (root_node, tline) = tline[day] 123 | else: 124 | tline[day] = (gdb.node(type="TIMELINE"), {}) 125 | root_node.relationships.create("NEXT_LEVEL", tline[day][0], day=day) 126 | (root_node, tline) = tline[day] 127 | 128 | if hour in tline: 129 | (root_node, tline) = tline[hour] 130 | else: 131 | tline[hour] = (gdb.node(type="TIMELINE"), {}) 132 | root_node.relationships.create("NEXT_LEVEL", tline[hour][0], hour=hour) 133 | (root_node, tline) = tline[hour] 134 | 135 | root_node.relationships.create("TIMELINE_INSTANCE", node, timestamp=timestamp) 136 | 137 | node['year'] = year 138 | node['month'] = month 139 | node['day'] = day 140 | node['hour'] = hour 141 | node['minute'] = minute 142 | node['second'] = second 143 | 144 | # ----------------------------------------------------- 145 | 146 | gdb = GraphDatabase(NEO4J_REST) 147 | 148 | actorsidx = gdb.nodes.indexes.create(name="actors_%s" % RUN_NAME, type="fulltext") 149 | 150 | REF_NODE = gdb.node[0] 151 | RUN = gdb.node(name=RUN_NAME, type='RUN') 152 | REF_NODE.relationships.create("HAS_RUN", RUN) 153 | 154 | TLINE = gdb.node(name='TIMELINE', type='TIMELINE', start=START_TIME, stop=STOP_TIME) 155 | RUN.relationships.create("HAS_TIMELINE", TLINE) 156 | 157 | ACTOR_DICT = {} 158 | INTERACTION_DICT = {} 159 | 160 | frame_count = 0 161 | prev_frame = None 162 | 163 | actors = set() 164 | interactions = set() 165 | frame_actors = [] 166 | frame_interactions = [] 167 | 168 | tx = gdb.transaction() 169 | 170 | for frame_time in range(START_TIME, STOP_TIME, DELTAT): 171 | frame_count += 1 172 | if frame_count % 1000 == 0: 173 | tx.commit() 174 | tx = gdb.transaction() 175 | 176 | interval = (frame_time, frame_time+DELTAT) 177 | print '#%d' % frame_count, time.ctime(frame_time) 178 | 179 | frame = gdb.node(name='FRAME_%05d' % frame_count, type='FRAME', frame_id=frame_count, timestamp=frame_time, timestamp_end=frame_time+DELTAT, time=time.ctime(frame_time), length=DELTAT) 180 | RUN.relationships.create("RUN_FRAME", frame) 181 | add_to_timeline(TLINE, frame, frame_time) 182 | 183 | if frame_count == 1: 184 | RUN.relationships.create("RUN_FRAME_FIRST", frame) 185 | 186 | if prev_frame: 187 | prev_frame.relationships.create("FRAME_NEXT", frame) 188 | prev_frame = frame 189 | 190 | for actor_id in NODE_TIMELINE: 191 | if not interval in NODE_TIMELINE[actor_id]: 192 | continue 193 | actors.add(actor_id) 194 | 195 | frame_actors.append((frame, actor_id)) 196 | 197 | for (id1, id2) in EDGE_TIMELINE: 198 | if not interval in EDGE_TIMELINE[(id1,id2)]: 199 | continue 200 | 201 | if id1 > id2: 202 | (id1, id2) = (id2, id1) 203 | 204 | interactions.add((id1,id2)) 205 | 206 | frame_interactions.append((frame, (id1,id2))) 207 | 208 | tx.commit() 209 | 210 | with gdb.transaction(): 211 | print 'Adding %d ACTOR nodes' % len(actors) 212 | for actor_id in actors: 213 | actor = gdb.node(name='ACTOR_%04d' % actor_id, type='ACTOR', actor=actor_id) 214 | actorsidx.add('actor_id', actor_id, actor) 215 | ACTOR_DICT[actor_id] = actor 216 | RUN.relationships.create("RUN_ACTOR", actor) 217 | 218 | print 'Adding %d INTERACTION nodes' % len(interactions) 219 | for (id1,id2) in interactions: 220 | edge = gdb.node(name='INTERACTION_%04d_%04d' % (id1, id2), type='INTERACTION', actor1=id1, actor2=id2) 221 | INTERACTION_DICT[(id1,id2)] = edge 222 | actor1 = ACTOR_DICT[id1] 223 | actor2 = ACTOR_DICT[id2] 224 | edge.relationships.create("INTERACTION_ACTOR", actor1) 225 | edge.relationships.create("INTERACTION_ACTOR", actor2) 226 | RUN.relationships.create("RUN_INTERACTION", edge) 227 | 228 | tx = gdb.transaction(update=False) 229 | print 'Adding %d ACTOR relations to frames' % len(frame_actors) 230 | for i, (frame, actor_id) in enumerate(frame_actors): 231 | if (i+i) % 1000 == 0: 232 | sys.stdout.write('.') 233 | sys.stdout.flush() 234 | tx.commit() 235 | tx = gdb.transaction(update=False) 236 | frame.relationships.create("FRAME_ACTOR", ACTOR_DICT[actor_id]) 237 | tx.commit() 238 | print 239 | 240 | tx = gdb.transaction(update=False) 241 | print 'Adding %d INTERACTION relations to frames' % len(frame_interactions) 242 | for i, (frame, interaction) in enumerate(frame_interactions): 243 | if (i+1) % 1000 == 0: 244 | sys.stdout.write('.') 245 | sys.stdout.flush() 246 | tx.commit() 247 | tx = gdb.transaction(update=False) 248 | frame.relationships.create("FRAME_INTERACTION", INTERACTION_DICT[interaction], weight=1) 249 | tx.commit() 250 | print 251 | 252 | print 'Done.' 253 | 254 | --------------------------------------------------------------------------------