diff --git a/.gitignore b/.gitignore index 7a80ce9..7df9013 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.shell_history .classpath .project bin @@ -5,3 +6,9 @@ lib target .ivy .DS_Store +.ant-eclipse +.ant-eclipse +.settings +.idea +tools +koan.iml diff --git a/README.markdown b/README.markdown index 7d7902c..bb8801e 100644 --- a/README.markdown +++ b/README.markdown @@ -1,6 +1,8 @@ Neo4j Koans =========== +![dalek](https://github.com/jimwebber/neo4j-tutorial/raw/master/presentation/Intro%20to%20Neo4j.key/image54.png) + This set of Koans will provide practical guidance for getting to grips with graph data structures and operations using the Neo4j open source graph database. It's part of a more comprehensive tutorial being developed by the [authors](#authors) which will make its way into conference tutorials soon enough. Prerequisites @@ -8,11 +10,30 @@ Prerequisites You'll need to be familiar with the Java programming language, and it'd be helpful if you understood unit testing too. All the graph database knowledge you'll needed will be developed as part of completing the Koans. +Live Sessions +------------- + +This tutorial is presented at conferences around the world. If you're interested in participating in a class, then it'll be taught at: + +[QCon San Francisco](http://qconsf.com/sf2011/presentations/show_presentation.jsp?oid=3459) + +[Skillsmatter](http://skillsmatter.com/course/nosql/neo4j-tutorial) + +[Yow! Australia](http://www.yowconference.com.au/index.html) + Authors ------- -[Ian Robinson](http://iansrobinson.com) +[Glen Ford](http://usersource.net/), [@glen_ford](http://twitter.com/glen_ford) + +[Tobias Ivarsson](http://www.thobe.org/), [@thobe](http://twitter.com/thobe) + +Peter Neubauer, [@peterneubauer](http://twitter.com/peterneubauer) + +[Ian Robinson](http://iansrobinson.com), [@iansrobinson](http://twitter.com/iansrobinson) + +[Aleksa Vukotic](http://aleksavukotic.com), [@aleksavukotic](http://twitter.com/aleksavukotic) -[Jim Webber](http://jimwebber.org/) +[Jim Webber](http://jimwebber.org/), [@jimwebber](http://twitter.com/jimwebber) diff --git a/build.xml b/build.xml index 7f46572..737a3ba 100644 --- a/build.xml +++ b/build.xml @@ -1,39 +1,44 @@ - + - - + + + - - - - + - + - - - - - - - + + + - - + + + + - + + - - + + + + + + + + + + @@ -44,32 +49,50 @@ - + - - - - - - - + - - + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/presentation/.gitignore b/presentation/.gitignore new file mode 100644 index 0000000..895a94e --- /dev/null +++ b/presentation/.gitignore @@ -0,0 +1,3 @@ +Intro to Neo4j.key/thumbs +Intro to Neo4j.key/QuickLook + diff --git a/presentation/A Programmatic Introduction to Neo4j.pptx b/presentation/A Programmatic Introduction to Neo4j.pptx new file mode 100644 index 0000000..157b056 Binary files /dev/null and b/presentation/A Programmatic Introduction to Neo4j.pptx differ diff --git a/presentation/Intro to Neo4j.key/Contents/PkgInfo b/presentation/Intro to Neo4j.key/Contents/PkgInfo new file mode 100644 index 0000000..b0bc8e0 --- /dev/null +++ b/presentation/Intro to Neo4j.key/Contents/PkgInfo @@ -0,0 +1 @@ +???????? \ No newline at end of file diff --git a/presentation/Intro to Neo4j.key/buildVersionHistory.plist b/presentation/Intro to Neo4j.key/buildVersionHistory.plist new file mode 100644 index 0000000..f407c50 --- /dev/null +++ b/presentation/Intro to Neo4j.key/buildVersionHistory.plist @@ -0,0 +1,7 @@ + + + + + local build-Jul 1 2011 + + diff --git a/presentation/Intro to Neo4j.key/fault-tolerance.png b/presentation/Intro to Neo4j.key/fault-tolerance.png new file mode 100644 index 0000000..5e0b91e Binary files /dev/null and b/presentation/Intro to Neo4j.key/fault-tolerance.png differ diff --git a/presentation/Intro to Neo4j.key/image-1.png b/presentation/Intro to Neo4j.key/image-1.png new file mode 100644 index 0000000..cfbb2ad Binary files /dev/null and b/presentation/Intro to Neo4j.key/image-1.png differ diff --git a/presentation/Intro to Neo4j.key/image.png b/presentation/Intro to Neo4j.key/image.png new file mode 100644 index 0000000..7bcfeae Binary files /dev/null and b/presentation/Intro to Neo4j.key/image.png differ diff --git a/presentation/Intro to Neo4j.key/image0.png b/presentation/Intro to Neo4j.key/image0.png new file mode 100644 index 0000000..29f3c45 Binary files /dev/null and b/presentation/Intro to Neo4j.key/image0.png differ diff --git a/presentation/Intro to Neo4j.key/image19.png b/presentation/Intro to Neo4j.key/image19.png new file mode 100644 index 0000000..e9850b9 Binary files /dev/null and b/presentation/Intro to Neo4j.key/image19.png differ diff --git a/presentation/Intro to Neo4j.key/image2.png b/presentation/Intro to Neo4j.key/image2.png new file mode 100644 index 0000000..9ab4807 Binary files /dev/null and b/presentation/Intro to Neo4j.key/image2.png differ diff --git a/presentation/Intro to Neo4j.key/image20.png b/presentation/Intro to Neo4j.key/image20.png new file mode 100644 index 0000000..5f25130 Binary files /dev/null and b/presentation/Intro to Neo4j.key/image20.png differ diff --git a/presentation/Intro to Neo4j.key/image21.png b/presentation/Intro to Neo4j.key/image21.png new file mode 100644 index 0000000..4548c51 Binary files /dev/null and b/presentation/Intro to Neo4j.key/image21.png differ diff --git a/presentation/Intro to Neo4j.key/image22.png b/presentation/Intro to Neo4j.key/image22.png new file mode 100644 index 0000000..838087a Binary files /dev/null and b/presentation/Intro to Neo4j.key/image22.png differ diff --git a/presentation/Intro to Neo4j.key/image23.png b/presentation/Intro to Neo4j.key/image23.png new file mode 100644 index 0000000..9027142 Binary files /dev/null and b/presentation/Intro to Neo4j.key/image23.png differ diff --git a/presentation/Intro to Neo4j.key/image24.png b/presentation/Intro to Neo4j.key/image24.png new file mode 100644 index 0000000..d82d21d Binary files /dev/null and b/presentation/Intro to Neo4j.key/image24.png differ diff --git a/presentation/Intro to Neo4j.key/image25.png b/presentation/Intro to Neo4j.key/image25.png new file mode 100644 index 0000000..fc6c9fa Binary files /dev/null and b/presentation/Intro to Neo4j.key/image25.png differ diff --git a/presentation/Intro to Neo4j.key/image26.png b/presentation/Intro to Neo4j.key/image26.png new file mode 100644 index 0000000..0e31e29 Binary files /dev/null and b/presentation/Intro to Neo4j.key/image26.png differ diff --git a/presentation/Intro to Neo4j.key/image27.png b/presentation/Intro to Neo4j.key/image27.png new file mode 100644 index 0000000..b919b54 Binary files /dev/null and b/presentation/Intro to Neo4j.key/image27.png differ diff --git a/presentation/Intro to Neo4j.key/image28.png b/presentation/Intro to Neo4j.key/image28.png new file mode 100644 index 0000000..3c36530 Binary files /dev/null and b/presentation/Intro to Neo4j.key/image28.png differ diff --git a/presentation/Intro to Neo4j.key/image29.png b/presentation/Intro to Neo4j.key/image29.png new file mode 100644 index 0000000..531e0b8 Binary files /dev/null and b/presentation/Intro to Neo4j.key/image29.png differ diff --git a/presentation/Intro to Neo4j.key/image30.png b/presentation/Intro to Neo4j.key/image30.png new file mode 100644 index 0000000..cb902a4 Binary files /dev/null and b/presentation/Intro to Neo4j.key/image30.png differ diff --git a/presentation/Intro to Neo4j.key/image31.png b/presentation/Intro to Neo4j.key/image31.png new file mode 100644 index 0000000..bc368dc Binary files /dev/null and b/presentation/Intro to Neo4j.key/image31.png differ diff --git a/presentation/Intro to Neo4j.key/image32.png b/presentation/Intro to Neo4j.key/image32.png new file mode 100644 index 0000000..dc284a8 Binary files /dev/null and b/presentation/Intro to Neo4j.key/image32.png differ diff --git a/presentation/Intro to Neo4j.key/image33.png b/presentation/Intro to Neo4j.key/image33.png new file mode 100644 index 0000000..b0daf48 Binary files /dev/null and b/presentation/Intro to Neo4j.key/image33.png differ diff --git a/presentation/Intro to Neo4j.key/image34.png b/presentation/Intro to Neo4j.key/image34.png new file mode 100644 index 0000000..4d816bf Binary files /dev/null and b/presentation/Intro to Neo4j.key/image34.png differ diff --git a/presentation/Intro to Neo4j.key/image35.png b/presentation/Intro to Neo4j.key/image35.png new file mode 100644 index 0000000..c0bd269 Binary files /dev/null and b/presentation/Intro to Neo4j.key/image35.png differ diff --git a/presentation/Intro to Neo4j.key/image36.png b/presentation/Intro to Neo4j.key/image36.png new file mode 100644 index 0000000..0b471cd Binary files /dev/null and b/presentation/Intro to Neo4j.key/image36.png differ diff --git a/presentation/Intro to Neo4j.key/image37.png b/presentation/Intro to Neo4j.key/image37.png new file mode 100644 index 0000000..cc3d845 Binary files /dev/null and b/presentation/Intro to Neo4j.key/image37.png differ diff --git a/presentation/Intro to Neo4j.key/image38.png b/presentation/Intro to Neo4j.key/image38.png new file mode 100644 index 0000000..852ac0f Binary files /dev/null and b/presentation/Intro to Neo4j.key/image38.png differ diff --git a/presentation/Intro to Neo4j.key/image39.png b/presentation/Intro to Neo4j.key/image39.png new file mode 100644 index 0000000..e0a7601 Binary files /dev/null and b/presentation/Intro to Neo4j.key/image39.png differ diff --git a/presentation/Intro to Neo4j.key/image40.png b/presentation/Intro to Neo4j.key/image40.png new file mode 100644 index 0000000..a9f3cb7 Binary files /dev/null and b/presentation/Intro to Neo4j.key/image40.png differ diff --git a/presentation/Intro to Neo4j.key/image41.png b/presentation/Intro to Neo4j.key/image41.png new file mode 100644 index 0000000..56002dc Binary files /dev/null and b/presentation/Intro to Neo4j.key/image41.png differ diff --git a/presentation/Intro to Neo4j.key/image45.png b/presentation/Intro to Neo4j.key/image45.png new file mode 100644 index 0000000..de41214 Binary files /dev/null and b/presentation/Intro to Neo4j.key/image45.png differ diff --git a/presentation/Intro to Neo4j.key/image46.png b/presentation/Intro to Neo4j.key/image46.png new file mode 100644 index 0000000..a638982 Binary files /dev/null and b/presentation/Intro to Neo4j.key/image46.png differ diff --git a/presentation/Intro to Neo4j.key/image47.png b/presentation/Intro to Neo4j.key/image47.png new file mode 100644 index 0000000..eee840d Binary files /dev/null and b/presentation/Intro to Neo4j.key/image47.png differ diff --git a/presentation/Intro to Neo4j.key/image48.png b/presentation/Intro to Neo4j.key/image48.png new file mode 100644 index 0000000..6e7bbed Binary files /dev/null and b/presentation/Intro to Neo4j.key/image48.png differ diff --git a/presentation/Intro to Neo4j.key/image49.png b/presentation/Intro to Neo4j.key/image49.png new file mode 100644 index 0000000..125c33a Binary files /dev/null and b/presentation/Intro to Neo4j.key/image49.png differ diff --git a/presentation/Intro to Neo4j.key/image50.png b/presentation/Intro to Neo4j.key/image50.png new file mode 100644 index 0000000..bc47c52 Binary files /dev/null and b/presentation/Intro to Neo4j.key/image50.png differ diff --git a/presentation/Intro to Neo4j.key/image51.png b/presentation/Intro to Neo4j.key/image51.png new file mode 100644 index 0000000..4f83ac3 Binary files /dev/null and b/presentation/Intro to Neo4j.key/image51.png differ diff --git a/presentation/Intro to Neo4j.key/image52.png b/presentation/Intro to Neo4j.key/image52.png new file mode 100644 index 0000000..fc5a1ca Binary files /dev/null and b/presentation/Intro to Neo4j.key/image52.png differ diff --git a/presentation/Intro to Neo4j.key/image53.png b/presentation/Intro to Neo4j.key/image53.png new file mode 100644 index 0000000..ab9276b Binary files /dev/null and b/presentation/Intro to Neo4j.key/image53.png differ diff --git a/presentation/Intro to Neo4j.key/image54.png b/presentation/Intro to Neo4j.key/image54.png new file mode 100644 index 0000000..25f2be7 Binary files /dev/null and b/presentation/Intro to Neo4j.key/image54.png differ diff --git a/presentation/Intro to Neo4j.key/image55.png b/presentation/Intro to Neo4j.key/image55.png new file mode 100644 index 0000000..11bf3d0 Binary files /dev/null and b/presentation/Intro to Neo4j.key/image55.png differ diff --git a/presentation/Intro to Neo4j.key/image56.png b/presentation/Intro to Neo4j.key/image56.png new file mode 100644 index 0000000..9d4ad3c Binary files /dev/null and b/presentation/Intro to Neo4j.key/image56.png differ diff --git a/presentation/Intro to Neo4j.key/image57.png b/presentation/Intro to Neo4j.key/image57.png new file mode 100644 index 0000000..641deef Binary files /dev/null and b/presentation/Intro to Neo4j.key/image57.png differ diff --git a/presentation/Intro to Neo4j.key/image58.png b/presentation/Intro to Neo4j.key/image58.png new file mode 100644 index 0000000..fa03177 Binary files /dev/null and b/presentation/Intro to Neo4j.key/image58.png differ diff --git a/presentation/Intro to Neo4j.key/image59.png b/presentation/Intro to Neo4j.key/image59.png new file mode 100644 index 0000000..04d97c5 Binary files /dev/null and b/presentation/Intro to Neo4j.key/image59.png differ diff --git a/presentation/Intro to Neo4j.key/image60.png b/presentation/Intro to Neo4j.key/image60.png new file mode 100644 index 0000000..dd975e2 Binary files /dev/null and b/presentation/Intro to Neo4j.key/image60.png differ diff --git a/presentation/Intro to Neo4j.key/image61.png b/presentation/Intro to Neo4j.key/image61.png new file mode 100644 index 0000000..f5a9aa7 Binary files /dev/null and b/presentation/Intro to Neo4j.key/image61.png differ diff --git a/presentation/Intro to Neo4j.key/image62.png b/presentation/Intro to Neo4j.key/image62.png new file mode 100644 index 0000000..3e82a5a Binary files /dev/null and b/presentation/Intro to Neo4j.key/image62.png differ diff --git a/presentation/Intro to Neo4j.key/image63.png b/presentation/Intro to Neo4j.key/image63.png new file mode 100644 index 0000000..3abd30a Binary files /dev/null and b/presentation/Intro to Neo4j.key/image63.png differ diff --git a/presentation/Intro to Neo4j.key/image64.png b/presentation/Intro to Neo4j.key/image64.png new file mode 100644 index 0000000..6340cc9 Binary files /dev/null and b/presentation/Intro to Neo4j.key/image64.png differ diff --git a/presentation/Intro to Neo4j.key/image67.png b/presentation/Intro to Neo4j.key/image67.png new file mode 100644 index 0000000..3bd19d3 Binary files /dev/null and b/presentation/Intro to Neo4j.key/image67.png differ diff --git a/presentation/Intro to Neo4j.key/image68.png b/presentation/Intro to Neo4j.key/image68.png new file mode 100644 index 0000000..8f64303 Binary files /dev/null and b/presentation/Intro to Neo4j.key/image68.png differ diff --git a/presentation/Intro to Neo4j.key/image69.png b/presentation/Intro to Neo4j.key/image69.png new file mode 100644 index 0000000..643d78c Binary files /dev/null and b/presentation/Intro to Neo4j.key/image69.png differ diff --git a/presentation/Intro to Neo4j.key/image70.png b/presentation/Intro to Neo4j.key/image70.png new file mode 100644 index 0000000..2d5b298 Binary files /dev/null and b/presentation/Intro to Neo4j.key/image70.png differ diff --git a/presentation/Intro to Neo4j.key/image71.png b/presentation/Intro to Neo4j.key/image71.png new file mode 100644 index 0000000..c1a25d0 Binary files /dev/null and b/presentation/Intro to Neo4j.key/image71.png differ diff --git a/presentation/Intro to Neo4j.key/index.apxl.gz b/presentation/Intro to Neo4j.key/index.apxl.gz new file mode 100644 index 0000000..43708bd Binary files /dev/null and b/presentation/Intro to Neo4j.key/index.apxl.gz differ diff --git a/presentation/Intro to Neo4j.key/neo-small.mov b/presentation/Intro to Neo4j.key/neo-small.mov new file mode 100644 index 0000000..33335e0 Binary files /dev/null and b/presentation/Intro to Neo4j.key/neo-small.mov differ diff --git a/presentation/Intro to Neo4j.key/neo-small.mp4 b/presentation/Intro to Neo4j.key/neo-small.mp4 new file mode 100644 index 0000000..56658a7 Binary files /dev/null and b/presentation/Intro to Neo4j.key/neo-small.mp4 differ diff --git a/presentation/Intro to Neo4j.key/neo-small.png b/presentation/Intro to Neo4j.key/neo-small.png new file mode 100644 index 0000000..e08538b Binary files /dev/null and b/presentation/Intro to Neo4j.key/neo-small.png differ diff --git a/settings/download-neoclipse.xml b/settings/download-neoclipse.xml new file mode 100644 index 0000000..02d50b7 --- /dev/null +++ b/settings/download-neoclipse.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/settings/install-ant-eclipse.xml b/settings/install-ant-eclipse.xml new file mode 100644 index 0000000..8b4fb37 --- /dev/null +++ b/settings/install-ant-eclipse.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/settings/install-ivy.xml b/settings/install-ivy.xml index fd3f859..9f9c3ef 100644 --- a/settings/install-ivy.xml +++ b/settings/install-ivy.xml @@ -1,29 +1,37 @@ - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/settings/ivy.xml b/settings/ivy.xml index 0e0fb98..a21efd4 100644 --- a/settings/ivy.xml +++ b/settings/ivy.xml @@ -1,8 +1,34 @@ - + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/settings/ivysettings.xml b/settings/ivysettings.xml index 6721609..84319ec 100644 --- a/settings/ivysettings.xml +++ b/settings/ivysettings.xml @@ -1,16 +1,24 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/settings/path.xml b/settings/path.xml index 7faa397..81febb9 100644 --- a/settings/path.xml +++ b/settings/path.xml @@ -1,38 +1,52 @@ - - - - - - - - - - - - - - + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + - + + + + + + + + + + + + diff --git a/src/koan/java/org/neo4j/tutorial/Koan01.java b/src/koan/java/org/neo4j/tutorial/Koan01.java new file mode 100644 index 0000000..1f44509 --- /dev/null +++ b/src/koan/java/org/neo4j/tutorial/Koan01.java @@ -0,0 +1,20 @@ +package org.neo4j.tutorial; + +import static org.junit.Assert.assertNotNull; + +import org.junit.Test; + +/** + * This first Koan will introduce you to the tool support available for Neo4j. + * It will also introduce you to the Doctor Who universe. + */ +public class Koan01 +{ + @Test + public void justEmitsThePathToTheDatabase() throws Exception + { + EmbeddedDoctorWhoUniverse universe = new EmbeddedDoctorWhoUniverse( new DoctorWhoUniverseGenerator() ); + assertNotNull( universe.getDatabase() ); + universe.stop(); + } +} diff --git a/src/koan/java/org/neo4j/tutorial/Koan02.java b/src/koan/java/org/neo4j/tutorial/Koan02.java new file mode 100644 index 0000000..31cc6ed --- /dev/null +++ b/src/koan/java/org/neo4j/tutorial/Koan02.java @@ -0,0 +1,257 @@ +package org.neo4j.tutorial; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.neo4j.graphdb.Direction; +import org.neo4j.graphdb.DynamicRelationshipType; +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.NotFoundException; +import org.neo4j.graphdb.Relationship; +import org.neo4j.graphdb.Transaction; + +/** + * This first programming Koan will get you started with the basics of managing + * nodes and relationships with the core API. It will also introduce you to the + * earliest Doctor Who storylines! + */ +public class Koan02 +{ + + private static GraphDatabaseService db; + private static DatabaseHelper databaseHelper; + + @BeforeClass + public static void createADatabase() + { + db = DatabaseHelper.createDatabase(); + databaseHelper = new DatabaseHelper( db ); + } + + @AfterClass + public static void closeTheDatabase() + { + db.shutdown(); + } + + @Test + public void shouldCreateANodeInTheDatabase() + { + Node node = null; + + // YOUR CODE GOES HERE + // SNIPPET_START + + Transaction tx = db.beginTx(); + try + { + node = db.createNode(); + tx.success(); + } finally + { + tx.finish(); + } + + // SNIPPET_END + + assertTrue( databaseHelper.nodeExistsInDatabase( node ) ); + } + + @Test + public void shouldCreateSomePropertiesOnANode() + { + Node theDoctor = null; + + // YOUR CODE GOES HERE + // SNIPPET_START + + Transaction tx = db.beginTx(); + try + { + theDoctor = db.createNode(); + theDoctor.setProperty( "firstname", "William" ); + theDoctor.setProperty( "lastname", "Hartnell" ); + tx.success(); + } finally + { + tx.finish(); + } + + // SNIPPET_END + + assertTrue( databaseHelper.nodeExistsInDatabase( theDoctor ) ); + + Node storedNode = db.getNodeById( theDoctor.getId() ); + assertEquals( "William", storedNode.getProperty( "firstname" ) ); + assertEquals( "Hartnell", storedNode.getProperty( "lastname" ) ); + } + + @Test + public void shouldRelateTwoNodes() + { + Node theDoctor = null; + Node susan = null; + Relationship companionRelationship = null; + + // YOUR CODE GOES HERE + // SNIPPET_START + + Transaction tx = db.beginTx(); + try + { + theDoctor = db.createNode(); + theDoctor.setProperty( "character", "The Doctor" ); + + susan = db.createNode(); + susan.setProperty( "firstname", "Susan" ); + susan.setProperty( "lastname", "Campbell" ); + + companionRelationship = susan.createRelationshipTo( theDoctor, + DynamicRelationshipType.withName( "COMPANION_OF" ) ); + + tx.success(); + } finally + { + tx.finish(); + } + + // SNIPPET_END + + Relationship storedCompanionRelationship = db.getRelationshipById( companionRelationship.getId() ); + assertNotNull( storedCompanionRelationship ); + assertEquals( susan, storedCompanionRelationship.getStartNode() ); + assertEquals( theDoctor, storedCompanionRelationship.getEndNode() ); + } + + @Test + public void shouldRemoveStarTrekInformation() + { + /* Captain Kirk has no business being in our database, so set phasers to kill */ + Node captainKirk = createPollutedDatabaseContainingStarTrekReferences(); + + // YOUR CODE GOES HERE + // SNIPPET_START + + Transaction tx = db.beginTx(); + try + { + + // This is the tricky part, you have to remove the active + // relationships before you can remove a node + Iterable relationships = captainKirk.getRelationships(); + for ( Relationship r : relationships ) + { + r.delete(); + } + + captainKirk.delete(); + + tx.success(); + } finally + { + tx.finish(); + } + + // SNIPPET_END + + try + { + captainKirk.hasProperty( "character" ); + fail(); + } catch ( NotFoundException nfe ) + { + // If the exception is thrown, we've removed Captain Kirk from the + // database + assertNotNull( nfe ); + } + } + + @Test + public void shouldRemoveIncorrectEnemyOfRelationshipBetweenSusanAndTheDoctor() + { + Node susan = createInaccurateDatabaseWhereSusanIsEnemyOfTheDoctor(); + + // YOUR CODE GOES HERE + // SNIPPET_START + + Transaction tx = db.beginTx(); + try + { + + Iterable relationships = susan.getRelationships( DoctorWhoRelationships.ENEMY_OF, + Direction.OUTGOING ); + for ( Relationship r : relationships ) + { + Node n = r.getEndNode(); + if ( n.hasProperty( "character" ) && n.getProperty( "character" ) + .equals( "The Doctor" ) ) + { + r.delete(); + } + } + + tx.success(); + } finally + { + tx.finish(); + } + + // SNIPPET_END + assertEquals( 1, databaseHelper.countRelationships( susan.getRelationships() ) ); + } + + private Node createInaccurateDatabaseWhereSusanIsEnemyOfTheDoctor() + { + Transaction tx = db.beginTx(); + Node susan = null; + try + { + Node theDoctor = db.createNode(); + theDoctor.setProperty( "character", "The Doctor" ); + + susan = db.createNode(); + susan.setProperty( "firstname", "Susan" ); + susan.setProperty( "lastname", "Campbell" ); + + susan.createRelationshipTo( theDoctor, DynamicRelationshipType.withName( "COMPANION_OF" ) ); + susan.createRelationshipTo( theDoctor, DynamicRelationshipType.withName( "ENEMY_OF" ) ); + + tx.success(); + return susan; + } finally + { + tx.finish(); + } + + } + + private Node createPollutedDatabaseContainingStarTrekReferences() + { + Transaction tx = db.beginTx(); + Node captainKirk = null; + try + { + Node theDoctor = db.createNode(); + theDoctor.setProperty( "character", "The Doctor" ); + + captainKirk = db.createNode(); + captainKirk.setProperty( "firstname", "James" ); + captainKirk.setProperty( "initial", "T" ); + captainKirk.setProperty( "lastname", "Kirk" ); + + captainKirk.createRelationshipTo( theDoctor, DynamicRelationshipType.withName( "COMPANION_OF" ) ); + + tx.success(); + return captainKirk; + } finally + { + tx.finish(); + } + } +} diff --git a/src/koan/java/org/neo4j/tutorial/Koan03.java b/src/koan/java/org/neo4j/tutorial/Koan03.java new file mode 100644 index 0000000..ece732b --- /dev/null +++ b/src/koan/java/org/neo4j/tutorial/Koan03.java @@ -0,0 +1,180 @@ +package org.neo4j.tutorial; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.neo4j.tutorial.matchers.ContainsOnlySpecificSpecies.containsOnlySpecies; +import static org.neo4j.tutorial.matchers.ContainsSpecificCompanions.contains; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.NotFoundException; +import org.neo4j.graphdb.Relationship; +import org.neo4j.graphdb.Transaction; +import org.neo4j.graphdb.index.Index; +import org.neo4j.graphdb.index.IndexHits; + +/** + * This Koan will introduce indexing based on the built-in index framework based + * on Lucene. It'll give you a feeling for the wealth of bad guys the Doctor has + * faced. + */ +public class Koan03 +{ + + private static EmbeddedDoctorWhoUniverse universe; + + @BeforeClass + public static void createDatabase() throws Exception + { + universe = new EmbeddedDoctorWhoUniverse( new DoctorWhoUniverseGenerator() ); + } + + @AfterClass + public static void closeTheDatabase() + { + universe.stop(); + } + + @Test + public void shouldRetrieveCharactersIndexFromTheDatabase() + { + Index characters = null; + + // YOUR CODE GOES HERE + // SNIPPET_START + + characters = universe.getDatabase() + .index() + .forNodes( "characters" ); + + // SNIPPET_END + + assertNotNull( characters ); + assertThat( + characters, + contains( "Master", "River Song", "Rose Tyler", "Adam Mitchell", "Jack Harkness", "Mickey Smith", + "Donna Noble", "Martha Jones" ) ); + } + + @Test + public void addingToAnIndexShouldBeHandledAsAMutatingOperation() + { + GraphDatabaseService db = universe.getDatabase(); + Node abigailPettigrew = createAbigailPettigrew( db ); + + assertNull( db.index() + .forNodes( "characters" ) + .get( "character", "Abigail Pettigrew" ) + .getSingle() ); + + // YOUR CODE GOES HERE + // SNIPPET_START + + Transaction transaction = db.beginTx(); + try + { + db.index() + .forNodes( "characters" ) + .add( abigailPettigrew, "character", abigailPettigrew.getProperty( "character" ) ); + transaction.success(); + } finally + { + transaction.finish(); + } + + // SNIPPET_END + + assertNotNull( db.index() + .forNodes( "characters" ) + .get( "character", "Abigail Pettigrew" ) + .getSingle() ); + } + + @Test + public void shouldFindSpeciesBeginningWithCapitalLetterSAndEndingWithTheLetterNUsingLuceneQuery() throws Exception + { + IndexHits species = null; + + // YOUR CODE GOES HERE + // SNIPPET_START + + species = universe.getDatabase() + .index() + .forNodes( "species" ) + .query( "species", "S*n" ); + + // SNIPPET_END + + assertThat( species, containsOnlySpecies( "Silurian", "Slitheen", "Sontaran", "Skarasen" ) ); + } + + /** + * In this example, it's more important to understand what you *don't* have + * to do, rather than the work you explicitly have to do. Sometimes indexes + * just do the right thing... + */ + @Test + public void shouldEnsureDatabaseAndIndexInSyncWhenCyberleaderIsDeleted() throws Exception + { + GraphDatabaseService db = universe.getDatabase(); + Node cyberleader = retriveCyberleaderFromIndex( db ); + + // YOUR CODE GOES HERE + // SNIPPET_START + + Transaction tx = db.beginTx(); + try + { + for ( Relationship rel : cyberleader.getRelationships() ) + { + rel.delete(); + } + cyberleader.delete(); + tx.success(); + } finally + { + tx.finish(); + } + + // SNIPPET_END + + assertNull( "Cyberleader has not been deleted from the characters index.", retriveCyberleaderFromIndex( db ) ); + + try + { + db.getNodeById( cyberleader.getId() ); + fail( "Cyberleader has not been deleted from the database." ); + } catch ( NotFoundException nfe ) + { + } + } + + private Node retriveCyberleaderFromIndex( GraphDatabaseService db ) + { + return db.index() + .forNodes( "characters" ) + .get( "character", "Cyberleader" ) + .getSingle(); + } + + private Node createAbigailPettigrew( GraphDatabaseService db ) + { + Node abigailPettigrew; + Transaction tx = db.beginTx(); + try + { + abigailPettigrew = db.createNode(); + abigailPettigrew.setProperty( "character", "Abigail Pettigrew" ); + tx.success(); + } finally + { + tx.finish(); + } + return abigailPettigrew; + } +} diff --git a/src/koan/java/org/neo4j/tutorial/Koan04.java b/src/koan/java/org/neo4j/tutorial/Koan04.java new file mode 100644 index 0000000..093ec0f --- /dev/null +++ b/src/koan/java/org/neo4j/tutorial/Koan04.java @@ -0,0 +1,95 @@ +package org.neo4j.tutorial; + +import static org.junit.Assert.assertThat; +import static org.neo4j.tutorial.matchers.CharacterAutoIndexContainsSpecificCharacters.containsSpecificCharacters; + +import java.util.HashSet; +import java.util.Set; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Transaction; +import org.neo4j.graphdb.index.AutoIndexer; +import org.neo4j.graphdb.index.Index; +import org.neo4j.graphdb.index.IndexHits; + +/** + * After having done the hard work of managing an index for ourselves in the + * previous Koan, this Koan will introduce auto-indexing which, in exchange for + * following some conventions, will handle the lifefcycle of nodes and + * relationships in the indexes automatically. + */ +public class Koan04 +{ + + private static EmbeddedDoctorWhoUniverse universe; + + @BeforeClass + public static void createDatabase() throws Exception + { + universe = new EmbeddedDoctorWhoUniverse( new DoctorWhoUniverseGenerator() ); + } + + @AfterClass + public static void closeTheDatabase() + { + universe.stop(); + } + + @Test + public void shouldCreateAnAutoIndexForAllTheCharacters() + { + + Set allCharacterNames = getAllCharacterNames(); + AutoIndexer charactersAutoIndex = null; + + // YOUR CODE GOES HERE + // SNIPPET_START + + charactersAutoIndex = universe.getDatabase() + .index() + .getNodeAutoIndexer(); + charactersAutoIndex.startAutoIndexingProperty( "character-name" ); + charactersAutoIndex.setEnabled( true ); + + // SNIPPET_END + + Transaction tx = universe.getDatabase() + .beginTx(); + + try + { + for ( String characterName : allCharacterNames ) + { + Node n = universe.getDatabase() + .createNode(); + n.setProperty( "character-name", characterName ); + } + tx.success(); + } finally + { + tx.finish(); + } + + assertThat( charactersAutoIndex, containsSpecificCharacters( allCharacterNames ) ); + } + + private Set getAllCharacterNames() + { + Index characters = universe.getDatabase() + .index() + .forNodes( "characters" ); + IndexHits results = characters.query( "character", "*" ); + + HashSet characterNames = new HashSet(); + + for ( Node character : results ) + { + characterNames.add( (String) character.getProperty( "character" ) ); + } + + return characterNames; + } +} diff --git a/src/koan/java/org/neo4j/tutorial/Koan05.java b/src/koan/java/org/neo4j/tutorial/Koan05.java new file mode 100644 index 0000000..0001219 --- /dev/null +++ b/src/koan/java/org/neo4j/tutorial/Koan05.java @@ -0,0 +1,147 @@ +package org.neo4j.tutorial; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.neo4j.tutorial.matchers.ContainsOnlyHumanCompanions.containsOnlyHumanCompanions; +import static org.neo4j.tutorial.matchers.ContainsOnlySpecificTitles.containsOnlyTitles; + +import java.util.HashSet; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.neo4j.graphdb.Direction; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Relationship; +import org.neo4j.graphdb.index.Index; + +/** + * In this Koan we start to mix indexing and core API to perform more targeted + * graph operations. We'll mix indexes and core graph operations to explore the + * Doctor's universe. + */ +public class Koan05 +{ + + private static EmbeddedDoctorWhoUniverse universe; + + @BeforeClass + public static void createDatabase() throws Exception + { + universe = new EmbeddedDoctorWhoUniverse( new DoctorWhoUniverseGenerator() ); + } + + @AfterClass + public static void closeTheDatabase() + { + universe.stop(); + } + + @Test + public void shouldCountTheNumberOfDoctorsRegenerations() + { + + Index actorsIndex = universe.getDatabase() + .index() + .forNodes( "actors" ); + int numberOfRegenerations = 1; + + // YOUR CODE GOES HERE + // SNIPPET_START + Node firstDoctor = actorsIndex.get( "actor", "William Hartnell" ) + .getSingle(); + + Relationship regeneratedTo = firstDoctor.getSingleRelationship( DoctorWhoRelationships.REGENERATED_TO, + Direction.OUTGOING ); + + while ( regeneratedTo != null ) + { + numberOfRegenerations++; + regeneratedTo = regeneratedTo.getEndNode() + .getSingleRelationship( DoctorWhoRelationships.REGENERATED_TO, Direction.OUTGOING ); + } + + // SNIPPET_END + + assertEquals( 11, numberOfRegenerations ); + } + + @Test + public void shouldFindHumanCompanionsUsingCoreApi() + { + HashSet humanCompanions = new HashSet(); + + // YOUR CODE GOES HERE + // SNIPPET_START + + Node human = universe.getDatabase() + .index() + .forNodes( "species" ) + .get( "species", "Human" ) + .getSingle(); + + Iterable relationships = universe.theDoctor() + .getRelationships( Direction.INCOMING, DoctorWhoRelationships.COMPANION_OF ); + for ( Relationship rel : relationships ) + { + Node companionNode = rel.getStartNode(); + if ( companionNode.hasRelationship( Direction.OUTGOING, DoctorWhoRelationships.IS_A ) ) + { + Relationship singleRelationship = companionNode.getSingleRelationship( DoctorWhoRelationships.IS_A, + Direction.OUTGOING ); + Node endNode = singleRelationship.getEndNode(); + if ( endNode.equals( human ) ) + { + humanCompanions.add( companionNode ); + } + } + } + + // SNIPPET_END + + int numberOfKnownHumanCompanions = 36; + assertEquals( numberOfKnownHumanCompanions, humanCompanions.size() ); + assertThat( humanCompanions, containsOnlyHumanCompanions() ); + } + + @Test + public void shouldFindAllEpisodesWhereRoseTylerFoughtTheDaleks() + { + Index friendliesIndex = universe.getDatabase() + .index() + .forNodes( "characters" ); + Index speciesIndex = universe.getDatabase() + .index() + .forNodes( "species" ); + HashSet episodesWhereRoseFightsTheDaleks = new HashSet(); + + // YOUR CODE GOES HERE + // SNIPPET_START + + Node roseTyler = friendliesIndex.get( "character", "Rose Tyler" ) + .getSingle(); + Node daleks = speciesIndex.get( "species", "Dalek" ) + .getSingle(); + + for ( Relationship r1 : roseTyler.getRelationships( DoctorWhoRelationships.APPEARED_IN, Direction.OUTGOING ) ) + { + Node episode = r1.getEndNode(); + + for ( Relationship r2 : episode.getRelationships( DoctorWhoRelationships.APPEARED_IN, Direction.INCOMING ) ) + { + if ( r2.getStartNode() + .equals( daleks ) ) + { + episodesWhereRoseFightsTheDaleks.add( episode ); + } + } + } + + // SNIPPET_END + + assertThat( + episodesWhereRoseFightsTheDaleks, + containsOnlyTitles( "Army of Ghosts", "The Stolen Earth", "Doomsday", "Journey's End", "Bad Wolf", + "The Parting of the Ways", "Dalek" ) ); + } +} diff --git a/src/koan/java/org/neo4j/tutorial/Koan06.java b/src/koan/java/org/neo4j/tutorial/Koan06.java new file mode 100644 index 0000000..3ac64f6 --- /dev/null +++ b/src/koan/java/org/neo4j/tutorial/Koan06.java @@ -0,0 +1,158 @@ +package org.neo4j.tutorial; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.neo4j.graphdb.Direction; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Relationship; +import org.neo4j.graphdb.ReturnableEvaluator; +import org.neo4j.graphdb.StopEvaluator; +import org.neo4j.graphdb.TraversalPosition; +import org.neo4j.graphdb.Traverser; +import org.neo4j.graphdb.Traverser.Order; + +/** + * In this Koan we start using the simple traversal framework to find + * interesting information from the graph. + */ +public class Koan06 +{ + + private static EmbeddedDoctorWhoUniverse universe; + + @BeforeClass + public static void createDatabase() throws Exception + { + universe = new EmbeddedDoctorWhoUniverse( new DoctorWhoUniverseGenerator() ); + } + + @AfterClass + public static void closeTheDatabase() + { + universe.stop(); + } + + @Test + public void shouldFindAllCompanions() + { + Node theDoctor = universe.theDoctor(); + Traverser t = null; + + // YOUR CODE GOES HERE + // SNIPPET_START + + t = theDoctor.traverse( Order.DEPTH_FIRST, StopEvaluator.DEPTH_ONE, ReturnableEvaluator.ALL_BUT_START_NODE, + DoctorWhoRelationships.COMPANION_OF, Direction.INCOMING ); + + // SNIPPET_END + + Collection foundCompanions = t.getAllNodes(); + + int knownNumberOfCompanions = 45; + assertEquals( knownNumberOfCompanions, foundCompanions.size() ); + } + + @Test + public void shouldFindAllDalekProps() + { + Node theDaleks = universe.getDatabase() + .index() + .forNodes( "species" ) + .get( "species", "Dalek" ) + .getSingle(); + Traverser t = null; + + // YOUR CODE GOES HERE + // SNIPPET_START + + t = theDaleks.traverse( Order.DEPTH_FIRST, StopEvaluator.END_OF_GRAPH, new ReturnableEvaluator() + { + public boolean isReturnableNode( TraversalPosition currentPos ) + { + return currentPos.currentNode() + .hasProperty( "prop" ); + } + }, DoctorWhoRelationships.APPEARED_IN, Direction.BOTH, DoctorWhoRelationships.USED_IN, Direction.INCOMING, + DoctorWhoRelationships.MEMBER_OF, Direction.INCOMING ); + + // SNIPPET_END + + assertCollectionContainsAllDalekProps( t.getAllNodes() ); + } + + private void assertCollectionContainsAllDalekProps( Collection nodes ) + { + String[] dalekProps = new String[]{"Dalek One-7", "Imperial 4", "Imperial 3", "Imperial 2", "Imperial 1", + "Supreme Dalek", "Remembrance 3", "Remembrance 2", "Remembrance 1", "Dalek V-VI", "Goon IV", "Goon II", + "Goon I", "Dalek Six-5", "Dalek Seven-2", "Dalek V-5", "Dalek Seven-V", "Dalek Six-Ex", + "Dalek Seven-8", "Dalek 8", "Dalek 7", "Dalek Five-6", "Dalek Two-1", "Dalek 2", "Dalek 1", "Dalek 6", + "Dalek 5", "Dalek 4", "Dalek 3", "Dalek IV-Ex", "Dalek Seven-II", "Necros 3", "Necros 2", "Necros 1", + "Goon III", "Goon VII", "Goon VI", "Goon V", "Gold Movie Dalek", "Dalek Six-7", "Dalek One-5"}; + + List propList = new ArrayList(); + for ( Node n : nodes ) + { + propList.add( n.getProperty( "prop" ) + .toString() ); + } + + assertEquals( dalekProps.length, propList.size() ); + for ( String prop : dalekProps ) + { + assertTrue( propList.contains( prop ) ); + } + } + + @Test + public void shouldFindAllTheEpisodesTheMasterAndDavidTennantWereInTogether() + { + Node theMaster = universe.getDatabase() + .index() + .forNodes( "characters" ) + .get( "character", "Master" ) + .getSingle(); + Traverser t = null; + + // YOUR CODE GOES HERE + // SNIPPET_START + + t = theMaster.traverse( Order.DEPTH_FIRST, StopEvaluator.END_OF_GRAPH, new ReturnableEvaluator() + { + public boolean isReturnableNode( TraversalPosition currentPos ) + { + if ( currentPos.currentNode() + .hasProperty( "episode" ) ) + { + Node episode = currentPos.currentNode(); + + for ( Relationship r : episode.getRelationships( DoctorWhoRelationships.APPEARED_IN, Direction.INCOMING ) ) + { + if ( r.getStartNode() + .hasProperty( "actor" ) && r.getStartNode() + .getProperty( "actor" ) + .equals( "David Tennant" ) ) + { + return true; + } + } + } + + return false; + } + }, DoctorWhoRelationships.APPEARED_IN, Direction.OUTGOING ); + + // SNIPPET_END + + int numberOfEpisodesWithTennantVersusTheMaster = 4; + assertEquals( numberOfEpisodesWithTennantVersusTheMaster, t.getAllNodes() + .size() ); + } +} diff --git a/src/koan/java/org/neo4j/tutorial/Koan07.java b/src/koan/java/org/neo4j/tutorial/Koan07.java new file mode 100644 index 0000000..3e319e5 --- /dev/null +++ b/src/koan/java/org/neo4j/tutorial/Koan07.java @@ -0,0 +1,106 @@ +package org.neo4j.tutorial; + +import static org.junit.Assert.assertThat; +import static org.neo4j.tutorial.matchers.ContainsOnlySpecificActors.containsOnlyActors; +import static org.neo4j.tutorial.matchers.ContainsSpecificNumberOfNodes.containsNumberOfNodes; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.neo4j.graphdb.Direction; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Path; +import org.neo4j.graphdb.traversal.Evaluation; +import org.neo4j.graphdb.traversal.Evaluator; +import org.neo4j.graphdb.traversal.TraversalDescription; +import org.neo4j.kernel.Traversal; + +/** + * In this Koan we start using the new traversal framework to find interesting + * information from the graph about the Doctor's past life. + */ +public class Koan07 +{ + + private static EmbeddedDoctorWhoUniverse universe; + + @BeforeClass + public static void createDatabase() throws Exception + { + universe = new EmbeddedDoctorWhoUniverse( new DoctorWhoUniverseGenerator() ); + } + + @AfterClass + public static void closeTheDatabase() + { + universe.stop(); + } + + @Test + public void shouldDiscoverHowManyDoctorActorsHaveParticipatedInARegeneration() throws Exception + { + Node theDoctor = universe.theDoctor(); + TraversalDescription regeneratedActors = null; + + // YOUR CODE GOES HERE + // SNIPPET_START + + regeneratedActors = Traversal.description() + .relationships( DoctorWhoRelationships.PLAYED, Direction.INCOMING ) + .breadthFirst() + .evaluator( new Evaluator() + { + public Evaluation evaluate( Path path ) + { + if ( path.endNode().hasRelationship( DoctorWhoRelationships.REGENERATED_TO, Direction.BOTH ) ) + { + return Evaluation.INCLUDE_AND_CONTINUE; + } else + { + return Evaluation.EXCLUDE_AND_PRUNE; + } + } + } ); + + // SNIPPET_END + + assertThat( regeneratedActors.traverse( theDoctor ).nodes(), containsNumberOfNodes( 11 ) ); + } + + @Test + public void shouldFindTheFirstDoctor() + { + Node theDoctor = universe.theDoctor(); + TraversalDescription firstDoctor = null; + + // YOUR CODE GOES HERE + // SNIPPET_START + + firstDoctor = Traversal.description() + .relationships( DoctorWhoRelationships.PLAYED, Direction.INCOMING ) + .depthFirst() + .evaluator( new Evaluator() + { + public Evaluation evaluate( Path path ) + { + if ( path.endNode().hasRelationship( DoctorWhoRelationships.REGENERATED_TO, Direction.INCOMING ) ) + { + return Evaluation.EXCLUDE_AND_CONTINUE; + } else if ( !path.endNode().hasRelationship( DoctorWhoRelationships.REGENERATED_TO, Direction.OUTGOING ) ) + { + // Catches Richard Hurdnall who played the William + // Hartnell's Doctor in The Five Doctors (William + // Hartnell had died by then) + return Evaluation.EXCLUDE_AND_CONTINUE; + } else + { + return Evaluation.INCLUDE_AND_PRUNE; + } + } + } ); + + // SNIPPET_END + + assertThat( firstDoctor.traverse( theDoctor ).nodes(), containsOnlyActors( "William Hartnell" ) ); + } +} diff --git a/src/koan/java/org/neo4j/tutorial/Koan08.java b/src/koan/java/org/neo4j/tutorial/Koan08.java new file mode 100644 index 0000000..28942a0 --- /dev/null +++ b/src/koan/java/org/neo4j/tutorial/Koan08.java @@ -0,0 +1,159 @@ +package org.neo4j.tutorial; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.neo4j.helpers.collection.IteratorUtil.asIterable; +import static org.neo4j.tutorial.matchers.ContainsOnlySpecificTitles.containsOnlyTitles; + +import java.util.Iterator; +import java.util.Map; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.neo4j.cypher.ExecutionEngine; +import org.neo4j.cypher.ExecutionResult; +import org.neo4j.cypher.commands.Query; +import org.neo4j.cypher.parser.CypherParser; +import org.neo4j.graphdb.Node; + +/** + * In this Koan we use the Cypher graph pattern matching language to investigate + * the history of the Dalek props. + */ +public class Koan08 +{ + private static EmbeddedDoctorWhoUniverse universe; + + @BeforeClass + public static void createDatabase() throws Exception + { + universe = new EmbeddedDoctorWhoUniverse( new DoctorWhoUniverseGenerator() ); + } + + @AfterClass + public static void closeTheDatabase() + { + universe.stop(); + } + + @Test + public void shouldFindAllTheEpisodesInWhichTheDaleksAppeared() throws Exception + { + CypherParser parser = new CypherParser(); + ExecutionEngine engine = new ExecutionEngine( universe.getDatabase() ); + String cql = null; + + // YOUR CODE GOES HERE + // SNIPPET_START + + cql = "start daleks = node:species(species ='Dalek') match (daleks)-[:APPEARED_IN]->(episode) RETURN episode"; + + // SNIPPET_END + + Query query = parser.parse( cql ); + ExecutionResult result = engine.execute( query ); + Iterator episodes = result.javaColumnAs( "episode" ); + + assertThat( asIterable( episodes ), + containsOnlyTitles( "The Pandorica Opens", "Victory of the Daleks", "Journey's End", "The Stolen Earth", "Evolution of the Daleks", + "Daleks in Manhattan", "Doomsday", "Army of Ghosts", "The Parting of the Ways", "Bad Wolf", "Dalek", "Remembrance of the Daleks", + "Revelation of the Daleks", "Resurrection of the Daleks", "The Five Doctors", "Destiny of the Daleks", "Genesis of the Daleks", "Death to the Daleks", + "Planet of the Daleks", "Frontier in Space", "Day of the Daleks", "The War Games", "The Evil of the Daleks", "The Power of the Daleks", "The Daleks' Master Plan", "The Chase", + "The Space Museum", "The Dalek Invasion of Earth", "The Daleks" ) ); + + } + + @Test + public void shouldFindEpisodesWhereTennantAndRoseBattleTheDaleks() throws Exception + { + CypherParser parser = new CypherParser(); + ExecutionEngine engine = new ExecutionEngine( universe.getDatabase() ); + String cql = null; + + // YOUR CODE GOES HERE + // SNIPPET_START + + cql = "start daleks = node:species( species = 'Dalek'), rose = node:characters( character= 'Rose Tyler'), tennant = node:actors( actor = 'David Tennant')"; + cql += "match (tennant)-[:APPEARED_IN]->(ep), (rose)-[:APPEARED_IN]->(ep), (daleks)-[:APPEARED_IN]->(ep)"; + cql += "return ep"; + + // SNIPPET_END + + Query query = parser.parse( cql ); + ExecutionResult result = engine.execute( query ); + Iterator episodes = result.javaColumnAs( "ep" ); + + assertThat( asIterable( episodes ), + containsOnlyTitles( "Journey's End", "The Stolen Earth", "Doomsday", "Army of Ghosts", "The Parting of the Ways" ) ); + } + + @Test + public void shouldFindTheFifthMostRecentPropToAppear() throws Exception + { + CypherParser parser = new CypherParser(); + ExecutionEngine engine = new ExecutionEngine( universe.getDatabase() ); + + String cql = null; + ExecutionResult result = null; + + // YOUR CODE GOES HERE + // SNIPPET_START + + //Not every prop part can be identified with a prop - e.g. the Exhibition skirt + //As a result, prop.prop will not exist for every prop node + //So, we must use prop.prop? - this fills the prop.prop column with a + //value for prop parts with no identifiable prop + + cql = "start dalek = node:species( species = 'Dalek') "; + cql += "match (dalek)-[:APPEARED_IN]->(episode)<-[:USED_IN]-(props)<-[:MEMBER_OF]-(prop) "; + cql += "return prop.prop?, episode.episode order by episode.episode desc skip 4 limit 1"; + + Query query = parser.parse( cql ); + result = engine.execute( query ); + + + // SNIPPET_END + + assertEquals( "Supreme Dalek", result.javaColumnAs( "prop.prop" ).next() ); + } + + + @Test + public void shouldFindTheHardestWorkingPropPartInShowbiz() throws Exception + { + CypherParser parser = new CypherParser(); + ExecutionEngine engine = new ExecutionEngine( universe.getDatabase() ); + String cql = null; + + // YOUR CODE GOES HERE + // SNIPPET_START + + cql = "start daleks= node:species(species = 'Dalek') match (daleks)-[:APPEARED_IN]->(episode)<-[:USED_IN]-(props)<-[:MEMBER_OF]-(prop)" + + "-[:COMPOSED_OF]->(part)-[:ORIGINAL_PROP]->(originalprop) return originalprop.prop, part.part, count(episode.title)" + + " order by count (episode.title) desc limit 1"; + + // SNIPPET_END + + Query query = parser.parse( cql ); + ExecutionResult result = engine.execute( query ); + + assertHardestWorkingPropParts( result.javaIterator(), + "Dalek 1", "shoulder", 15 ); + + } + + private void assertHardestWorkingPropParts( Iterator> results, Object... partsAndCounts ) + { + for ( int index = 0; index < partsAndCounts.length; index = index + 3 ) + { + Map row = results.next(); + assertEquals( partsAndCounts[index], row.get( "originalprop.prop" ) ); + assertEquals( partsAndCounts[index + 1], row.get( "part.part" ) ); + assertEquals( partsAndCounts[index + 2], row.get( "count(episode.title)" ) ); + } + + assertFalse( results.hasNext() ); + } +} diff --git a/src/koan/java/org/neo4j/tutorial/Koan09.java b/src/koan/java/org/neo4j/tutorial/Koan09.java new file mode 100644 index 0000000..0bb3c2c --- /dev/null +++ b/src/koan/java/org/neo4j/tutorial/Koan09.java @@ -0,0 +1,149 @@ +package org.neo4j.tutorial; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.neo4j.tutorial.matchers.ContainsOnlySpecificNodes.containsOnly; +import static org.neo4j.tutorial.matchers.PathsMatcher.consistPreciselyOf; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.neo4j.graphalgo.GraphAlgoFactory; +import org.neo4j.graphalgo.PathFinder; +import org.neo4j.graphdb.Direction; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Path; +import org.neo4j.kernel.Traversal; + +/** + * In this Koan we use some of the pre-canned graph algorithms that come with + * Neo4j to gain more insight into the Doctor's universe. + */ +public class Koan09 +{ + + private static EmbeddedDoctorWhoUniverse universe; + + @BeforeClass + public static void createDatabase() throws Exception + { + universe = new EmbeddedDoctorWhoUniverse( new DoctorWhoUniverseGenerator() ); + } + + @AfterClass + public static void closeTheDatabase() + { + universe.stop(); + } + + @Test + public void shouldRevealTheEpisodesWhereRoseTylerFoughtTheDaleks() + { + Node rose = universe.getDatabase() + .index() + .forNodes( "characters" ) + .get( "character", "Rose Tyler" ) + .getSingle(); + Node daleks = universe.getDatabase() + .index() + .forNodes( "species" ) + .get( "species", "Dalek" ) + .getSingle(); + Iterable paths = null; + + // YOUR CODE GOES HERE + // SNIPPET_START + + PathFinder pathFinder = GraphAlgoFactory.pathsWithLength( + Traversal.expanderForTypes( DoctorWhoRelationships.APPEARED_IN, Direction.BOTH ), 2 ); + paths = pathFinder.findAllPaths( rose, daleks ); + + // SNIPPET_END + + assertThat( paths, consistPreciselyOf( rose, knownRoseVersusDaleksEpisodes(), daleks ) ); + } + + private HashSet knownRoseVersusDaleksEpisodes() + { + List roseVersusDaleksEpisodeTitles = Arrays.asList( "Dalek", "Army of Ghosts", "Doomsday", + "The Parting of the Ways", "The Stolen Earth", "Bad Wolf", "Journey's End" ); + HashSet roseVersusDaleksEpisodes = new HashSet(); + for ( String title : roseVersusDaleksEpisodeTitles ) + { + roseVersusDaleksEpisodes.add( universe.getDatabase() + .index() + .forNodes( "episodes" ) + .get( "title", title ) + .getSingle() ); + } + return roseVersusDaleksEpisodes; + } + + @Test + public void shouldFindTheNumberOfMasterRegenerationsTheEasyWay() + { + Node delgado = universe.getDatabase() + .index() + .forNodes( "actors" ) + .get( "actor", "Roger Delgado" ) + .getSingle(); + Node simm = universe.getDatabase() + .index() + .forNodes( "actors" ) + .get( "actor", "John Simm" ) + .getSingle(); + Path path = null; + + // YOUR CODE GOES HERE + // SNIPPET_START + + PathFinder pathFinder = GraphAlgoFactory.shortestPath( + Traversal.expanderForTypes( DoctorWhoRelationships.REGENERATED_TO, Direction.OUTGOING ), 100 ); + path = pathFinder.findSinglePath( delgado, simm ); + + // SNIPPET_END + + assertNotNull( path ); + int numberOfMasterRegenerations = 8; + int numberOfActorsFound = path.length() + 1; + assertEquals( numberOfMasterRegenerations, numberOfActorsFound ); + } + + @Test + public void shouldRevealEpisodeWhenTennantRegeneratedToSmith() + { + Node tennant = universe.getDatabase() + .index() + .forNodes( "actors" ) + .get( "actor", "David Tennant" ) + .getSingle(); + Node smith = universe.getDatabase() + .index() + .forNodes( "actors" ) + .get( "actor", "Matt Smith" ) + .getSingle(); + Path path = null; + + // YOUR CODE GOES HERE + // SNIPPET_START + + PathFinder pathFinder = GraphAlgoFactory.pathsWithLength( + Traversal.expanderForTypes( DoctorWhoRelationships.APPEARED_IN, Direction.BOTH ), 2 ); + path = pathFinder.findSinglePath( tennant, smith ); + + // SNIPPET_END + + assertNotNull( path ); + Node endOfTimeEpisode = universe.getDatabase() + .index() + .forNodes( "episodes" ) + .get( "title", "The End of Time" ) + .getSingle(); + assertThat( path, containsOnly( tennant, smith, endOfTimeEpisode ) ); + } +} diff --git a/src/koan/java/org/neo4j/tutorial/Koan10.java b/src/koan/java/org/neo4j/tutorial/Koan10.java new file mode 100644 index 0000000..788b9af --- /dev/null +++ b/src/koan/java/org/neo4j/tutorial/Koan10.java @@ -0,0 +1,186 @@ +package org.neo4j.tutorial; + +import static org.junit.Assert.assertThat; +import static org.neo4j.tutorial.matchers.ContainsOnlySpecificActors.containsOnlyActors; +import static org.neo4j.tutorial.matchers.ContainsOnlySpecificSpecies.containsOnlySpecies; +import static org.neo4j.tutorial.matchers.ContainsOnlySpecificTitles.containsOnlyTitles; + +import java.util.HashSet; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.neo4j.graphdb.Node; +import org.neo4j.graphmatching.CommonValueMatchers; +import org.neo4j.graphmatching.PatternMatch; +import org.neo4j.graphmatching.PatternMatcher; +import org.neo4j.graphmatching.PatternNode; + +/** + * In this Koan we use the graph-matching library to look for patterns in the + * Doctor's universe. + */ +public class Koan10 +{ + + private static EmbeddedDoctorWhoUniverse universe; + + @BeforeClass + public static void createDatabase() throws Exception + { + universe = new EmbeddedDoctorWhoUniverse( new DoctorWhoUniverseGenerator() ); + } + + @AfterClass + public static void closeTheDatabase() + { + universe.stop(); + } + + @Test + public void shouldFindEpisodesWhereTheDoctorFoughtTheCybermen() + { + HashSet cybermenEpisodes = new HashSet(); + + // YOUR CODE GOES HERE + // SNIPPET_START + + final PatternNode theDoctor = new PatternNode(); + theDoctor.setAssociation( universe.theDoctor() ); + + final PatternNode anEpisode = new PatternNode(); + anEpisode.addPropertyConstraint( "title", CommonValueMatchers.has() ); + anEpisode.addPropertyConstraint( "episode", CommonValueMatchers.has() ); + + final PatternNode aDoctorActor = new PatternNode(); + aDoctorActor.createRelationshipTo( theDoctor, DoctorWhoRelationships.PLAYED ); + aDoctorActor.createRelationshipTo( anEpisode, DoctorWhoRelationships.APPEARED_IN ); + aDoctorActor.addPropertyConstraint( "actor", CommonValueMatchers.has() ); + + final PatternNode theCybermen = new PatternNode(); + theCybermen.setAssociation( universe.getDatabase() + .index() + .forNodes( "species" ) + .get( "species", "Cyberman" ) + .getSingle() ); + theCybermen.createRelationshipTo( anEpisode, DoctorWhoRelationships.APPEARED_IN ); + theCybermen.createRelationshipTo( theDoctor, DoctorWhoRelationships.ENEMY_OF ); + + PatternMatcher matcher = PatternMatcher.getMatcher(); + final Iterable matches = matcher.match( theDoctor, universe.theDoctor() ); + + for ( PatternMatch pm : matches ) + { + cybermenEpisodes.add( pm.getNodeFor( anEpisode ) ); + } + + // SNIPPET_END + + assertThat( cybermenEpisodes, containsOnlyTitles( knownCybermenTitles() ) ); + } + + private String[] knownCybermenTitles() + { + return new String[]{"The Moonbase", "The Tomb of the Cybermen", "The Wheel in Space", + "Revenge of the Cybermen", "Earthshock", "Silver Nemesis", "Rise of the Cybermen", "The Age of Steel", + "Army of Ghosts", "Doomsday", "The Next Doctor", "The Pandorica Opens", "A Good Man Goes to War"}; + } + + @Test + public void shouldFindDoctorsThatBattledTheCybermen() + { + HashSet doctorActors = new HashSet(); + Node cybermenNode = universe.getDatabase() + .index() + .forNodes( "species" ) + .get( "species", "Cyberman" ) + .getSingle(); + + // YOUR CODE GOES HERE + // SNIPPET_START + + final PatternNode theDoctor = new PatternNode(); + theDoctor.setAssociation( universe.theDoctor() ); + + final PatternNode anEpisode = new PatternNode(); + anEpisode.addPropertyConstraint( "title", CommonValueMatchers.has() ); + anEpisode.addPropertyConstraint( "episode", CommonValueMatchers.has() ); + + final PatternNode aDoctorActor = new PatternNode(); + aDoctorActor.createRelationshipTo( theDoctor, DoctorWhoRelationships.PLAYED ); + aDoctorActor.createRelationshipTo( anEpisode, DoctorWhoRelationships.APPEARED_IN ); + aDoctorActor.addPropertyConstraint( "actor", CommonValueMatchers.has() ); + + final PatternNode theCybermen = new PatternNode(); + theCybermen.setAssociation( cybermenNode ); + theCybermen.createRelationshipTo( anEpisode, DoctorWhoRelationships.APPEARED_IN ); + theCybermen.createRelationshipTo( theDoctor, DoctorWhoRelationships.ENEMY_OF ); + + PatternMatcher matcher = PatternMatcher.getMatcher(); + final Iterable matches = matcher.match( theDoctor, universe.theDoctor() ); + + for ( PatternMatch pm : matches ) + { + doctorActors.add( pm.getNodeFor( aDoctorActor ) ); + } + + // SNIPPET_END + + assertThat( + doctorActors, + containsOnlyActors( "David Tennant", "Matt Smith", "Patrick Troughton", "Tom Baker", "Peter Davison", + "Sylvester McCoy" ) ); + } + + @Test + public void shouldFindEnemySpeciesThatRoseTylerAndTheNinthDoctorEncountered() + { + HashSet enemySpeciesRoseAndTheNinthDoctorEncountered = new HashSet(); + Node ninthDoctorNode = universe.getDatabase() + .index() + .forNodes( "actors" ) + .get( "actor", "Christopher Eccleston" ) + .getSingle(); + Node roseTylerNode = universe.getDatabase() + .index() + .forNodes( "characters" ) + .get( "character", "Rose Tyler" ) + .getSingle(); + + // YOUR CODE GOES HERE + // SNIPPET_START + + final PatternNode theDoctor = new PatternNode(); + theDoctor.setAssociation( universe.theDoctor() ); + + final PatternNode ecclestone = new PatternNode(); + ecclestone.setAssociation( ninthDoctorNode ); + + final PatternNode roseTyler = new PatternNode(); + roseTyler.setAssociation( roseTylerNode ); + + final PatternNode anEpisode = new PatternNode(); + anEpisode.addPropertyConstraint( "title", CommonValueMatchers.has() ); + anEpisode.addPropertyConstraint( "episode", CommonValueMatchers.has() ); + + final PatternNode anEnemySpecies = new PatternNode(); + anEnemySpecies.addPropertyConstraint( "species", CommonValueMatchers.has() ); + + ecclestone.createRelationshipTo( anEpisode, DoctorWhoRelationships.APPEARED_IN ); + roseTyler.createRelationshipTo( anEpisode, DoctorWhoRelationships.APPEARED_IN ); + anEnemySpecies.createRelationshipTo( anEpisode, DoctorWhoRelationships.APPEARED_IN ); + anEnemySpecies.createRelationshipTo( theDoctor, DoctorWhoRelationships.ENEMY_OF ); + + PatternMatcher matcher = PatternMatcher.getMatcher(); + final Iterable matches = matcher.match( theDoctor, universe.theDoctor() ); + + for ( PatternMatch pm : matches ) + { + enemySpeciesRoseAndTheNinthDoctorEncountered.add( pm.getNodeFor( anEnemySpecies ) ); + } + + // SNIPPET_END + + assertThat( enemySpeciesRoseAndTheNinthDoctorEncountered, containsOnlySpecies( "Dalek", "Slitheen", "Auton" ) ); + } +} diff --git a/src/koan/java/org/neo4j/tutorial/Koan11.java b/src/koan/java/org/neo4j/tutorial/Koan11.java new file mode 100644 index 0000000..a47221a --- /dev/null +++ b/src/koan/java/org/neo4j/tutorial/Koan11.java @@ -0,0 +1,214 @@ +package org.neo4j.tutorial; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.List; +import java.util.Map; + +import javax.ws.rs.core.MediaType; + +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.WebResource; +import com.sun.jersey.api.client.config.ClientConfig; +import com.sun.jersey.api.client.config.DefaultClientConfig; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.neo4j.graphdb.Direction; +import org.neo4j.graphdb.DynamicRelationshipType; +import org.neo4j.graphdb.Node; +import org.neo4j.graphmatching.CommonValueMatchers; +import org.neo4j.graphmatching.PatternMatch; +import org.neo4j.graphmatching.PatternMatcher; +import org.neo4j.graphmatching.PatternNode; +import org.neo4j.helpers.collection.MapUtil; +import org.neo4j.kernel.AbstractGraphDatabase; +import org.neo4j.server.rest.domain.JsonHelper; +import org.neo4j.tutorial.server.rest.BatchCommandBuilder; +import org.neo4j.tutorial.server.rest.RelationshipDescription; +import org.neo4j.tutorial.server.rest.TraversalDescription; +import org.neo4j.tutorial.server.rest.domain.EpisodeSearchResult; +import org.neo4j.tutorial.server.rest.domain.EpisodeSearchResults; + +/** + * In this Koan we use the REST API to explore the Doctor Who universe. + */ +public class Koan11 +{ + + private static ServerDoctorWhoUniverse universe; + + @BeforeClass + public static void createDatabase() throws Exception + { + universe = new ServerDoctorWhoUniverse( new DoctorWhoUniverseGenerator() ); + } + + @AfterClass + public static void closeTheDatabase() + { + universe.stop(); + } + + @Test + public void shouldCountTheEnemiesOfTheDoctor() throws Exception + { + ClientConfig config = new DefaultClientConfig(); + Client client = Client.create( config ); + + String response = null; + + // YOUR CODE GOES HERE + // SNIPPET_START + + WebResource resource = client.resource( universe.theDoctor().get( "incoming_relationships" ) + "/ENEMY_OF" ); + response = resource.accept( MediaType.APPLICATION_JSON ).get( String.class ); + + // SNIPPET_END + + List> json = JsonHelper.jsonToList( response ); + int numberOfEnemiesOfTheDoctor = 142; + assertEquals( numberOfEnemiesOfTheDoctor, json.size() ); + } + + @Test + public void shouldIdentifyWhichDoctorsTookPartInInvasionStories() + throws Exception + { + ClientConfig config = new DefaultClientConfig(); + Client client = Client.create( config ); + + ClientResponse response = null; + TraversalDescription traversal = new TraversalDescription(); + + // YOUR CODE GOES HERE + // SNIPPET_START + + traversal.setOrder( "depth_first" ); + traversal.setUniqueness( "node_path" ); + traversal.setRelationships( + new RelationshipDescription( "PLAYED", RelationshipDescription.IN ), + new RelationshipDescription( "APPEARED_IN", RelationshipDescription.OUT ) ); + traversal.setReturnFilter( "position.endNode().hasProperty('title') && position.endNode().getProperty('title').contains('Invasion')" ); + traversal.setMaxDepth( 3 ); + + WebResource resource = client.resource( universe.theDoctor().get( "traverse" ).toString().replace( "{returnType}", "fullpath" ) ); + String requestJson = traversal.toJson(); + + response = resource + .accept( MediaType.APPLICATION_JSON ) + .type( MediaType.APPLICATION_JSON ) + .post( ClientResponse.class, requestJson ); + + // SNIPPET_END + + String responseJson = response.getEntity( String.class ); + + EpisodeSearchResults results = new EpisodeSearchResults( JsonHelper.jsonToList( responseJson ) ); + assertActorsAndInvasionEpisodes( results ); + } + + @Test + public void canAddFirstAndSecondIncarnationInformationForTheDoctor() + { + + // We'd like to update the model to add a new domain entity - "incarnation". + // Timelords have one or more incarnations. In the TV series, an incarnation is played by one or + // more actors (usually one). Here we're going to use the REST batch API to add a bit of this new + // model. See the presentation for an example of the target graph structure. + + String PLAYED = "PLAYED"; + String INCARNATION_OF = "INCARNATION_OF"; + + Map theDoctorJson = universe.theDoctor(); + String theDoctorUri = theDoctorJson.get( "self" ).toString(); + + Map williamHartnellJson = universe.getJsonFor( universe.getUriFromIndex( "actors", "actor", "William Hartnell" ) ); + Map richardHurdnallJson = universe.getJsonFor( universe.getUriFromIndex( "actors", "actor", "Richard Hurdnall" ) ); + Map patrickTroughtonJson = universe.getJsonFor( universe.getUriFromIndex( "actors", "actor", "Patrick Troughton" ) ); + + ClientConfig config = new DefaultClientConfig(); + Client client = Client.create( config ); + + BatchCommandBuilder cmds = new BatchCommandBuilder(); + + // YOUR CODE GOES HERE + // SNIPPET_START + + cmds + .createNode( 0, MapUtil.stringMap( "incarnation", "First Doctor" ) ) + .createNode( 1, MapUtil.stringMap( "incarnation", "Second Doctor" ) ) + .createRelationship( "{0}/relationships", theDoctorUri, INCARNATION_OF ) + .createRelationship( "{1}/relationships", theDoctorUri, INCARNATION_OF ) + .createRelationship( williamHartnellJson.get( "create_relationship" ).toString(), "{0}", PLAYED ) + .createRelationship( richardHurdnallJson.get( "create_relationship" ).toString(), "{0}", PLAYED ) + .createRelationship( patrickTroughtonJson.get( "create_relationship" ).toString(), "{1}", PLAYED ); + + WebResource resource = client.resource( "http://localhost:7474/db/data/batch" ); + resource.accept( MediaType.APPLICATION_JSON ) + .type( MediaType.APPLICATION_JSON ) + .post( String.class, cmds.build() ); + + // SNIPPET_END + + assertFirstAndSecondDoctorCreatedAndLinkedToActors( universe.getServer().getDatabase().graph ); + + } + + private void assertActorsAndInvasionEpisodes( EpisodeSearchResults results ) + { + + Map episodesAndActors = MapUtil.stringMap( "The Christmas Invasion", "David Tennant", + "The Invasion of Time", "Tom Baker", + "The Android Invasion", "Tom Baker", + "Invasion of the Dinosaurs", "Jon Pertwee", + "The Invasion", "Patrick Troughton", + "The Dalek Invasion of Earth", "William Hartnell" ); + + int count = 0; + for ( EpisodeSearchResult result : results ) + { + assertTrue( episodesAndActors.containsKey( result.getEpisode() ) ); + assertEquals( episodesAndActors.get( result.getEpisode() ), result.getActor() ); + count++; + } + + assertEquals( episodesAndActors.keySet().size(), count ); + } + + private void assertFirstAndSecondDoctorCreatedAndLinkedToActors( AbstractGraphDatabase db ) + { + Node doctorNode = db.getNodeById( 1 ); + + final PatternNode theDoctor = new PatternNode(); + theDoctor.addPropertyConstraint( "character", CommonValueMatchers.exact( "Doctor" ) ); + + final PatternNode firstDoctor = new PatternNode(); + firstDoctor.addPropertyConstraint( "incarnation", CommonValueMatchers.exact( "First Doctor" ) ); + + final PatternNode secondDoctor = new PatternNode(); + secondDoctor.addPropertyConstraint( "incarnation", CommonValueMatchers.exact( "Second Doctor" ) ); + + final PatternNode williamHartell = new PatternNode(); + williamHartell.addPropertyConstraint( "actor", CommonValueMatchers.exact( "William Hartnell" ) ); + + final PatternNode richardHurdnall = new PatternNode(); + richardHurdnall.addPropertyConstraint( "actor", CommonValueMatchers.exact( "Richard Hurdnall" ) ); + + final PatternNode patrickTroughton = new PatternNode(); + patrickTroughton.addPropertyConstraint( "actor", CommonValueMatchers.exact( "Patrick Troughton" ) ); + + firstDoctor.createRelationshipTo( theDoctor, DynamicRelationshipType.withName( "INCARNATION_OF" ), Direction.OUTGOING ); + secondDoctor.createRelationshipTo( theDoctor, DynamicRelationshipType.withName( "INCARNATION_OF" ), Direction.OUTGOING ); + williamHartell.createRelationshipTo( firstDoctor, DoctorWhoRelationships.PLAYED, Direction.OUTGOING ); + richardHurdnall.createRelationshipTo( firstDoctor, DoctorWhoRelationships.PLAYED, Direction.OUTGOING ); + patrickTroughton.createRelationshipTo( secondDoctor, DoctorWhoRelationships.PLAYED, Direction.OUTGOING ); + + PatternMatcher matcher = PatternMatcher.getMatcher(); + final Iterable matches = matcher.match( theDoctor, doctorNode ); + + assertTrue( matches.iterator().hasNext() ); + } +} diff --git a/src/koan/java/org/neo4j/tutorial/matchers/CharacterAutoIndexContainsSpecificCharacters.java b/src/koan/java/org/neo4j/tutorial/matchers/CharacterAutoIndexContainsSpecificCharacters.java new file mode 100644 index 0000000..a7a095c --- /dev/null +++ b/src/koan/java/org/neo4j/tutorial/matchers/CharacterAutoIndexContainsSpecificCharacters.java @@ -0,0 +1,52 @@ +package org.neo4j.tutorial.matchers; + +import java.util.Set; + +import org.hamcrest.Description; +import org.hamcrest.Factory; +import org.hamcrest.Matcher; +import org.junit.internal.matchers.TypeSafeMatcher; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.index.AutoIndexer; + +public class CharacterAutoIndexContainsSpecificCharacters extends TypeSafeMatcher> +{ + + private final Set characterNames; + private String failedCharacterName; + + private CharacterAutoIndexContainsSpecificCharacters( Set characterNames ) + { + this.characterNames = characterNames; + } + + public void describeTo( Description description ) + { + description.appendText( String.format( + "The presented arguments did not contain all the supplied character names. Missing [%s].", + failedCharacterName ) ); + } + + @Override + public boolean matchesSafely( AutoIndexer characters ) + { + for ( String name : characterNames ) + { + if ( characters.getAutoIndex() + .get( "character-name", name ) + .getSingle() == null ) + { + failedCharacterName = name; + return false; + } + } + + return true; + } + + @Factory + public static Matcher> containsSpecificCharacters( Set allCharacterNames ) + { + return new CharacterAutoIndexContainsSpecificCharacters( allCharacterNames ); + } +} diff --git a/src/koan/java/org/neo4j/tutorial/matchers/ContainsOnlyHumanCompanions.java b/src/koan/java/org/neo4j/tutorial/matchers/ContainsOnlyHumanCompanions.java new file mode 100644 index 0000000..30770cb --- /dev/null +++ b/src/koan/java/org/neo4j/tutorial/matchers/ContainsOnlyHumanCompanions.java @@ -0,0 +1,46 @@ +package org.neo4j.tutorial.matchers; + +import java.util.Set; + +import org.hamcrest.Description; +import org.hamcrest.Factory; +import org.junit.internal.matchers.TypeSafeMatcher; +import org.neo4j.graphdb.Direction; +import org.neo4j.graphdb.Node; +import org.neo4j.tutorial.DoctorWhoRelationships; + +public class ContainsOnlyHumanCompanions extends TypeSafeMatcher> +{ + + private Node failedNode; + + public void describeTo( Description description ) + { + description.appendText( String.format( + "Node [%d] does not have an IS_A relationship to the human species node.", failedNode.getId() ) ); + } + + @Override + public boolean matchesSafely( Set nodes ) + { + for ( Node n : nodes ) + { + if ( !(n.hasRelationship( DoctorWhoRelationships.IS_A, Direction.OUTGOING ) && n.getSingleRelationship( + DoctorWhoRelationships.IS_A, Direction.OUTGOING ) + .getEndNode() + .getProperty( "species" ) + .equals( "Human" )) ) + { + failedNode = n; + return false; + } + } + return true; + } + + @Factory + public static ContainsOnlyHumanCompanions containsOnlyHumanCompanions() + { + return new ContainsOnlyHumanCompanions(); + } +} diff --git a/src/koan/java/org/neo4j/tutorial/matchers/ContainsOnlySpecificActors.java b/src/koan/java/org/neo4j/tutorial/matchers/ContainsOnlySpecificActors.java new file mode 100644 index 0000000..966011c --- /dev/null +++ b/src/koan/java/org/neo4j/tutorial/matchers/ContainsOnlySpecificActors.java @@ -0,0 +1,66 @@ +package org.neo4j.tutorial.matchers; + +import java.util.Collections; +import java.util.HashSet; + +import org.hamcrest.Description; +import org.hamcrest.Factory; +import org.hamcrest.Matcher; +import org.junit.internal.matchers.TypeSafeMatcher; +import org.neo4j.graphdb.Node; + +public class ContainsOnlySpecificActors extends TypeSafeMatcher> +{ + + private final HashSet actorNames = new HashSet(); + private boolean matchedParameterLengths; + private boolean nodeIsNotAnActor; + + private ContainsOnlySpecificActors( String... actors ) + { + Collections.addAll( actorNames, actors ); + } + + public void describeTo( Description description ) + { + if ( nodeIsNotAnActor ) + { + description.appendText( "A supplied node does not have the property 'actor'" ); + } + + if ( !matchedParameterLengths ) + { + description.appendText( "Number of actor names presented does not match number of actors" ); + } + } + + @Override + public boolean matchesSafely( Iterable actors ) + { + + for ( Node actor : actors ) + { + if ( !actor.hasProperty( "actor" ) ) + { + nodeIsNotAnActor = true; + return false; + } + + Object actorProperty = actor.getProperty( "actor" ); + if ( !actorNames.contains( actorProperty ) ) + { + return false; + } else + { + actorNames.remove( actorProperty ); + } + } + return matchedParameterLengths = actorNames.size() == 0; + } + + @Factory + public static Matcher> containsOnlyActors( String... actorNames ) + { + return new ContainsOnlySpecificActors( actorNames ); + } +} \ No newline at end of file diff --git a/src/koan/java/org/neo4j/tutorial/matchers/ContainsOnlySpecificNodes.java b/src/koan/java/org/neo4j/tutorial/matchers/ContainsOnlySpecificNodes.java new file mode 100644 index 0000000..9f52f16 --- /dev/null +++ b/src/koan/java/org/neo4j/tutorial/matchers/ContainsOnlySpecificNodes.java @@ -0,0 +1,53 @@ +package org.neo4j.tutorial.matchers; + +import java.util.HashSet; + +import org.hamcrest.Description; +import org.hamcrest.Factory; +import org.hamcrest.Matcher; +import org.junit.internal.matchers.TypeSafeMatcher; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Path; + +public class ContainsOnlySpecificNodes extends TypeSafeMatcher +{ + + private final HashSet nodes = new HashSet(); + + public ContainsOnlySpecificNodes( Node... nodes ) + { + for ( Node n : nodes ) + { + this.nodes.add( n ); + } + } + + @Override + public void describeTo( Description description ) + { + description.appendText( String.format( "Path does not contain only the specified nodes." ) ); + } + + @Override + public boolean matchesSafely( Path path ) + { + for ( Node n : path.nodes() ) + { + if ( nodes.contains( n ) ) + { + nodes.remove( n ); + } else + { + return false; + } + } + + return nodes.size() == 0; + } + + @Factory + public static Matcher containsOnly( Node... nodes ) + { + return new ContainsOnlySpecificNodes( nodes ); + } +} diff --git a/src/koan/java/org/neo4j/tutorial/matchers/ContainsOnlySpecificSpecies.java b/src/koan/java/org/neo4j/tutorial/matchers/ContainsOnlySpecificSpecies.java new file mode 100644 index 0000000..4879585 --- /dev/null +++ b/src/koan/java/org/neo4j/tutorial/matchers/ContainsOnlySpecificSpecies.java @@ -0,0 +1,64 @@ +package org.neo4j.tutorial.matchers; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.hamcrest.Description; +import org.hamcrest.Factory; +import org.hamcrest.Matcher; +import org.junit.internal.matchers.TypeSafeMatcher; +import org.neo4j.graphdb.Node; + +public class ContainsOnlySpecificSpecies extends TypeSafeMatcher> +{ + + private final Set species; + private String failedToFindSpecies; + private boolean matchedSize; + + public ContainsOnlySpecificSpecies( String... speciesNames ) + { + this.species = new HashSet(); + Collections.addAll( species, speciesNames ); + } + + public void describeTo( Description description ) + { + if ( failedToFindSpecies != null ) + { + description.appendText( String.format( "Failed to find species [%s] in the given species names", + failedToFindSpecies ) ); + } + + if ( !matchedSize ) + { + description.appendText( String.format( "Mismatched number of species, expected [%d]", species.size() ) ); + } + } + + @Override + public boolean matchesSafely( Iterable nodes ) + { + + for ( Node n : nodes ) + { + String speciesName = String.valueOf( n.getProperty( "species" ) ); + + if ( !species.contains( speciesName ) ) + { + failedToFindSpecies = speciesName; + return false; + } + species.remove( speciesName ); + } + + return matchedSize = species.size() == 0; + } + + @Factory + public static Matcher> containsOnlySpecies( String... speciesNames ) + { + return new ContainsOnlySpecificSpecies( speciesNames ); + } +} diff --git a/src/koan/java/org/neo4j/tutorial/matchers/ContainsOnlySpecificTitles.java b/src/koan/java/org/neo4j/tutorial/matchers/ContainsOnlySpecificTitles.java new file mode 100644 index 0000000..9c114e2 --- /dev/null +++ b/src/koan/java/org/neo4j/tutorial/matchers/ContainsOnlySpecificTitles.java @@ -0,0 +1,55 @@ +package org.neo4j.tutorial.matchers; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.hamcrest.Description; +import org.hamcrest.Factory; +import org.hamcrest.Matcher; +import org.junit.internal.matchers.TypeSafeMatcher; +import org.neo4j.graphdb.Node; + +public class ContainsOnlySpecificTitles extends TypeSafeMatcher> +{ + + private final Set titles; + private Node failedNode; + + public ContainsOnlySpecificTitles( String... specificTitles ) + { + this.titles = new HashSet(); + Collections.addAll( titles, specificTitles ); + } + + public void describeTo( Description description ) + { + description.appendText( String.format( "Node [%d] does not contain any of the specified titles", + failedNode.getId() ) ); + } + + @Override + public boolean matchesSafely( Iterable candidateNodes ) + { + + for ( Node n : candidateNodes ) + { + String property = String.valueOf( n.getProperty( "title" ) ); + + if ( !titles.contains( property ) ) + { + failedNode = n; + return false; + } + titles.remove( property ); + } + + return titles.size() == 0; + } + + @Factory + public static Matcher> containsOnlyTitles( String... titles ) + { + return new ContainsOnlySpecificTitles( titles ); + } +} diff --git a/src/koan/java/org/neo4j/tutorial/matchers/ContainsSpecificCompanions.java b/src/koan/java/org/neo4j/tutorial/matchers/ContainsSpecificCompanions.java new file mode 100644 index 0000000..2727c93 --- /dev/null +++ b/src/koan/java/org/neo4j/tutorial/matchers/ContainsSpecificCompanions.java @@ -0,0 +1,46 @@ +package org.neo4j.tutorial.matchers; + +import org.hamcrest.Description; +import org.hamcrest.Factory; +import org.hamcrest.Matcher; +import org.junit.internal.matchers.TypeSafeMatcher; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.index.Index; + +public class ContainsSpecificCompanions extends TypeSafeMatcher> +{ + + private final String[] companionNames; + + private ContainsSpecificCompanions( String[] companionNames ) + { + this.companionNames = companionNames; + } + + @Override + public void describeTo( Description description ) + { + description.appendText( "Checks whether each index in the presented arguments contains the supplied companion names." ); + } + + @Override + public boolean matchesSafely( Index companions ) + { + for ( String name : companionNames ) + { + if ( companions.get( "character", name ) + .getSingle() == null ) + { + return false; + } + } + + return true; + } + + @Factory + public static Matcher> contains( String... companionNames ) + { + return new ContainsSpecificCompanions( companionNames ); + } +} diff --git a/src/koan/java/org/neo4j/tutorial/matchers/ContainsSpecificNumberOfNodes.java b/src/koan/java/org/neo4j/tutorial/matchers/ContainsSpecificNumberOfNodes.java new file mode 100644 index 0000000..8bbef12 --- /dev/null +++ b/src/koan/java/org/neo4j/tutorial/matchers/ContainsSpecificNumberOfNodes.java @@ -0,0 +1,42 @@ +package org.neo4j.tutorial.matchers; + +import org.hamcrest.Description; +import org.hamcrest.Factory; +import org.hamcrest.Matcher; +import org.junit.internal.matchers.TypeSafeMatcher; +import org.neo4j.graphdb.Node; + +public class ContainsSpecificNumberOfNodes extends TypeSafeMatcher> +{ + + private final int number; + private int count; + + private ContainsSpecificNumberOfNodes( int number ) + { + this.number = number; + } + + @Override + public void describeTo( Description description ) + { + description.appendText( String.format( "Expected [%d] nodes, found [%s]", number, count ) ); + } + + @Override + public boolean matchesSafely( Iterable nodes ) + { + count = 0; + for ( @SuppressWarnings("unused") Node n : nodes ) + { + count++; + } + return count == number; + } + + @Factory + public static Matcher> containsNumberOfNodes( int number ) + { + return new ContainsSpecificNumberOfNodes( number ); + } +} diff --git a/src/koan/java/org/neo4j/tutorial/matchers/PathsMatcher.java b/src/koan/java/org/neo4j/tutorial/matchers/PathsMatcher.java new file mode 100644 index 0000000..126c277 --- /dev/null +++ b/src/koan/java/org/neo4j/tutorial/matchers/PathsMatcher.java @@ -0,0 +1,80 @@ +package org.neo4j.tutorial.matchers; + +import java.util.Set; + +import org.hamcrest.Description; +import org.hamcrest.Factory; +import org.hamcrest.Matcher; +import org.junit.internal.matchers.TypeSafeMatcher; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Path; + +public class PathsMatcher extends TypeSafeMatcher> +{ + private final Set nodes; + private final Node start; + private final Node end; + + private PathsMatcher( Node start, Set nodes, Node end ) + { + this.start = start; + this.nodes = nodes; + this.end = end; + + } + + public void describeTo( Description description ) + { + description.appendText( String.format( "Expected nodes: " ) ); + for ( Node n : nodes ) + { + description.appendText( "[" ); + description.appendText( String.format( String.valueOf( n.getId() ) ) ); + description.appendText( "]" ); + } + } + + @Override + public boolean matchesSafely( Iterable paths ) + { + int numberOfPaths = 0; + for ( Path p : paths ) + { + numberOfPaths++; + boolean middleNodeFound = false; + for ( Node middle : nodes ) + { + if ( nodesAreInPath( p, start, middle, end ) ) + { + middleNodeFound = true; + break; + } + } + + if ( !middleNodeFound ) + { + return false; + } + } + + return numberOfPaths == nodes.size(); + } + + private boolean nodesAreInPath( Path p, Node start, Node middle, Node end ) + { + for ( Node n : p.nodes() ) + { + if ( !(n.equals( start ) || n.equals( end ) || n.equals( middle )) ) + { + return false; + } + } + return true; + } + + @Factory + public static Matcher> consistPreciselyOf( Node start, Set nodes, Node end ) + { + return new PathsMatcher( start, nodes, end ); + } +} diff --git a/src/koan/java/org/neo4j/tutorial/server/rest/domain/EpisodeSearchResult.java b/src/koan/java/org/neo4j/tutorial/server/rest/domain/EpisodeSearchResult.java new file mode 100644 index 0000000..c2fc503 --- /dev/null +++ b/src/koan/java/org/neo4j/tutorial/server/rest/domain/EpisodeSearchResult.java @@ -0,0 +1,32 @@ +package org.neo4j.tutorial.server.rest.domain; + +import java.util.List; +import java.util.Map; + +public class EpisodeSearchResult +{ + private final List> nodes; + + public EpisodeSearchResult( List> nodes ) + { + super(); + this.nodes = nodes; + } + + @SuppressWarnings("unchecked") + public String getActor() + { + Map actor = (Map) nodes.get( 1 ) + .get( "data" ); + return (String) actor.get( "actor" ); + } + + @SuppressWarnings("unchecked") + public String getEpisode() + { + Map actor = (Map) nodes.get( 2 ) + .get( "data" ); + return (String) actor.get( "title" ); + } + +} diff --git a/src/koan/java/org/neo4j/tutorial/server/rest/domain/EpisodeSearchResults.java b/src/koan/java/org/neo4j/tutorial/server/rest/domain/EpisodeSearchResults.java new file mode 100644 index 0000000..1d20421 --- /dev/null +++ b/src/koan/java/org/neo4j/tutorial/server/rest/domain/EpisodeSearchResults.java @@ -0,0 +1,27 @@ +package org.neo4j.tutorial.server.rest.domain; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +public class EpisodeSearchResults implements Iterable +{ + private List results; + + @SuppressWarnings("unchecked") + public EpisodeSearchResults( List> json ) + { + super(); + this.results = new ArrayList(); + for ( Map result : json ) + { + results.add( new EpisodeSearchResult( (List>) result.get( "nodes" ) ) ); + } + } + + public Iterator iterator() + { + return results.iterator(); + } +} diff --git a/src/main/java/org/neo4j/tutorial/CharacterBuilder.java b/src/main/java/org/neo4j/tutorial/CharacterBuilder.java new file mode 100644 index 0000000..fcf5e2d --- /dev/null +++ b/src/main/java/org/neo4j/tutorial/CharacterBuilder.java @@ -0,0 +1,318 @@ +package org.neo4j.tutorial; + +import static org.neo4j.tutorial.DatabaseHelper.ensureRelationshipInDb; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Node; + +public class CharacterBuilder +{ + private final String characterName; + private HashSet species; + private boolean companion = false; + private String[] loverNames; + private String planet; + private String[] things; + private boolean enemy; + private boolean ally; + private ArrayList actors = new ArrayList(); + private HashMap startDates = new HashMap(); + + public static CharacterBuilder character( String characterName ) + { + return new CharacterBuilder( characterName ); + } + + public CharacterBuilder( String characterName ) + { + this.characterName = characterName; + } + + public CharacterBuilder isA( String speciesString ) + { + if ( species == null ) + { + species = new HashSet(); + } + species.add( speciesString ); + return this; + } + + public CharacterBuilder isCompanion() + { + companion = true; + return this; + } + + public void fact( GraphDatabaseService db ) + { + Node characterNode = ensureCharacterIsInDb( characterName, db ); + Node theDoctor = db.index() + .forNodes( "characters" ) + .get( "character", "Doctor" ) + .getSingle(); + + if ( species != null ) + { + for ( String speciesString : species ) + { + ensureRelationshipInDb( characterNode, DoctorWhoRelationships.IS_A, + SpeciesBuilder.ensureSpeciesInDb( speciesString, db ) ); + } + } + + if ( companion ) + { + ensureCompanionRelationshipInDb( characterNode, db ); + } + + if ( enemy ) + { + ensureEnemyOfRelationshipInDb( characterNode, db ); + } + + if ( ally ) + { + ensureRelationshipInDb( characterNode, DoctorWhoRelationships.ALLY_OF, theDoctor ); + } + + if ( loverNames != null ) + { + ensureLoversInDb( characterNode, loverNames, db ); + } + + if ( planet != null ) + { + ensurePlanetInDb( characterNode, planet, db ); + } + + if ( things != null ) + { + ensureThingsInDb( characterNode, things, db ); + } + + if ( actors != null ) + { + ensureActorsInDb( characterNode, actors, db ); + } + } + + public static void ensureAllyOfRelationshipInDb( Node allyNode, GraphDatabaseService db ) + { + Node theDoctor = db.index() + .forNodes( "characters" ) + .get( "character", "Doctor" ) + .getSingle(); + ensureRelationshipInDb( allyNode, DoctorWhoRelationships.ALLY_OF, theDoctor ); + ensureRelationshipInDb( theDoctor, DoctorWhoRelationships.ALLY_OF, allyNode ); + } + + public static void ensureEnemyOfRelationshipInDb( Node enemyNode, GraphDatabaseService db ) + { + Node theDoctor = db.index() + .forNodes( "characters" ) + .get( "character", "Doctor" ) + .getSingle(); + ensureRelationshipInDb( enemyNode, DoctorWhoRelationships.ENEMY_OF, theDoctor ); + ensureRelationshipInDb( theDoctor, DoctorWhoRelationships.ENEMY_OF, enemyNode ); + } + + public static void ensureCompanionRelationshipInDb( Node companionNode, GraphDatabaseService db ) + { + Node theDoctor = db.index() + .forNodes( "characters" ) + .get( "character", "Doctor" ) + .getSingle(); + ensureRelationshipInDb( companionNode, DoctorWhoRelationships.COMPANION_OF, theDoctor ); + } + + public void ensureActorsInDb( Node characterNode, List actors, GraphDatabaseService db ) + { + Node previousActorNode = null; + for ( String actor : actors ) + { + Node theActorNode = db.index() + .forNodes( "actors" ) + .get( "actor", actor ) + .getSingle(); + if ( theActorNode == null ) + { + theActorNode = db.createNode(); + theActorNode.setProperty( "actor", actor ); + db.index() + .forNodes( "actors" ) + .add( theActorNode, "actor", actor ); + } + + ensureRelationshipInDb( theActorNode, DoctorWhoRelationships.PLAYED, characterNode ); + db.index() + .forNodes( "actors" ) + .add( theActorNode, "actor", actor ); + + if ( previousActorNode != null ) + { + ensureRelationshipInDb( previousActorNode, DoctorWhoRelationships.REGENERATED_TO, theActorNode, + map( "year", startDates.get( actor ) ) ); + } + + previousActorNode = theActorNode; + } + } + + private Map map( String key, Integer value ) + { + + HashMap result = new HashMap(); + + if ( value != null ) + { + result.put( key, value ); + } + + return result; + } + + private static void ensureThingsInDb( Node characterNode, String[] things, GraphDatabaseService db ) + { + for ( String thing : things ) + { + ensureRelationshipInDb( characterNode, DoctorWhoRelationships.OWNS, ensureThingInDb( thing, db ) ); + } + } + + private static Node ensureThingInDb( String thing, GraphDatabaseService database ) + { + Node theThingNode = database.index() + .forNodes( "things" ) + .get( "thing", thing ) + .getSingle(); + if ( theThingNode == null ) + { + theThingNode = database.createNode(); + theThingNode.setProperty( "thing", thing ); + ensureThingIsIndexed( theThingNode, database ); + } + + return theThingNode; + } + + private static void ensureThingIsIndexed( Node thingNode, GraphDatabaseService database ) + { + database.index() + .forNodes( "things" ) + .add( thingNode, "thing", thingNode.getProperty( "thing" ) ); + } + + private static Node ensurePlanetInDb( Node characterNode, String planet, GraphDatabaseService database ) + { + Node thePlanetNode = database.index() + .forNodes( "planets" ) + .get( "planet", planet ) + .getSingle(); + if ( thePlanetNode == null ) + { + thePlanetNode = database.createNode(); + thePlanetNode.setProperty( "planet", planet ); + ensurePlanetIsIndexed( thePlanetNode, database ); + } + + ensureRelationshipInDb( characterNode, DoctorWhoRelationships.COMES_FROM, thePlanetNode ); + + return thePlanetNode; + } + + private static void ensurePlanetIsIndexed( Node thePlanetNode, GraphDatabaseService database ) + { + database.index() + .forNodes( "planets" ) + .add( thePlanetNode, "planet", thePlanetNode.getProperty( "planet" ) ); + } + + public static Node ensureCharacterIsInDb( String name, GraphDatabaseService db ) + { + Node theCharacterNode = db.index() + .forNodes( "characters" ) + .get( "character", name ) + .getSingle(); + if ( theCharacterNode == null ) + { + theCharacterNode = db.createNode(); + theCharacterNode.setProperty( "character", name ); + ensureCharacterIsIndexed( theCharacterNode, db ); + } + return theCharacterNode; + } + + private static void ensureCharacterIsIndexed( Node characterNode, GraphDatabaseService database ) + { + if ( database.index() + .forNodes( "characters" ) + .get( "character", characterNode.getProperty( "character" ) ) + .getSingle() == null ) + { + database.index() + .forNodes( "characters" ) + .add( characterNode, "character", characterNode.getProperty( "character" ) ); + } + } + + private static void ensureLoversInDb( Node characterNode, String[] loverNames, GraphDatabaseService db ) + { + for ( String lover : loverNames ) + { + ensureRelationshipInDb( characterNode, DoctorWhoRelationships.LOVES, ensureCharacterIsInDb( lover, db ) ); + } + } + + public CharacterBuilder loves( String... loverNames ) + { + this.loverNames = loverNames; + return this; + } + + public CharacterBuilder isFrom( String planet ) + { + this.planet = planet; + return this; + } + + public CharacterBuilder owns( String... things ) + { + this.things = things; + return this; + } + + public CharacterBuilder isEnemy() + { + this.enemy = true; + return this; + } + + public CharacterBuilder isAlly() + { + ally = true; + return this; + } + + public CharacterBuilder regeneration( String... actors ) + { + for ( String actor : actors ) + { + this.actors.add( actor ); + } + return this; + } + + public CharacterBuilder regeneration( String actor, int year ) + { + this.actors.add( actor ); + this.startDates.put( actor, new Integer( year ) ); + return this; + } +} diff --git a/src/main/java/org/neo4j/tutorial/Characters.java b/src/main/java/org/neo4j/tutorial/Characters.java new file mode 100644 index 0000000..6e4549d --- /dev/null +++ b/src/main/java/org/neo4j/tutorial/Characters.java @@ -0,0 +1,388 @@ +package org.neo4j.tutorial; + +import static org.neo4j.tutorial.CharacterBuilder.character; + +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Transaction; + +class Characters +{ + + private final GraphDatabaseService db; + + public Characters( GraphDatabaseService db ) + { + this.db = db; + } + + public void insert() + { + Transaction tx = db.beginTx(); + try + { + character( "Doctor" ).regeneration( "William Hartnell" ) + .regeneration( "Patrick Troughton", 1966 ) + .regeneration( "Jon Pertwee", 1970 ) + .regeneration( "Tom Baker", 1974 ) + .regeneration( "Peter Davison", 1981 ) + .regeneration( "Colin Baker", 1984 ) + .regeneration( "Sylvester McCoy", 1987 ) + .regeneration( "Paul McGann", 1996 ) + .regeneration( "Christopher Eccleston", 2005 ) + .regeneration( "David Tennant", 2005 ) + .regeneration( "Matt Smith", 2010 ) + .loves( "Rose Tyler", "River Song" ) + .isA( "Timelord" ) + .isFrom( "Gallifrey" ) + .owns( "Tardis", "Sonic Screwdriver" ) + .fact( db ); + loadCompanions(); + loadEnemies(); + loadAllies(); + tx.success(); + } finally + { + tx.finish(); + } + } + + private void loadEnemies() + { + character( "Master" ).regeneration( "Roger Delgado", "Peter Pratt", "Geoffrey Beevers", "Anthony Ainley", + "Gordon Tipple", "Eric Roberts", "Derek Jacobi", "John Simm" ) + .isEnemy() + .isA( "Timelord" ) + .isFrom( "Gallifrey" ) + .owns( "Tardis" ) + .fact( db ); + character( "Rani" ).isA( "Timelord" ) + .isFrom( "Gallifrey" ) + .owns( "Tardis" ) + .fact( db ); + character( "Meddling Monk" ).isA( "Timelord" ) + .isFrom( "Gallifrey" ) + .owns( "Tardis" ) + .fact( db ); + character( "Helen A" ).isA( "Human" ) + .isFrom( "Terra Alpha" ) + .isEnemy() + .fact( db ); + character( "Abzorbaloff" ).isA( "Abrobvian" ) + .isFrom( "Clom" ) + .isEnemy() + .fact( db ); + character( "Beast" ).isA( "Devil" ) + .isEnemy() + .fact( db ); + character( "Black Guardian" ).isEnemy() + .fact( db ); + character( "Bok" ).isA( "Gargoyle" ) + .isEnemy() + .fact( db ); + character( "Cassandra" ).isA( "Human" ) + .isFrom( "Earth" ) + .isEnemy() + .fact( db ); + character( "Cybercontroller" ).isA( "Cyberman" ) + .isFrom( "Mondas" ) + .isEnemy() + .fact( db ); + character( "Cyberleader" ).isA( "Cyberman" ) + .isFrom( "Mondas" ) + .isEnemy() + .fact( db ); + character( "Daemon" ).isEnemy() + .fact( db ); + character( "Dalek Caan" ).isA( "Dalek" ) + .isFrom( "Skaro" ) + .isEnemy() + .fact( db ); + character( "Dalek Jast" ).isA( "Dalek" ) + .isFrom( "Skaro" ) + .isEnemy() + .fact( db ); + character( "Dalek Sec" ).isA( "Dalek" ) + .isFrom( "Skaro" ) + .isEnemy() + .fact( db ); + character( "Dalek Thay" ).isA( "Dalek" ) + .isFrom( "Skaro" ) + .isEnemy() + .fact( db ); + character( "Davros" ).isA( "Kaled" ) + .isFrom( "Skaro" ) + .isEnemy() + .fact( db ); + character( "Destroyer" ).isEnemy() + .fact( db ); + character( "Eldrad" ).isA( "Kastrian" ) + .isFrom( "Kastria" ) + .isEnemy() + .fact( db ); + character( "Empress of Racnoss" ).isEnemy() + .fact( db ); + character( "Fendahl" ).isEnemy() + .fact( db ); + character( "General Staal" ).isA( "Sontaran" ) + .isFrom( "Sontar" ) + .isEnemy() + .fact( db ); + character( "K1 Robot" ).isEnemy() + .fact( db ); + character( "Linx" ).isA( "Sontaran" ) + .isFrom( "Sontar" ) + .isEnemy() + .fact( db ); + character( "Miss Hartigan" ).isA( "Human" ) + .isA( "Cyberman" ) + .isFrom( "Earth" ) + .isEnemy() + .fact( db ); + character( "Loch Ness Monster" ).isA( "Skarasen" ) + .isEnemy() + .fact( db ); + character( "Morbious" ).isA( "Timelord" ) + .isEnemy() + .fact( db ); + character( "Omega" ).isA( "Timelord" ) + .isEnemy() + .fact( db ); + character( "Ogron" ).isEnemy() + .fact( db ); + character( "Pyrovile" ).isEnemy() + .fact( db ); + character( "Reaper" ).isEnemy() + .fact( db ); + character( "Scaroth" ).isA( "Jagaroth" ) + .isEnemy() + .fact( db ); + character( "Stor" ).isA( "Sontaran" ) + .isFrom( "Sontar" ) + .isEnemy() + .fact( db ); + character( "Styre" ).isA( "Sontaran" ) + .isFrom( "Sontar" ) + .isEnemy() + .fact( db ); + character( "Sutekh" ).isA( "Osiron" ) + .isEnemy() + .fact( db ); + character( "Terileptils" ).isEnemy() + .fact( db ); + character( "Yartek" ).isA( "Voord" ) + .isEnemy() + .fact( db ); + } + + private void loadAllies() + { + character( "River Song" ).isA( "Human" ) + .loves( "Doctor" ) + .isAlly() + .fact( db ); + character( "Sergeant Benton" ).isA( "Human" ) + .isFrom( "Earth" ) + .isAlly() + .fact( db ); + character( "Mike Yates" ).isA( "Human" ) + .isFrom( "Earth" ) + .isAlly() + .fact( db ); + character( "Brigadier Lethbridge-Stewart" ).isA( "Human" ) + .isFrom( "Earth" ) + .isAlly() + .fact( db ); + character( "Professor Travers" ).isA( "Human" ) + .isFrom( "Earth" ) + .isAlly() + .fact( db ); + character( "Alpha Centauri" ).isA( "Alpha Centauran" ) + .isFrom( "Earth" ) + .isAlly() + .fact( db ); + character( "Duggan" ).isA( "Human" ) + .isFrom( "Earth" ) + .isAlly() + .fact( db ); + character( "Richard Mace" ).isA( "Human" ) + .isFrom( "Earth" ) + .isAlly() + .fact( db ); + character( "Chang Lee" ).isA( "Human" ) + .isFrom( "Earth" ) + .isAlly() + .fact( db ); + } + + private void loadCompanions() + { + character( "Susan Foreman" ).isA( "Timelord" ) + .isFrom( "Gallifrey" ) + .isCompanion() + .fact( db ); + character( "Romana" ).isA( "Timelord" ) + .isFrom( "Gallifrey" ) + .isCompanion() + .fact( db ); + character( "Barbara Wright" ).isA( "Human" ) + .isFrom( "Earth" ) + .isCompanion() + .fact( db ); + character( "Ian Chesterton" ).isA( "Human" ) + .isFrom( "Earth" ) + .isCompanion() + .fact( db ); + character( "Vicki" ).isA( "Human" ) + .isFrom( "Earth" ) + .isCompanion() + .fact( db ); + character( "Steven Taylor" ).isA( "Human" ) + .isFrom( "Earth" ) + .isCompanion() + .fact( db ); + character( "Katarina" ).isA( "Human" ) + .isFrom( "Earth" ) + .isCompanion() + .fact( db ); + character( "Sara Kingdom" ).isA( "Human" ) + .isFrom( "Earth" ) + .isCompanion() + .fact( db ); + character( "Dodo Chaplet" ).isA( "Human" ) + .isFrom( "Earth" ) + .isCompanion() + .fact( db ); + character( "Polly" ).isA( "Human" ) + .isFrom( "Earth" ) + .isCompanion() + .fact( db ); + character( "Ben Jackson" ).isA( "Human" ) + .isFrom( "Earth" ) + .isCompanion() + .fact( db ); + character( "Jamie McCrimmon" ).isA( "Human" ) + .isFrom( "Earth" ) + .isCompanion() + .fact( db ); + character( "Hamish Wilson" ).isA( "Human" ) + .isFrom( "Earth" ) + .isCompanion() + .fact( db ); + character( "Victoria Waterfield" ).isA( "Human" ) + .isFrom( "Earth" ) + .isCompanion() + .fact( db ); + character( "Zoe Heriot" ).isA( "Human" ) + .isFrom( "Earth" ) + .isCompanion() + .fact( db ); + character( "Liz Shaw" ).isA( "Human" ) + .isFrom( "Earth" ) + .isCompanion() + .fact( db ); + character( "Jo Grant" ).isA( "Human" ) + .isFrom( "Earth" ) + .isCompanion() + .fact( db ); + character( "Sarah Jane Smith" ).isA( "Human" ) + .isFrom( "Earth" ) + .isCompanion() + .fact( db ); + character( "Harry Sullivan" ).isA( "Human" ) + .isFrom( "Earth" ) + .isCompanion() + .fact( db ); + character( "Leela" ).isA( "Human" ) + .isCompanion() + .fact( db ); + character( "K9" ).isA( "Robotic Canine" ) + .isCompanion() + .fact( db ); + character( "Adric" ).isA( "Humanoid" ) + .isFrom( "Alzarius" ) + .isCompanion() + .fact( db ); + character( "Nyssa" ).isA( "Humanoid" ) + .isCompanion() + .fact( db ); + character( "Tegan Jovanka" ).isA( "Human" ) + .isFrom( "Earth" ) + .isCompanion() + .fact( db ); + character( "Vislor Turlough" ).isA( "Trion" ) + .isFrom( "Trion" ) + .isCompanion() + .fact( db ); + character( "Kamelion" ).isA( "Android" ) + .isFrom( "Xeriphas" ) + .isCompanion() + .fact( db ); + character( "Peri Brown" ).isA( "Human" ) + .isFrom( "Earth" ) + .isCompanion() + .fact( db ); + character( "Melanie Bush" ).isA( "Human" ) + .isFrom( "Earth" ) + .isCompanion() + .fact( db ); + character( "Ace" ).isA( "Human" ) + .isFrom( "Earth" ) + .isCompanion() + .fact( db ); + character( "Grace Holloway" ).isA( "Human" ) + .isFrom( "Earth" ) + .isCompanion() + .fact( db ); + character( "Rose Tyler" ).isA( "Human" ) + .isFrom( "Earth" ) + .loves( "Doctor" ) + .isCompanion() + .loves( "Doctor" ) + .fact( db ); + character( "Adam Mitchell" ).isA( "Human" ) + .isFrom( "Earth" ) + .isCompanion() + .fact( db ); + character( "Jack Harkness" ).isA( "Human" ) + .isCompanion() + .fact( db ); + character( "Mickey Smith" ).isA( "Human" ) + .isFrom( "Earth" ) + .isCompanion() + .fact( db ); + character( "Donna Noble" ).isA( "Human" ) + .isFrom( "Earth" ) + .isCompanion() + .fact( db ); + character( "Martha Jones" ).isA( "Human" ) + .isFrom( "Earth" ) + .loves( "Doctor" ) + .isCompanion() + .fact( db ); + character( "Astrid Peth" ).isA( "Human" ) + .isFrom( "Sto" ) + .isCompanion() + .fact( db ); + character( "Jackson Lake" ).isA( "Human" ) + .isFrom( "Earth" ) + .isCompanion() + .fact( db ); + character( "Rosita Farisi" ).isA( "Human" ) + .isFrom( "Earth" ) + .isCompanion() + .fact( db ); + character( "Lady Christina de Souza" ).isA( "Human" ) + .isFrom( "Earth" ) + .isCompanion() + .fact( db ); + character( "Adelaide Brooke" ).isA( "Human" ) + .isFrom( "Earth" ) + .isCompanion() + .fact( db ); + character( "Wilfred Mott" ).isA( "Human" ) + .isFrom( "Earth" ) + .isCompanion() + .fact( db ); + } + +} diff --git a/src/main/java/org/neo4j/tutorial/DalekPropBuilder.java b/src/main/java/org/neo4j/tutorial/DalekPropBuilder.java new file mode 100644 index 0000000..1ed0e62 --- /dev/null +++ b/src/main/java/org/neo4j/tutorial/DalekPropBuilder.java @@ -0,0 +1,226 @@ +package org.neo4j.tutorial; + +import java.util.ArrayList; +import java.util.List; + +import org.neo4j.graphdb.Direction; +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Relationship; +import org.neo4j.graphdb.RelationshipType; +import org.neo4j.graphdb.index.Index; + +public class DalekPropBuilder +{ + private static final String PROP = "prop"; + private static final String PROPS = "props"; + private final String episode; + private List props; + + public static DalekPropBuilder dalekProps( String episode ) + { + return new DalekPropBuilder( episode ); + } + + private DalekPropBuilder( String episode ) + { + super(); + this.episode = episode; + props = new ArrayList(); + } + + public DalekPropBuilder addProp( String shoulder, String skirt, String name ) + { + props.add( new Prop( shoulder, skirt, name ) ); + return this; + } + + private class Prop + { + private String shoulder; + private String skirt; + private String name; + + public Prop( String shoulder, String skirt, String name ) + { + super(); + this.shoulder = shoulder; + this.skirt = skirt; + this.name = name; + } + + public String getShoulder() + { + return shoulder; + } + + public String getSkirt() + { + return skirt; + } + + public String getName() + { + return name; + } + } + + public void fact( GraphDatabaseService db ) + { + Node dalekSpeciesNode = db.index() + .forNodes( "species" ) + .get( "species", "Dalek" ) + .getSingle(); + Node episodeNode = ensureEpisodeIsInDb( episode, db ); + ensureEpisodeIsConnectedToDalekSpecies( episodeNode, dalekSpeciesNode ); + + Node episodePropsNode = db.createNode(); + episodePropsNode.setProperty( PROPS, "Daleks" ); + episodePropsNode.createRelationshipTo( episodeNode, DoctorWhoRelationships.USED_IN ); + + for ( Prop prop : props ) + { + if ( isFullProp( prop ) ) + { + Node currentDalekPropNode = ensurePropAppearsInDb( prop.getName(), db ); + currentDalekPropNode.createRelationshipTo( episodePropsNode, DoctorWhoRelationships.MEMBER_OF ); + + if ( shoulderExists( prop ) ) + { + createPartAttachedToProp( prop.getShoulder(), "shoulder", currentDalekPropNode, db ); + } + + if ( skirtExists( prop ) ) + { + createPartAttachedToProp( prop.getSkirt(), "skirt", currentDalekPropNode, db ); + } + } else + { + if ( shoulderExists( prop ) ) + { + createPartAttachedToPropGroup( prop.getShoulder(), "shoulder", episodePropsNode, db ); + } + + if ( skirtExists( prop ) ) + { + createPartAttachedToPropGroup( prop.getSkirt(), "skirt", episodePropsNode, db ); + } + } + + } + } + + private void createPartAttachedToProp( String originalPropName, String part, Node currentDalekPropNode, + GraphDatabaseService db ) + { + Node partNode = ensurePartExistsInDb( originalPropName, part, db ); + if ( !relationshipExists( currentDalekPropNode, partNode, DoctorWhoRelationships.COMPOSED_OF, Direction.OUTGOING ) ) + { + currentDalekPropNode.createRelationshipTo( partNode, DoctorWhoRelationships.COMPOSED_OF ); + } + } + + private void createPartAttachedToPropGroup( String originalPropName, String part, Node propGroupNode, + GraphDatabaseService db ) + { + Node partNode = ensurePartExistsInDb( originalPropName, part, db ); + if ( !relationshipExists( partNode, propGroupNode, DoctorWhoRelationships.MEMBER_OF, Direction.OUTGOING ) ) + { + partNode.createRelationshipTo( propGroupNode, DoctorWhoRelationships.MEMBER_OF ); + } + } + + private boolean relationshipExists( Node startNode, Node endNode, RelationshipType relationship, Direction direction ) + { + Iterable rels = startNode.getRelationships( direction, relationship ); + for ( Relationship rel : rels ) + { + if ( rel.getOtherNode( startNode ) + .equals( endNode ) ) + { + return true; + } + } + return false; + } + + private boolean skirtExists( Prop prop ) + { + return prop.getSkirt() != null; + } + + private boolean shoulderExists( Prop prop ) + { + return prop.getShoulder() != null; + } + + private boolean isFullProp( Prop prop ) + { + return prop.getName() != null; + } + + private Node ensurePartExistsInDb( String originalPropName, String part, GraphDatabaseService db ) + { + Index index = db.index() + .forNodes( PROPS ); + Node shoulderNode = index.get( part, originalPropName ) + .getSingle(); + if ( shoulderNode == null ) + { + shoulderNode = db.createNode(); + shoulderNode.setProperty( "part", part ); + index.add( shoulderNode, part, originalPropName ); + + Node originalDalekPropNode = ensurePropAppearsInDb( originalPropName, db ); + shoulderNode.createRelationshipTo( originalDalekPropNode, DoctorWhoRelationships.ORIGINAL_PROP ); + } + return shoulderNode; + } + + private Node ensurePropAppearsInDb( String prop, GraphDatabaseService db ) + { + Index index = db.index() + .forNodes( PROPS ); + Node dalekPropNode = index.get( PROP, prop ) + .getSingle(); + if ( dalekPropNode == null ) + { + dalekPropNode = db.createNode(); + dalekPropNode.setProperty( PROP, prop ); + index.add( dalekPropNode, PROP, prop ); + } + return dalekPropNode; + } + + private void ensureEpisodeIsConnectedToDalekSpecies( Node episodeNode, Node speciesNode ) + { + boolean isConnected = false; + for ( Relationship rel : episodeNode.getRelationships( DoctorWhoRelationships.APPEARED_IN, Direction.INCOMING ) ) + { + if ( rel.getStartNode() + .equals( speciesNode ) ) + { + isConnected = true; + break; + } + } + if ( !isConnected ) + { + throw new RuntimeException( "Episode '" + episodeNode.getProperty( "title" ) + + "' not connected to Dalek species." ); + } + } + + private Node ensureEpisodeIsInDb( String episode, GraphDatabaseService db ) + { + Index index = db.index() + .forNodes( "episodes" ); + Node episodeNode = index.get( "title", episode ) + .getSingle(); + if ( episodeNode == null ) + { + throw new RuntimeException( "Episode '" + episode + "' missing from database." ); + } + return episodeNode; + } +} diff --git a/src/main/java/org/neo4j/tutorial/DalekProps.java b/src/main/java/org/neo4j/tutorial/DalekProps.java new file mode 100644 index 0000000..d2562e1 --- /dev/null +++ b/src/main/java/org/neo4j/tutorial/DalekProps.java @@ -0,0 +1,136 @@ +package org.neo4j.tutorial; + +import static org.neo4j.tutorial.DalekPropBuilder.dalekProps; + +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Transaction; + +public class DalekProps +{ + + private final GraphDatabaseService db; + + public DalekProps( GraphDatabaseService db ) + { + this.db = db; + } + + public void insert() + { + Transaction tx = db.beginTx(); + try + { + dalekProps( "The Daleks" ).addProp( "Dalek 1", "Dalek 1", "Dalek 1" ) + .addProp( "Dalek 2", "Dalek 2", "Dalek 2" ) + .addProp( "Dalek 3", "Dalek 3", "Dalek 3" ) + .addProp( "Dalek 4", "Dalek 4", "Dalek 4" ) + .fact( db ); + dalekProps( "The Dalek Invasion of Earth" ).addProp( "Dalek 1", "Dalek 1", "Dalek 1" ) + .addProp( "Dalek 2", "Dalek 2", "Dalek 2" ) + .addProp( "Dalek 3", "Dalek 3", "Dalek 3" ) + .addProp( "Dalek 4", "Dalek 4", "Dalek 4" ) + .addProp( "Dalek 5", "Dalek 5", "Dalek 5" ) + .addProp( "Dalek 6", "Dalek 6", "Dalek 6" ) + .fact( db ); + dalekProps( "The Space Museum" ).addProp( "Dalek 1", "Dalek 1", "Dalek 1" ) + .fact( db ); + dalekProps( "The Chase" ).addProp( "Dalek 1", "Dalek 1", "Dalek 1" ) + .addProp( "Dalek 2", "Dalek 2", "Dalek 2" ) + .addProp( "Dalek 5", "Dalek 5", "Dalek 5" ) + .addProp( "Dalek 6", "Dalek 6", "Dalek 6" ) + .addProp( "Dalek 7", "Dalek 7", "Dalek 7" ) + .fact( db ); + dalekProps( "The Daleks' Master Plan" ).addProp( "Dalek 1", "Dalek 1", "Dalek 1" ) + .addProp( "Dalek 2", "Dalek 2", "Dalek 2" ) + .addProp( "Dalek 5", "Dalek 5", "Dalek 5" ) + .addProp( "Dalek 6", "Dalek 6", "Dalek 6" ) + .fact( db ); + dalekProps( "The Power of the Daleks" ).addProp( "Dalek 1", "Dalek 1", "Dalek 1" ) + .addProp( "Dalek 2", "Dalek 2", "Dalek 2" ) + .addProp( "Dalek 6", "Dalek 5", "Dalek Six-5" ) + .addProp( "Dalek 7", "Dalek 7", "Dalek 7" ) + .fact( db ); + dalekProps( "The Evil of the Daleks" ).addProp( "Dalek 2", "Dalek 1", "Dalek Two-1" ) + .addProp( "Dalek 5", "Dalek 6", "Dalek Five-6" ) + .addProp( "Dalek 6", "Dalek 5", "Dalek Six-5" ) + .addProp( "Dalek 7", "Dalek 7", "Dalek 7" ) + .addProp( "Dalek 8", "Dalek 8", "Dalek 8" ) + .addProp( null, "Dalek 2", null ) + .fact( db ); + dalekProps( "The War Games" ).addProp( "Dalek 7", "Dalek 8", "Dalek Seven-8" ) + .fact( db ); + dalekProps( "Day of the Daleks" ).addProp( "Dalek 7", "Dalek 2", "Dalek Seven-2" ) + .addProp( "Dalek 1", "Dalek 5", "Dalek One-5" ) + .addProp( "Dalek 6", "Dalek 7", "Dalek Six-7" ) + .addProp( null, "Dalek 1", null ) + .fact( db ); + dalekProps( "Frontier in Space" ).addProp( "Dalek 7", "Dalek 2", "Dalek Seven-2" ) + .addProp( "Dalek 1", "Dalek 5", "Dalek One-5" ) + .addProp( "Dalek 6", "Dalek 7", "Dalek Six-7" ) + .fact( db ); + dalekProps( "Planet of the Daleks" ).addProp( "Dalek 1", "Dalek 5", "Dalek One-5" ) + .addProp( "Dalek 7", "Dalek 2", "Dalek Seven-2" ) + .addProp( "Dalek 6", "Dalek 7", "Dalek Six-7" ) + .addProp( "Gold Movie", "Gold Movie", "Gold Movie Dalek" ) + .addProp( "Goon I", "Goon I", "Goon I" ) + .addProp( "Goon II", "Goon II", "Goon II" ) + .addProp( "Goon III", "Goon III", "Goon III" ) + .addProp( "Goon IV", "Goon IV", "Goon IV" ) + .addProp( "Goon V", "Goon V", "Goon V" ) + .addProp( "Goon VI", "Goon VI", "Goon VI" ) + .addProp( "Goon VII", "Goon VII", "Goon VII" ) + .fact( db ); + dalekProps( "Death to the Daleks" ).addProp( "Dalek 1", "Dalek 7", "Dalek One-7" ) + .addProp( "Dalek 7", "Dalek 2", "Dalek Seven-2" ) + .addProp( "Dalek 6", "Dalek 5", "Dalek Six-5" ) + .addProp( "Goon I", "Goon I", "Goon I" ) + .addProp( "Goon VII", "Goon VII", "Goon VII" ) + .addProp( "Goon III", "Goon III", "Goon III" ) + .fact( db ); + dalekProps( "Genesis of the Daleks" ).addProp( "Dalek 1", "Dalek 7", "Dalek One-7" ) + .addProp( "Dalek 7", "Dalek 2", "Dalek Seven-2" ) + .addProp( "Dalek 6", "Dalek 5", "Dalek Six-5" ) + .addProp( "Goon I", "Goon I", "Goon I" ) + .addProp( "Goon II", "Goon II", "Goon II" ) + .addProp( "Goon IV", "Goon IV", "Goon IV" ) + .addProp( "Goon V", "Goon VI", "Dalek V-VI" ) + .fact( db ); + dalekProps( "Destiny of the Daleks" ).addProp( "Dalek 6", "Dalek 5", "Dalek Six-5" ) + .addProp( "Dalek 7", "Goon II", "Dalek Seven-II" ) + .addProp( "Goon IV", "Exhibition", "Dalek IV-Ex" ) + .addProp( "Goon V", "Goon VI", "Dalek V-VI" ) + .fact( db ); + dalekProps( "The Five Doctors" ).addProp( "Dalek 1", "Dalek 7", "Dalek One-7" ) + .fact( db ); + dalekProps( "Resurrection of the Daleks" ).addProp( "Dalek 1", "Dalek 7", "Dalek One-7" ) + .addProp( "Dalek 6", "Exhibition", "Dalek Six-Ex" ) + .addProp( "Dalek 7", "Goon V", "Dalek Seven-V" ) + .addProp( "Goon V", "Dalek 5", "Dalek V-5" ) + .fact( db ); + dalekProps( "Revelation of the Daleks" ).addProp( "Dalek 1", "Dalek 7", "Dalek One-7" ) + .addProp( "Dalek 6", "Exhibition", "Dalek Six-Ex" ) + .addProp( "Dalek 7", "Goon V", "Dalek Seven-V" ) + .addProp( "Goon V", "Dalek 5", "Dalek V-5" ) + .addProp( "Necros 1", "Necros 1", "Necros 1" ) + .addProp( "Necros 2", "Necros 2", "Necros 2" ) + .addProp( "Necros 3", "Necros 3", "Necros 3" ) + .fact( db ); + dalekProps( "Remembrance of the Daleks" ).addProp( "Dalek 1", "Dalek 7", "Dalek One-7" ) + .addProp( "Dalek 7", "Goon V", "Dalek Seven-V" ) + .addProp( "Remembrance 1", "Remembrance 1", "Remembrance 1" ) + .addProp( "Remembrance 2", "Remembrance 2", "Remembrance 2" ) + .addProp( "Remembrance 3", "Remembrance 3", "Remembrance 3" ) + .addProp( "Supreme Dalek", "Supreme Dalek", "Supreme Dalek" ) + .addProp( "Imperial 1", "Imperial 1", "Imperial 1" ) + .addProp( "Imperial 2", "Imperial 2", "Imperial 2" ) + .addProp( "Imperial 3", "Imperial 3", "Imperial 3" ) + .addProp( "Imperial 4", "Imperial 4", "Imperial 4" ) + .fact( db ); + tx.success(); + } finally + { + tx.finish(); + } + } + +} diff --git a/src/main/java/org/neo4j/tutorial/DatabaseHelper.java b/src/main/java/org/neo4j/tutorial/DatabaseHelper.java new file mode 100644 index 0000000..9b547a4 --- /dev/null +++ b/src/main/java/org/neo4j/tutorial/DatabaseHelper.java @@ -0,0 +1,192 @@ +package org.neo4j.tutorial; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.neo4j.graphdb.Direction; +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Relationship; +import org.neo4j.graphdb.RelationshipType; +import org.neo4j.graphdb.index.IndexHits; +import org.neo4j.kernel.EmbeddedGraphDatabase; + +public class DatabaseHelper +{ + + private final GraphDatabaseService db; + + public DatabaseHelper( GraphDatabaseService db ) + { + this.db = db; + + } + + public static EmbeddedGraphDatabase createDatabase() + { + return new EmbeddedGraphDatabase( createTempDatabaseDir().getAbsolutePath() ); + } + + public static EmbeddedGraphDatabase createDatabase( String dbDir ) + { + return new EmbeddedGraphDatabase( dbDir ); + } + + public static File createTempDatabaseDir() + { + + File d; + try + { + d = File.createTempFile( "neo4j-koans", "dir" ); + System.out.println( String.format( "Created a new Neo4j database at [%s]", d.getAbsolutePath() ) ); + } catch ( IOException e ) + { + throw new RuntimeException( e ); + } + if ( !d.delete() ) + { + throw new RuntimeException( "temp config directory pre-delete failed" ); + } + if ( !d.mkdirs() ) + { + throw new RuntimeException( "temp config directory not created" ); + } + d.deleteOnExit(); + return d; + } + + public static void ensureRelationshipInDb( Node startNode, RelationshipType relType, Node endNode, Map relationshipProperties ) + { + for ( Relationship r : startNode.getRelationships( relType, Direction.OUTGOING ) ) + { + if ( r.getEndNode() + .equals( endNode ) ) + { + return; + } + } + + Relationship relationship = startNode.createRelationshipTo( endNode, relType ); + + for ( String key : relationshipProperties.keySet() ) + { + relationship.setProperty( key, relationshipProperties.get( key ) ); + } + } + + public static void ensureRelationshipInDb( Node startNode, RelationshipType relType, Node endNode ) + { + ensureRelationshipInDb( startNode, relType, endNode, new HashMap() ); + } + + public void dumpGraphToConsole() + { + for ( Node n : db.getAllNodes() ) + { + Iterable propertyKeys = n.getPropertyKeys(); + for ( String key : propertyKeys ) + { + System.out.print( key + " : " ); + System.out.println( n.getProperty( key ) ); + } + } + } + + public int countNodesWithAllGivenProperties( Iterable allNodes, String... propertyNames ) + { + Iterator iterator = allNodes.iterator(); + int count = 0; + while ( iterator.hasNext() ) + { + Node next = iterator.next(); + + boolean hasAllPropertyNames = true; + for ( String propertyName : propertyNames ) + { + hasAllPropertyNames = hasAllPropertyNames && next.hasProperty( propertyName ); + if ( !hasAllPropertyNames ) + { + break; // Modest optimisation + } + } + if ( hasAllPropertyNames ) + { + count++; + } + } + return count; + } + + public int countNodes( Iterable allNodes ) + { + return destructivelyCount( allNodes ); + } + + public boolean nodeExistsInDatabase( Node node ) + { + return db.getNodeById( node.getId() ) != null; + } + + public int countRelationships( Iterable relationships ) + { + return destructivelyCount( relationships ); + } + + public void dumpNode( Node node ) + { + if ( node == null ) + { + System.out.println( "Null Node" ); + return; + } + System.out.println( String.format( "Node ID [%d]", node.getId() ) ); + for ( String key : node.getPropertyKeys() ) + { + System.out.print( key + " : " ); + System.out.println( node.getProperty( key ) ); + } + } + + public List toListOfRelationships( Iterable iterable ) + { + ArrayList rels = new ArrayList(); + for ( Relationship r : iterable ) + { + rels.add( r ); + } + return rels; + } + + public List toListOfNodes( Iterable nodes ) + { + ArrayList rels = new ArrayList(); + for ( Node n : nodes ) + { + rels.add( n ); + } + return rels; + } + + public int count( IndexHits indexHits ) + { + return destructivelyCount( indexHits ); + } + + private int destructivelyCount( Iterable iterable ) + { + int count = 0; + + for ( @SuppressWarnings("unused") Object o : iterable ) + { + count++; + } + + return count; + } +} diff --git a/src/main/java/org/neo4j/tutorial/DoctorWhoRelationships.java b/src/main/java/org/neo4j/tutorial/DoctorWhoRelationships.java new file mode 100644 index 0000000..11095d2 --- /dev/null +++ b/src/main/java/org/neo4j/tutorial/DoctorWhoRelationships.java @@ -0,0 +1,22 @@ +package org.neo4j.tutorial; + +import org.neo4j.graphdb.RelationshipType; + + +public enum DoctorWhoRelationships implements RelationshipType +{ + REGENERATED_TO, + PLAYED, + ENEMY_OF, + COMES_FROM, + IS_A, + COMPANION_OF, + APPEARED_IN, + USED_IN, + LOVES, + OWNS, + ALLY_OF, + COMPOSED_OF, + ORIGINAL_PROP, + MEMBER_OF +} diff --git a/src/main/java/org/neo4j/tutorial/DoctorWhoUniverseGenerator.java b/src/main/java/org/neo4j/tutorial/DoctorWhoUniverseGenerator.java new file mode 100644 index 0000000..d9a2322 --- /dev/null +++ b/src/main/java/org/neo4j/tutorial/DoctorWhoUniverseGenerator.java @@ -0,0 +1,56 @@ +package org.neo4j.tutorial; + +import org.neo4j.graphdb.GraphDatabaseService; + +public class DoctorWhoUniverseGenerator +{ + + private final String dbDir = DatabaseHelper.createTempDatabaseDir() + .getAbsolutePath(); + + public DoctorWhoUniverseGenerator() + { + GraphDatabaseService db = DatabaseHelper.createDatabase( dbDir ); + addCharacters( db ); + addSpecies( db ); + addPlanets( db ); + addEpisodes( db ); + addDalekProps( db ); + db.shutdown(); + } + + private void addEpisodes( GraphDatabaseService db ) + { + Episodes episodes = new Episodes( db ); + episodes.insert(); + } + + private void addCharacters( GraphDatabaseService db ) + { + Characters characters = new Characters( db ); + characters.insert(); + } + + private void addSpecies( GraphDatabaseService db ) + { + Species species = new Species( db ); + species.insert(); + } + + private void addPlanets( GraphDatabaseService db ) + { + Planets planets = new Planets( db ); + planets.insert(); + } + + private void addDalekProps( GraphDatabaseService db ) + { + DalekProps dalekProps = new DalekProps( db ); + dalekProps.insert(); + } + + public final String getDatabaseDirectory() + { + return dbDir; + } +} diff --git a/src/main/java/org/neo4j/tutorial/EmbeddedDoctorWhoUniverse.java b/src/main/java/org/neo4j/tutorial/EmbeddedDoctorWhoUniverse.java new file mode 100644 index 0000000..4de18c8 --- /dev/null +++ b/src/main/java/org/neo4j/tutorial/EmbeddedDoctorWhoUniverse.java @@ -0,0 +1,37 @@ +package org.neo4j.tutorial; + +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Node; +import org.neo4j.kernel.EmbeddedGraphDatabase; + +public class EmbeddedDoctorWhoUniverse +{ + + private final EmbeddedGraphDatabase db; + + public EmbeddedDoctorWhoUniverse( DoctorWhoUniverseGenerator universe ) + { + db = new EmbeddedGraphDatabase( universe.getDatabaseDirectory() ); + } + + public Node theDoctor() + { + return db.index() + .forNodes( "characters" ) + .get( "character", "Doctor" ) + .getSingle(); + } + + public void stop() + { + if ( db != null ) + { + db.shutdown(); + } + } + + public GraphDatabaseService getDatabase() + { + return db; + } +} diff --git a/src/main/java/org/neo4j/tutorial/EpisodeBuilder.java b/src/main/java/org/neo4j/tutorial/EpisodeBuilder.java new file mode 100644 index 0000000..b7c0a71 --- /dev/null +++ b/src/main/java/org/neo4j/tutorial/EpisodeBuilder.java @@ -0,0 +1,227 @@ +package org.neo4j.tutorial; + +import static org.neo4j.tutorial.CharacterBuilder.ensureAllyOfRelationshipInDb; +import static org.neo4j.tutorial.CharacterBuilder.ensureCompanionRelationshipInDb; +import static org.neo4j.tutorial.CharacterBuilder.ensureEnemyOfRelationshipInDb; +import static org.neo4j.tutorial.DatabaseHelper.ensureRelationshipInDb; + +import java.util.ArrayList; +import java.util.List; + +import org.neo4j.graphdb.Direction; +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Relationship; + +public class EpisodeBuilder +{ + + private String title; + private List companionNames = new ArrayList(); + private int episodeNumber = 0; + private List doctorActors = new ArrayList(); + private List enemySpecies = new ArrayList(); + private List enemies = new ArrayList(); + private String[] allies; + private List alliedSpecies = new ArrayList(); + + private EpisodeBuilder( int episodeNumber ) + { + this.episodeNumber = episodeNumber; + } + + public static EpisodeBuilder episode( int episodeNumber ) + { + return new EpisodeBuilder( episodeNumber ); + } + + public EpisodeBuilder title( String title ) + { + this.title = title; + return this; + } + + public EpisodeBuilder doctor( String actorName ) + { + doctorActors.add( actorName ); + return this; + } + + public EpisodeBuilder companion( String... namesOfCompanions ) + { + for ( String str : namesOfCompanions ) + { + companionNames.add( str ); + } + return this; + } + + public EpisodeBuilder enemySpecies( String... enemySpecies ) + { + for ( String str : enemySpecies ) + { + this.enemySpecies.add( str ); + } + return this; + } + + public EpisodeBuilder enemy( String... enemies ) + { + for ( String str : enemies ) + { + this.enemies.add( str ); + } + return this; + } + + public void fact( GraphDatabaseService db ) + { + checkEpisodeNumberAndTitle(); + + Node episode = ensureEpisodeNodeInDb( db ); + + ensureDoctorActorsAreInDb( db, episode ); + + if ( this.companionNames != null ) + { + for ( String companionName : companionNames ) + { + Node companionNode = CharacterBuilder.ensureCharacterIsInDb( companionName, db ); + companionNode.createRelationshipTo( episode, DoctorWhoRelationships.APPEARED_IN ); + ensureCompanionRelationshipInDb( companionNode, db ); + } + } + + if ( this.enemySpecies != null ) + { + for ( String eSpecies : enemySpecies ) + { + Node speciesNode = SpeciesBuilder.ensureSpeciesInDb( eSpecies, db ); + speciesNode.createRelationshipTo( episode, DoctorWhoRelationships.APPEARED_IN ); + ensureEnemyOfRelationshipInDb( speciesNode, db ); + } + } + + if ( this.enemies != null ) + { + for ( String enemy : enemies ) + { + Node enemyNode = CharacterBuilder.ensureCharacterIsInDb( enemy, db ); + enemyNode.createRelationshipTo( episode, DoctorWhoRelationships.APPEARED_IN ); + ensureEnemyOfRelationshipInDb( enemyNode, db ); + } + } + + if ( this.allies != null ) + { + for ( String ally : allies ) + { + Node allyNode = CharacterBuilder.ensureCharacterIsInDb( ally, db ); + allyNode.createRelationshipTo( episode, DoctorWhoRelationships.APPEARED_IN ); + ensureAllyOfRelationshipInDb( allyNode, db ); + } + } + + if ( this.alliedSpecies != null ) + { + for ( String aSpecies : alliedSpecies ) + { + Node speciesNode = SpeciesBuilder.ensureSpeciesInDb( aSpecies, db ); + speciesNode.createRelationshipTo( episode, DoctorWhoRelationships.APPEARED_IN ); + ensureAllyOfRelationshipInDb( speciesNode, db ); + } + } + + } + + private void ensureDoctorActorsAreInDb( GraphDatabaseService db, Node episode ) + { + if ( doctorActors != null ) + { + for ( String actor : doctorActors ) + { + Node actorNode = ensureDoctorActorInDb( actor, db ); + ensureRelationshipInDb( actorNode, DoctorWhoRelationships.APPEARED_IN, episode ); + } + } + } + + private Node ensureEpisodeNodeInDb( GraphDatabaseService db ) + { + Node episode = db.index() + .forNodes( "episodes" ) + .get( "title", this.title ) + .getSingle(); + + if ( episode == null ) + { + episode = db.createNode(); + episode.setProperty( "episode", episodeNumber ); + episode.setProperty( "title", title ); + } + + db.index() + .forNodes( "episodes" ) + .add( episode, "title", title ); + db.index() + .forNodes( "episodes" ) + .add( episode, "episode", episodeNumber ); + + return episode; + } + + private void checkEpisodeNumberAndTitle() + { + if ( title == null ) + { + throw new RuntimeException( "Episodes must have a title" ); + } + + if ( episodeNumber < 1 ) + { + throw new RuntimeException( "Episodes must have a number" ); + } + } + + private Node ensureDoctorActorInDb( String doctorActor, GraphDatabaseService db ) + { + Node theDoctor = db.index() + .forNodes( "characters" ) + .get( "character", "Doctor" ) + .getSingle(); + Iterable relationships = theDoctor.getRelationships( DoctorWhoRelationships.PLAYED, Direction.INCOMING ); + + for ( Relationship r : relationships ) + { + Node current = r.getStartNode(); + if ( current.getProperty( "actor" ) + .equals( doctorActor ) ) + { + return current; + } + } + + Node doctorActorNode = db.createNode(); + doctorActorNode.setProperty( "actor", doctorActor ); + doctorActorNode.createRelationshipTo( theDoctor, DoctorWhoRelationships.PLAYED ); + db.index() + .forNodes( "actors" ) + .add( doctorActorNode, "actor", doctorActor ); + return doctorActorNode; + } + + public EpisodeBuilder allies( String... allies ) + { + this.allies = allies; + return this; + } + + public EpisodeBuilder alliedSpecies( String... alliedSpecies ) + { + for ( String str : alliedSpecies ) + { + this.alliedSpecies.add( str ); + } + return this; + } +} diff --git a/src/main/java/org/neo4j/tutorial/Episodes.java b/src/main/java/org/neo4j/tutorial/Episodes.java new file mode 100644 index 0000000..aed5c22 --- /dev/null +++ b/src/main/java/org/neo4j/tutorial/Episodes.java @@ -0,0 +1,1355 @@ +package org.neo4j.tutorial; + +import static org.neo4j.tutorial.EpisodeBuilder.episode; + +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Transaction; + +public class Episodes +{ + + private final GraphDatabaseService db; + + public Episodes( GraphDatabaseService db ) + { + this.db = db; + } + + public void insert() + { + Transaction tx = db.beginTx(); + try + { + season01(); + season02(); + season03(); + season04(); + season05(); + season06(); + season07(); + season08(); + season09(); + season10(); + season11(); + season12(); + season13(); + season14(); + season15(); + season16(); + season17(); + season18(); + season19(); + season20(); + episode( 129 ).title( "The Five Doctors" ) + .doctor( "Richard Hurdnall" ) + .doctor( "William Hartnell" ) + .doctor( "Patrick Troughton" ) + .doctor( "Jon Pertwee" ) + .doctor( "Tom Baker" ) + .doctor( "Peter Davison" ) + .companion( "Tegan Jovanka", "Vislor Turlough", "Susan Foreman", "Sarah Jane Smith", "Romana" ) + .enemy( "Master" ) + .enemySpecies( "Dalek" ) + .fact( db ); + season21(); + season22(); + season23(); + season24(); + season25(); + season26(); + episode( 156 ).title( "Doctor Who" ) + .doctor( "Paul McGann" ) + .doctor( "Sylvester McCoy" ) + .companion( "Grace Holloway" ) + .enemy( "Master" ) + .fact( db ); + series01(); + episode( 167 ).title( "The Christmas Invasion" ) + .doctor( "David Tennant" ) + .companion( "Rose Tyler" ) + .enemySpecies( "Sycorax" ) + .fact( db ); + series02(); + episode( 178 ).title( "The Runaway Bride" ) + .doctor( "David Tennant" ) + .companion( "Donna Noble" ) + .enemy( "Empress of Racnoss" ) + .fact( db ); + series03(); + episode( 188 ).title( "Voyage of the Damned" ) + .doctor( "David Tennant" ) + .companion( "Astrid Peth" ) + .fact( db ); + series04(); + series05(); + episode( 213 ).title( "A Christmas Carol" ) + .doctor( "Matt Smith" ) + .companion( "Amy Pond", "Rory Williams" ) + .enemy( "Kazran Sardick" ) + .fact( db ); + series06(); + + tx.success(); + } finally + { + tx.finish(); + } + } + + private void series06() + { + episode( 214 ).title( "The Impossible Astronaut" ) + .doctor( "Matt Smith" ) + .companion( "Amy Pond", "Rory Williams", "River Song" ) + .allies( "Richard Nixon", "Canton Everett Delaware III" ) + .enemySpecies( "The Silence" ) + .fact( db ); + episode( 214 ).title( "Day of the Moon" ) + .doctor( "Matt Smith" ) + .companion( "Amy Pond", "Rory Williams", "River Song" ) + .enemySpecies( "The Silence" ) + .fact( db ); + episode( 215 ).title( "The Curse of the Black Spot" ) + .doctor( "Matt Smith" ) + .companion( "Amy Pond", "Rory Williams" ) + .fact( db ); + episode( 216 ).title( "The Doctor's Wife" ) + .doctor( "Matt Smith" ) + .companion( "Amy Pond", "Rory Williams" ) + .enemy( "House" ) + .fact( db ); + episode( 217 ).title( "The Rebel Flesh" ) + .doctor( "Matt Smith" ) + .companion( "Amy Pond", "Rory Williams" ) + .fact( db ); + episode( 217 ).title( "The Almost People" ) + .doctor( "Matt Smith" ) + .companion( "Amy Pond", "Rory Williams" ) + .fact( db ); + episode( 217 ).title( "A Good Man Goes to War" ) + .doctor( "Matt Smith" ) + .companion( "Amy Pond", "Rory Williams", "River Song" ) + .allies( "Commander Strax", "Madame Vastra", "Jenny", "Dorium Maldovar" ) + .alliedSpecies( "Judoon", "Silurian" ) + .enemySpecies( "Cyberman" ) + .enemy( "Madame Kovarian" ) + .fact( db ); + } + + private void series05() + { + episode( 199 ).title( "The Next Doctor" ) + .doctor( "David Tennant" ) + .companion( "Jackson Lake", "Rosita Farisi" ) + .enemy( "Miss Hartigan" ) + .enemySpecies( "Cyberman" ) + .fact( db ); + episode( 200 ).title( "Planet of the Dead" ) + .doctor( "David Tennant" ) + .companion( "Lady Christina de Souza" ) + .fact( db ); + episode( 201 ).title( "The Waters of Mars" ) + .doctor( "David Tennant" ) + .companion( "Adelaide Brooke" ) + .fact( db ); + episode( 202 ).title( "The End of Time" ) + .doctor( "David Tennant" ) + .doctor( "Matt Smith" ) + .companion( "Wilfred Mott" ) + .enemy( "Master", "Lord President" ) + .fact( db ); + episode( 203 ).title( "The Eleventh Hour" ) + .doctor( "Matt Smith" ) + .companion( "Amy Pond" ) + .enemy( "Prisoner Zero" ) + .fact( db ); + episode( 204 ).title( "The Beast Below" ) + .doctor( "Matt Smith" ) + .companion( "Amy Pond" ) + .enemy( "Prisoner Zero" ) + .fact( db ); + episode( 206 ).title( "Victory of the Daleks" ) + .doctor( "Matt Smith" ) + .companion( "Amy Pond" ) + .enemySpecies( "Dalek" ) + .fact( db ); + episode( 206 ).title( "The Time of Angels" ) + .doctor( "Matt Smith" ) + .companion( "Amy Pond" ) + .enemySpecies( "Weeping Angel" ) + .fact( db ); + episode( 206 ).title( "Flesh and Stone" ) + .doctor( "Matt Smith" ) + .companion( "Amy Pond" ) + .enemySpecies( "Weeping Angel" ) + .fact( db ); + episode( 207 ).title( "The Vampires of Venice" ) + .doctor( "Matt Smith" ) + .companion( "Amy Pond" ) + .enemy( "Signora Calvierri" ) + .fact( db ); + episode( 208 ).title( "Amy's Choice" ) + .doctor( "Matt Smith" ) + .companion( "Amy Pond", "Rory Williams" ) + .enemySpecies( "Eknodine" ) + .fact( db ); + episode( 209 ).title( "The Hungry Earth" ) + .doctor( "Matt Smith" ) + .companion( "Amy Pond", "Rory Williams" ) + .enemySpecies( "Silurian" ) + .fact( db ); + episode( 209 ).title( "Cold Blood" ) + .doctor( "Matt Smith" ) + .companion( "Amy Pond", "Rory Williams" ) + .enemySpecies( "Silurian" ) + .fact( db ); + episode( 210 ).title( "Vincent and the Doctor" ) + .doctor( "Matt Smith" ) + .companion( "Amy Pond" ) + .fact( db ); + episode( 211 ).title( "The Lodger" ) + .doctor( "Matt Smith" ) + .companion( "Amy Pond" ) + .fact( db ); + episode( 212 ).title( "The Pandorica Opens" ) + .doctor( "Matt Smith" ) + .companion( "Amy Pond", "Rory Williams" ) + .enemySpecies( "Dalek", "Auton", "Cyberman", "Sontaran", "Judoon", "Sycorax", "Hoix", "Silurian", + "Roboform" ) + .fact( db ); + episode( 212 ).title( "The Big Bang" ) + .doctor( "Matt Smith" ) + .companion( "Amy Pond", "Rory Williams" ) + .fact( db ); + } + + private void series04() + { + episode( 189 ).title( "Partners in Crime" ) + .doctor( "David Tennant" ) + .companion( "Donna Noble" ) + .enemy( "Miss Foster" ) + .fact( db ); + episode( 190 ).title( "The Fires of Pompeii" ) + .doctor( "David Tennant" ) + .companion( "Donna Noble" ) + .enemy( "Pyrovile" ) + .fact( db ); + episode( 191 ).title( "Planet of the Ood" ) + .doctor( "David Tennant" ) + .companion( "Donna Noble" ) + .fact( db ); + episode( 192 ).title( "The Sontaran Stratagem" ) + .doctor( "David Tennant" ) + .companion( "Donna Noble", "Martha Jones" ) + .enemy( "General Staal" ) + .fact( db ); + episode( 192 ).title( "The Poison Sky" ) + .doctor( "David Tennant" ) + .companion( "Donna Noble", "Martha Jones" ) + .enemy( "General Staal" ) + .fact( db ); + episode( 193 ).title( "The Doctor's Daughter" ) + .doctor( "David Tennant" ) + .companion( "Donna Noble", "Martha Jones" ) + .enemy( "General Cobb" ) + .fact( db ); + episode( 194 ).title( "The Unicorn and the Wasp" ) + .doctor( "David Tennant" ) + .companion( "Donna Noble" ) + .fact( db ); + episode( 195 ).title( "Silence in the Library" ) + .doctor( "David Tennant" ) + .companion( "Donna Noble" ) + .enemySpecies( "Vashta Nerada" ) + .fact( db ); + episode( 195 ).title( "Forest of the Dead" ) + .doctor( "David Tennant" ) + .companion( "Donna Noble" ) + .enemySpecies( "Vashta Nerada" ) + .fact( db ); + episode( 196 ).title( "Midnight" ) + .doctor( "David Tennant" ) + .companion( "Donna Noble" ) + .fact( db ); + episode( 197 ).title( "Turn Left" ) + .doctor( "David Tennant" ) + .companion( "Donna Noble", "Rose Tyler" ) + .fact( db ); + episode( 198 ).title( "The Stolen Earth" ) + .doctor( "David Tennant" ) + .companion( "Donna Noble", "Rose Tyler", "Martha Jones", "Jack Harkness", "Sarah Jane Smith" ) + .enemySpecies( "Dalek" ) + .fact( db ); + episode( 198 ).title( "Journey's End" ) + .doctor( "David Tennant" ) + .companion( "Donna Noble", "Rose Tyler", "Martha Jones", "Jack Harkness", "Sarah Jane Smith" ) + .enemySpecies( "Dalek" ) + .fact( db ); + } + + private void series03() + { + episode( 179 ).title( "Smith and Jones" ) + .doctor( "David Tennant" ) + .companion( "Martha Jones" ) + .enemySpecies( "Plasmavore" ) + .fact( db ); + episode( 180 ).title( "The Shakespeare Code" ) + .doctor( "David Tennant" ) + .companion( "Martha Jones" ) + .enemySpecies( "Carrionite" ) + .fact( db ); + episode( 181 ).title( "Gridlock" ) + .doctor( "David Tennant" ) + .companion( "Martha Jones" ) + .enemySpecies( "Macra" ) + .fact( db ); + episode( 182 ).title( "Daleks in Manhattan" ) + .doctor( "David Tennant" ) + .companion( "Martha Jones" ) + .enemySpecies( "Dalek" ) + .fact( db ); + episode( 182 ).title( "Evolution of the Daleks" ) + .doctor( "David Tennant" ) + .companion( "Martha Jones" ) + .enemySpecies( "Dalek" ) + .fact( db ); + episode( 183 ).title( "The Lazarus Experiment" ) + .doctor( "David Tennant" ) + .companion( "Martha Jones" ) + .enemy( "Lazarus" ) + .fact( db ); + episode( 184 ).title( "42" ) + .doctor( "David Tennant" ) + .companion( "Martha Jones" ) + .fact( db ); + episode( 185 ).title( "Human Nature" ) + .doctor( "David Tennant" ) + .companion( "Martha Jones" ) + .enemy( "Family of Blood" ) + .fact( db ); + episode( 185 ).title( "Family of Blood" ) + .doctor( "David Tennant" ) + .companion( "Martha Jones" ) + .enemy( "Family of Blood" ) + .fact( db ); + episode( 186 ).title( "Blink" ) + .doctor( "David Tennant" ) + .companion( "Martha Jones" ) + .enemySpecies( "Weeping Angel" ) + .fact( db ); + episode( 187 ).title( "Utopia" ) + .doctor( "David Tennant" ) + .companion( "Martha Jones", "Jack Harkness" ) + .enemy( "Master" ) + .fact( db ); + episode( 187 ).title( "The Sound of Drums" ) + .doctor( "David Tennant" ) + .companion( "Martha Jones", "Jack Harkness" ) + .enemy( "Master" ) + .fact( db ); + episode( 187 ).title( "Last of the Time Lords" ) + .doctor( "David Tennant" ) + .companion( "Martha Jones", "Jack Harkness" ) + .enemy( "Master" ) + .fact( db ); + } + + private void series02() + { + episode( 168 ).title( "New Earth" ) + .doctor( "David Tennant" ) + .companion( "Rose Tyler" ) + .fact( db ); + episode( 169 ).title( "Tooth and Claw" ) + .doctor( "David Tennant" ) + .companion( "Rose Tyler" ) + .fact( db ); + episode( 170 ).title( "School Reunion" ) + .doctor( "David Tennant" ) + .companion( "Rose Tyler", "Mickey Smith", "Sarah Jane Smith" ) + .enemySpecies( "Krillitane" ) + .fact( db ); + episode( 171 ).title( "The Girl in the Fireplace" ) + .doctor( "David Tennant" ) + .companion( "Rose Tyler", "Mickey Smith" ) + .enemySpecies( "Clockwork Android" ) + .fact( db ); + episode( 172 ).title( "Rise of the Cybermen" ) + .doctor( "David Tennant" ) + .companion( "Rose Tyler", "Mickey Smith" ) + .enemySpecies( "Cyberman" ) + .fact( db ); + episode( 172 ).title( "The Age of Steel" ) + .doctor( "David Tennant" ) + .companion( "Rose Tyler", "Mickey Smith" ) + .enemySpecies( "Cyberman" ) + .fact( db ); + episode( 173 ).title( "The Idiot's Lantern" ) + .doctor( "David Tennant" ) + .companion( "Rose Tyler" ) + .enemy( "The Wire" ) + .fact( db ); + episode( 174 ).title( "The Impossible Planet" ) + .doctor( "David Tennant" ) + .companion( "Rose Tyler" ) + .enemy( "Beast" ) + .fact( db ); + episode( 174 ).title( "The Satan Pit" ) + .doctor( "David Tennant" ) + .companion( "Rose Tyler" ) + .enemy( "Beast" ) + .fact( db ); + episode( 175 ).title( "Love & Monsters" ) + .doctor( "David Tennant" ) + .companion( "Rose Tyler" ) + .enemy( "Abzorbaloff" ) + .fact( db ); + episode( 176 ).title( "Fear Her" ) + .doctor( "David Tennant" ) + .companion( "Rose Tyler" ) + .fact( db ); + episode( 177 ).title( "Army of Ghosts" ) + .doctor( "David Tennant" ) + .companion( "Rose Tyler" ) + .enemySpecies( "Cyberman", "Dalek" ) + .fact( db ); + episode( 177 ).title( "Doomsday" ) + .doctor( "David Tennant" ) + .companion( "Rose Tyler" ) + .enemySpecies( "Cyberman", "Dalek" ) + .fact( db ); + } + + private void series01() + { + episode( 157 ).title( "Rose" ) + .doctor( "Christopher Eccleston" ) + .companion( "Rose Tyler" ) + .enemySpecies( "Auton" ) + .fact( db ); + episode( 158 ).title( "The End of the World" ) + .doctor( "Christopher Eccleston" ) + .companion( "Rose Tyler" ) + .enemy( "Cassandra" ) + .fact( db ); + episode( 159 ).title( "The Unquiet Dead" ) + .doctor( "Christopher Eccleston" ) + .companion( "Rose Tyler" ) + .enemy( "Gabriel Sneed" ) + .fact( db ); + episode( 160 ).title( "Aliens of London" ) + .doctor( "Christopher Eccleston" ) + .companion( "Rose Tyler" ) + .enemySpecies( "Slitheen" ) + .fact( db ); + episode( 160 ).title( "World War Three" ) + .doctor( "Christopher Eccleston" ) + .companion( "Rose Tyler" ) + .enemySpecies( "Slitheen" ) + .fact( db ); + episode( 161 ).title( "Dalek" ) + .doctor( "Christopher Eccleston" ) + .companion( "Rose Tyler" ) + .enemySpecies( "Dalek" ) + .fact( db ); + episode( 162 ).title( "The Long Game" ) + .doctor( "Christopher Eccleston" ) + .enemy( "The Editor" ) + .companion( "Rose Tyler" ) + .fact( db ); + episode( 163 ).title( "Father's Day" ) + .doctor( "Christopher Eccleston" ) + .companion( "Rose Tyler" ) + .fact( db ); + episode( 164 ).title( "The Empty Child" ) + .doctor( "Christopher Eccleston" ) + .companion( "Rose Tyler", "Jack Harkness" ) + .fact( db ); + episode( 164 ).title( "The Doctor Dances" ) + .doctor( "Christopher Eccleston" ) + .companion( "Rose Tyler", "Jack Harkness" ) + .fact( db ); + episode( 165 ).title( "Boom Town" ) + .doctor( "Christopher Eccleston" ) + .companion( "Rose Tyler", "Jack Harkness" ) + .enemySpecies( "Slitheen" ) + .fact( db ); + episode( 166 ).title( "Bad Wolf" ) + .doctor( "Christopher Eccleston" ) + .companion( "Rose Tyler", "Jack Harkness" ) + .enemySpecies( "Dalek" ) + .fact( db ); + episode( 166 ).title( "The Parting of the Ways" ) + .doctor( "Christopher Eccleston" ) + .doctor( "David Tennant" ) + .companion( "Rose Tyler", "Jack Harkness" ) + .enemySpecies( "Dalek" ) + .fact( db ); + } + + private void season26() + { + episode( 151 ).title( "Battlefield" ) + .doctor( "Sylvester McCoy" ) + .companion( "Ace" ) + .fact( db ); + episode( 152 ).title( "Ghost Light" ) + .doctor( "Sylvester McCoy" ) + .companion( "Ace" ) + .enemy( "Josiah Samuel Smith" ) + .fact( db ); + episode( 153 ).title( "The Curse of Fenric" ) + .doctor( "Sylvester McCoy" ) + .companion( "Ace" ) + .enemy( "Fenric" ) + .fact( db ); + episode( 154 ).title( "Survival" ) + .doctor( "Sylvester McCoy" ) + .companion( "Ace" ) + .enemy( "Master" ) + .fact( db ); + } + + private void season25() + { + episode( 148 ).title( "Remembrance of the Daleks" ) + .doctor( "Sylvester McCoy" ) + .companion( "Ace" ) + .enemy( "Davros" ) + .enemySpecies( "Dalek" ) + .fact( db ); + episode( 149 ).title( "The Happiness Patrol" ) + .doctor( "Sylvester McCoy" ) + .companion( "Ace" ) + .enemy( "Helen A" ) + .fact( db ); + episode( 150 ).title( "Silver Nemesis" ) + .doctor( "Sylvester McCoy" ) + .companion( "Ace" ) + .enemySpecies( "Cyberman" ) + .fact( db ); + episode( 151 ).title( "The Greatest Show in the Galaxy" ) + .doctor( "Sylvester McCoy" ) + .companion( "Ace" ) + .fact( db ); + } + + private void season24() + { + episode( 144 ).title( "Time and the Rani" ) + .doctor( "Colin Baker" ) + .doctor( "Sylvester McCoy" ) + .companion( "Melanie Bush" ) + .enemy( "Rani" ) + .fact( db ); + episode( 145 ).title( "Paradise Towers" ) + .doctor( "Sylvester McCoy" ) + .companion( "Melanie Bush" ) + .enemy( "Kroagnon" ) + .fact( db ); + episode( 146 ).title( "Delta and the Bannermen" ) + .doctor( "Sylvester McCoy" ) + .companion( "Melanie Bush" ) + .enemy( "Gavrok" ) + .fact( db ); + episode( 147 ).title( "Dragonfire" ) + .doctor( "Sylvester McCoy" ) + .companion( "Melanie Bush", "Ace" ) + .enemy( "Kane" ) + .fact( db ); + } + + private void season23() + { + episode( 143 ).title( "The Mysterious Planet" ) + .doctor( "Colin Baker" ) + .companion( "Peri Brown" ) + .fact( db ); + episode( 143 ).title( "Mindwarp" ) + .doctor( "Colin Baker" ) + .companion( "Peri Brown" ) + .fact( db ); + episode( 143 ).title( "Terror of the Vervoids" ) + .doctor( "Colin Baker" ) + .companion( "Melanie Bush" ) + .fact( db ); + episode( 143 ).title( "The Ultimate Foe" ) + .doctor( "Colin Baker" ) + .companion( "Melanie Bush" ) + .enemy( "Master" ) + .fact( db ); + } + + private void season22() + { + episode( 136 ).title( "The Twin Dilemma" ) + .doctor( "Colin Baker" ) + .companion( "Peri Brown" ) + .fact( db ); + episode( 137 ).title( "Attack of the Cybermen" ) + .doctor( "Colin Baker" ) + .companion( "Peri Brown" ) + .fact( db ); + episode( 138 ).title( "Vengeance on Varos" ) + .doctor( "Colin Baker" ) + .companion( "Peri Brown" ) + .fact( db ); + episode( 139 ).title( "The Mark of the Rani" ) + .doctor( "Colin Baker" ) + .companion( "Peri Brown" ) + .enemy( "Master", "Rani" ) + .fact( db ); + episode( 140 ).title( "The Two Doctors" ) + .doctor( "Colin Baker" ) + .doctor( "Patrick Troughton" ) + .companion( "Peri Brown", "Jamie McCrimmon" ) + .enemy( "Shockeye", "Chessene", "Dastari" ) + .fact( db ); + episode( 141 ).title( "Timelash" ) + .doctor( "Colin Baker" ) + .companion( "Peri Brown" ) + .enemy( "Borad" ) + .fact( db ); + episode( 142 ).title( "Revelation of the Daleks" ) + .doctor( "Colin Baker" ) + .companion( "Peri Brown" ) + .enemySpecies( "Dalek" ) + .fact( db ); + } + + private void season21() + { + episode( 130 ).title( "Warriors of the Deep" ) + .doctor( "Peter Davison" ) + .companion( "Tegan Jovanka", "Vislor Turlough" ) + .enemySpecies( "Silurian", "Sea Devil" ) + .fact( db ); + episode( 131 ).title( "The Awakening" ) + .doctor( "Peter Davison" ) + .companion( "Tegan Jovanka", "Vislor Turlough" ) + .enemy( "Malus" ) + .fact( db ); + episode( 132 ).title( "Frontios" ) + .doctor( "Peter Davison" ) + .companion( "Tegan Jovanka", "Vislor Turlough" ) + .enemySpecies( "Tractator" ) + .fact( db ); + episode( 133 ).title( "Resurrection of the Daleks" ) + .doctor( "Peter Davison" ) + .companion( "Tegan Jovanka", "Vislor Turlough" ) + .enemySpecies( "Dalek" ) + .fact( db ); + episode( 134 ).title( "Planet of Fire" ) + .doctor( "Peter Davison" ) + .companion( "Tegan Jovanka", "Vislor Turlough", "Peri Brown" ) + .enemy( "Master" ) + .fact( db ); + episode( 135 ).title( "The Caves of Androzani" ) + .doctor( "Peter Davison" ) + .doctor( "Colin Baker" ) + .companion( "Peri Brown" ) + .enemy( "Master" ) + .fact( db ); + } + + private void season20() + { + episode( 123 ).title( "Arc of Infinity" ) + .doctor( "Peter Davison" ) + .companion( "Nyssa", "Tegan Jovanka" ) + .enemy( "Omega" ) + .fact( db ); + episode( 124 ).title( "Snakedance" ) + .doctor( "Peter Davison" ) + .companion( "Nyssa", "Tegan Jovanka" ) + .enemy( "Mara" ) + .fact( db ); + episode( 125 ).title( "Mawdryn Undead" ) + .doctor( "Peter Davison" ) + .companion( "Nyssa", "Tegan Jovanka", "Vislor Turlough" ) + .enemy( "Mawdryn", "Black Guardian" ) + .fact( db ); + episode( 126 ).title( "Terminus" ) + .doctor( "Peter Davison" ) + .companion( "Nyssa", "Tegan Jovanka", "Vislor Turlough" ) + .enemy( "Vanir" ) + .fact( db ); + episode( 127 ).title( "Enlightenment" ) + .doctor( "Peter Davison" ) + .companion( "Tegan Jovanka", "Vislor Turlough" ) + .enemy( "Black Guardian" ) + .fact( db ); + episode( 128 ).title( "The King's Demons" ) + .doctor( "Peter Davison" ) + .companion( "Tegan Jovanka", "Vislor Turlough", "Kamelion" ) + .enemy( "Master" ) + .fact( db ); + } + + private void season19() + { + episode( 116 ).title( "Castrovalva" ) + .doctor( "Peter Davison" ) + .companion( "Adric", "Nyssa", "Tegan Jovanka" ) + .enemy( "Master" ) + .fact( db ); + episode( 117 ).title( "Four to Doomsday" ) + .doctor( "Peter Davison" ) + .companion( "Adric", "Nyssa", "Tegan Jovanka" ) + .fact( db ); + episode( 118 ).title( "Kinda" ) + .doctor( "Peter Davison" ) + .companion( "Adric", "Nyssa", "Tegan Jovanka" ) + .enemy( "Mara" ) + .fact( db ); + episode( 119 ).title( "The Visitation" ) + .doctor( "Peter Davison" ) + .companion( "Adric", "Nyssa", "Tegan Jovanka" ) + .enemy( "Terileptils" ) + .fact( db ); + episode( 120 ).title( "Black Orchid" ) + .doctor( "Peter Davison" ) + .companion( "Adric", "Nyssa", "Tegan Jovanka" ) + .fact( db ); + episode( 121 ).title( "Earthshock" ) + .doctor( "Peter Davison" ) + .companion( "Adric", "Nyssa", "Tegan Jovanka" ) + .enemySpecies( "Cyberman" ) + .fact( db ); + episode( 122 ).title( "Time-Flight" ) + .doctor( "Peter Davison" ) + .companion( "Nyssa", "Tegan Jovanka" ) + .enemy( "Master" ) + .fact( db ); + } + + private void season18() + { + episode( 109 ).title( "The Leisure Hive" ) + .doctor( "Tom Baker" ) + .companion( "Romana", "K9" ) + .fact( db ); + episode( 110 ).title( "Meglos" ) + .doctor( "Tom Baker" ) + .companion( "Romana", "K9" ) + .enemy( "Meglos" ) + .fact( db ); + episode( 111 ).title( "Full Circle" ) + .doctor( "Tom Baker" ) + .companion( "Romana", "K9", "Adric" ) + .enemySpecies( "Marshman" ) + .fact( db ); + episode( 112 ).title( "State of Decay" ) + .doctor( "Tom Baker" ) + .companion( "Romana", "K9", "Adric" ) + .enemy( "Zargo", "Camilla", "Aukon" ) + .fact( db ); + episode( 113 ).title( "Warriors' Gate" ) + .doctor( "Tom Baker" ) + .companion( "Romana", "K9", "Adric" ) + .fact( db ); + episode( 114 ).title( "The Keeper of Traken" ) + .doctor( "Tom Baker" ) + .companion( "Adric" ) + .enemy( "Master" ) + .fact( db ); + episode( 115 ).title( "Logopolis" ) + .doctor( "Tom Baker" ) + .companion( "Adric", "Nyssa", "Tegan Jovanka" ) + .enemy( "Master" ) + .fact( db ); + } + + private void season17() + { + episode( 104 ).title( "Destiny of the Daleks" ) + .doctor( "Tom Baker" ) + .companion( "Romana" ) + .enemySpecies( "Dalek" ) + .fact( db ); + episode( 105 ).title( "City of Death" ) + .doctor( "Tom Baker" ) + .companion( "Romana" ) + .enemy( "Scaroth" ) + .fact( db ); + episode( 106 ).title( "The Creature from the Pit" ) + .doctor( "Tom Baker" ) + .companion( "Romana", "K9" ) + .enemy( "Erato", "Lady Adrasta" ) + .fact( db ); + episode( 107 ).title( "Nightmare of Eden" ) + .doctor( "Tom Baker" ) + .companion( "Romana", "K9" ) + .enemySpecies( "Mandrel" ) + .fact( db ); + episode( 108 ).title( "The Horns of Nimon" ) + .doctor( "Tom Baker" ) + .companion( "Romana", "K9" ) + .enemySpecies( "Nimon" ) + .fact( db ); + } + + private void season16() + { + episode( 98 ).title( "The Ribos Operation" ) + .doctor( "Tom Baker" ) + .companion( "Romana", "K9" ) + .enemy( "Graff Vynda-K", "Black Guardian" ) + .fact( db ); + episode( 99 ).title( "The Pirate Planet" ) + .doctor( "Tom Baker" ) + .companion( "Romana", "K9" ) + .enemy( "Pirate Captain" ) + .fact( db ); + episode( 100 ).title( "The Stones of Blood" ) + .doctor( "Tom Baker" ) + .companion( "Romana", "K9" ) + .enemy( "de Vries" ) + .enemySpecies( "Ogri" ) + .fact( db ); + episode( 101 ).title( "The Androids of Tara" ) + .doctor( "Tom Baker" ) + .companion( "Romana", "K9" ) + .enemy( "Count Grendel of Gracht" ) + .fact( db ); + episode( 102 ).title( "The Power of Kroll" ) + .doctor( "Tom Baker" ) + .companion( "Romana" ) + .enemy( "Kroll" ) + .fact( db ); + episode( 103 ).title( "The Armageddon Factor" ) + .doctor( "Tom Baker" ) + .companion( "Romana", "K9" ) + .enemy( "The Shadow", "Black Guardian" ) + .fact( db ); + } + + private void season15() + { + episode( 92 ).title( "Horror of Fang Rock" ) + .doctor( "Tom Baker" ) + .companion( "Leela" ) + .fact( db ); + episode( 93 ).title( "The Invisible Enemy" ) + .doctor( "Tom Baker" ) + .companion( "Leela", "K9" ) + .enemy( "Nucleus" ) + .fact( db ); + episode( 94 ).title( "Image of the Fendahl" ) + .doctor( "Tom Baker" ) + .companion( "Leela" ) + .enemy( "Fendahl" ) + .fact( db ); + episode( 95 ).title( "The Sun Makers" ) + .doctor( "Tom Baker" ) + .companion( "Leela", "K9" ) + .enemy( "Collector" ) + .fact( db ); + episode( 96 ).title( "Underworld" ) + .doctor( "Tom Baker" ) + .companion( "Leela", "K9" ) + .enemy( "Oracle" ) + .fact( db ); + episode( 97 ).title( "The Invasion of Time" ) + .doctor( "Tom Baker" ) + .companion( "Leela", "K9" ) + .enemySpecies( "Sontaran" ) + .enemy( "Stor" ) + .fact( db ); + } + + private void season14() + { + episode( 86 ).title( "The Masque of Mandragora" ) + .doctor( "Tom Baker" ) + .companion( "Sarah Jane Smith" ) + .enemy( "Count Federico", "Captain Rossini" ) + .fact( db ); + episode( 87 ).title( "The Hand of Fear" ) + .doctor( "Tom Baker" ) + .companion( "Sarah Jane Smith" ) + .enemy( "Eldrad" ) + .fact( db ); + episode( 88 ).title( "The Deadly Assassin" ) + .doctor( "Tom Baker" ) + .enemy( "Master" ) + .fact( db ); + episode( 89 ).title( "The Face of Evil" ) + .doctor( "Tom Baker" ) + .companion( "Leela" ) + .enemy( "Xoanon" ) + .fact( db ); + episode( 90 ).title( "The Robots of Death" ) + .doctor( "Tom Baker" ) + .companion( "Leela" ) + .fact( db ); + episode( 91 ).title( "The Talons of Weng-Chiang" ) + .doctor( "Tom Baker" ) + .companion( "Leela" ) + .enemy( "Li H'sen Chang" ) + .fact( db ); + } + + private void season13() + { + episode( 80 ).title( "Terror of the Zygons" ) + .doctor( "Tom Baker" ) + .companion( "Sarah Jane Smith", "Harry Sullivan" ) + .enemy( "Skarasen" ) + .enemySpecies( "Zygon" ) + .fact( db ); + episode( 81 ).title( "Planet of Evil" ) + .doctor( "Tom Baker" ) + .companion( "Sarah Jane Smith" ) + .fact( db ); + episode( 82 ).title( "Pyramids of Mars" ) + .doctor( "Tom Baker" ) + .companion( "Sarah Jane Smith" ) + .enemy( "Sutekh" ) + .fact( db ); + episode( 83 ).title( "The Android Invasion" ) + .doctor( "Tom Baker" ) + .companion( "Sarah Jane Smith" ) + .enemySpecies( "Android" ) + .fact( db ); + episode( 84 ).title( "The Brain of Morbius" ) + .doctor( "Tom Baker" ) + .companion( "Sarah Jane Smith" ) + .enemy( "Morbius", "Doctor Solon" ) + .fact( db ); + episode( 85 ).title( "The Seeds of Doom" ) + .doctor( "Tom Baker" ) + .companion( "Sarah Jane Smith" ) + .enemy( "Harrison Chase" ) + .fact( db ); + } + + private void season12() + { + episode( 75 ).title( "Robot" ) + .doctor( "Tom Baker" ) + .companion( "Sarah Jane Smith", "Harry Sullivan" ) + .enemy( "K1 Robot" ) + .fact( db ); + episode( 76 ).title( "The Ark in Space" ) + .doctor( "Tom Baker" ) + .companion( "Sarah Jane Smith", "Harry Sullivan" ) + .fact( db ); + episode( 77 ).title( "The Sontaran Experiment" ) + .doctor( "Tom Baker" ) + .companion( "Sarah Jane Smith", "Harry Sullivan" ) + .enemy( "Styre" ) + .fact( db ); + episode( 78 ).title( "Genesis of the Daleks" ) + .doctor( "Tom Baker" ) + .companion( "Sarah Jane Smith", "Harry Sullivan" ) + .enemy( "Davros" ) + .enemySpecies( "Dalek" ) + .fact( db ); + episode( 79 ).title( "Revenge of the Cybermen" ) + .doctor( "Tom Baker" ) + .companion( "Sarah Jane Smith", "Harry Sullivan" ) + .enemy( "Cyberleader" ) + .enemySpecies( "Cyberman" ) + .fact( db ); + } + + private void season11() + { + episode( 70 ).title( "The Time Warrior" ) + .doctor( "Jon Pertwee" ) + .companion( "Sarah Jane Smith" ) + .enemy( "Linx" ) + .fact( db ); + episode( 71 ).title( "Invasion of the Dinosaurs" ) + .doctor( "Jon Pertwee" ) + .companion( "Sarah Jane Smith" ) + .fact( db ); + episode( 72 ).title( "Death to the Daleks" ) + .doctor( "Jon Pertwee" ) + .companion( "Sarah Jane Smith" ) + .enemySpecies( "Dalek" ) + .fact( db ); + episode( 73 ).title( "The Monster of Peladon" ) + .doctor( "Jon Pertwee" ) + .companion( "Sarah Jane Smith" ) + .enemy( "Chancellor Ortron" ) + .fact( db ); + episode( 74 ).title( "Planet of the Spiders" ) + .doctor( "Jon Pertwee" ) + .doctor( "Tom Baker" ) + .companion( "Sarah Jane Smith" ) + .fact( db ); + } + + private void season10() + { + episode( 65 ).title( "The Three Doctors" ) + .doctor( "Jon Pertwee" ) + .doctor( "Patrick Troughton" ) + .doctor( "William Hartnell" ) + .companion( "Jo Grant" ) + .enemy( "Omega" ) + .fact( db ); + episode( 66 ).title( "Carnival of Monsters" ) + .doctor( "Jon Pertwee" ) + .companion( "Jo Grant" ) + .fact( db ); + episode( 67 ).title( "Frontier in Space" ) + .doctor( "Jon Pertwee" ) + .companion( "Jo Grant" ) + .enemy( "Master" ) + .enemySpecies( "Dalek" ) + .fact( db ); + episode( 68 ).title( "Planet of the Daleks" ) + .doctor( "Jon Pertwee" ) + .companion( "Jo Grant" ) + .enemySpecies( "Dalek" ) + .fact( db ); + episode( 69 ).title( "The Green Death" ) + .doctor( "Jon Pertwee" ) + .companion( "Jo Grant" ) + .enemy( "BOSS" ) + .fact( db ); + } + + private void season09() + { + episode( 60 ).title( "Day of the Daleks" ) + .doctor( "Jon Pertwee" ) + .companion( "Jo Grant" ) + .enemySpecies( "Dalek" ) + .fact( db ); + episode( 61 ).title( "The Curse of Peladon" ) + .doctor( "Jon Pertwee" ) + .companion( "Jo Grant" ) + .fact( db ); + episode( 62 ).title( "The Sea Devils" ) + .doctor( "Jon Pertwee" ) + .companion( "Jo Grant" ) + .enemy( "Master" ) + .enemySpecies( "Sea Devil" ) + .fact( db ); + episode( 63 ).title( "The Mutants" ) + .doctor( "Jon Pertwee" ) + .enemy( "The Marshal" ) + .companion( "Jo Grant" ) + .fact( db ); + episode( 64 ).title( "The Time Monster" ) + .doctor( "Jon Pertwee" ) + .enemy( "Master" ) + .companion( "Jo Grant" ) + .fact( db ); + } + + private void season08() + { + episode( 55 ).title( "Terror of the Autons" ) + .doctor( "Jon Pertwee" ) + .companion( "Jo Grant" ) + .enemy( "Master" ) + .enemySpecies( "Auton" ) + .fact( db ); + episode( 56 ).title( "The Mind of Evil" ) + .doctor( "Jon Pertwee" ) + .companion( "Jo Grant" ) + .enemy( "Master" ) + .fact( db ); + episode( 57 ).title( "The Claws of Axos" ) + .doctor( "Jon Pertwee" ) + .companion( "Jo Grant" ) + .enemy( "Master" ) + .enemySpecies( "Axon" ) + .fact( db ); + episode( 58 ).title( "Colony in Space" ) + .doctor( "Jon Pertwee" ) + .companion( "Jo Grant" ) + .enemy( "Master" ) + .fact( db ); + episode( 59 ).title( "The Daemons" ) + .doctor( "Jon Pertwee" ) + .companion( "Jo Grant" ) + .enemy( "Bok", "Master" ) + .fact( db ); + } + + private void season07() + { + episode( 51 ).title( "Spearhead from Space" ) + .doctor( "Jon Pertwee" ) + .companion( "Liz Shaw" ) + .enemySpecies( "Auton" ) + .fact( db ); + episode( 52 ).title( "Doctor Who and the Silurians" ) + .doctor( "Jon Pertwee" ) + .companion( "Liz Shaw" ) + .enemySpecies( "Silurian" ) + .fact( db ); + episode( 53 ).title( "The Ambassadors of Death" ) + .doctor( "Jon Pertwee" ) + .companion( "Liz Shaw" ) + .enemy( "Reegan" ) + .fact( db ); + episode( 54 ).title( "Inferno" ) + .doctor( "Jon Pertwee" ) + .companion( "Liz Shaw" ) + .fact( db ); + } + + private void season06() + { + episode( 44 ).title( "The Dominators" ) + .doctor( "Patrick Troughton" ) + .companion( "Jamie McCrimmon", "Zoe Heriot" ) + .enemySpecies( "Dominator", "Quark" ) + .fact( db ); + episode( 45 ).title( "The Mind Robber" ) + .doctor( "Patrick Troughton" ) + .companion( "Jamie McCrimmon", "Zoe Heriot" ) + .enemy( "Master" ) + .fact( db ); + episode( 46 ).title( "The Invasion" ) + .doctor( "Patrick Troughton" ) + .companion( "Jamie McCrimmon", "Zoe Heriot" ) + .fact( db ); + episode( 47 ).title( "The Krotons" ) + .doctor( "Patrick Troughton" ) + .companion( "Jamie McCrimmon", "Zoe Heriot" ) + .enemySpecies( "Kroton" ) + .fact( db ); + episode( 48 ).title( "The Seeds of Death" ) + .doctor( "Patrick Troughton" ) + .companion( "Jamie McCrimmon", "Zoe Heriot" ) + .enemySpecies( "Ice Warrior" ) + .fact( db ); + episode( 49 ).title( "The Space Pirates" ) + .doctor( "Patrick Troughton" ) + .companion( "Jamie McCrimmon", "Zoe Heriot" ) + .enemy( "Caven", "Dervish" ) + .fact( db ); + episode( 50 ).title( "The War Games" ) + .doctor( "Patrick Troughton" ) + .companion( "Jamie McCrimmon", "Zoe Heriot" ) + .enemy( "War Chief" ) + .enemySpecies( "Dalek" ) + .fact( db ); + } + + private void season05() + { + episode( 37 ).title( "The Tomb of the Cybermen" ) + .doctor( "Patrick Troughton" ) + .companion( "Jamie McCrimmon", "Victoria Waterfield" ) + .enemySpecies( "Cyberman" ) + .fact( db ); + episode( 38 ).title( "The Abominable Snowmen" ) + .doctor( "Patrick Troughton" ) + .companion( "Jamie McCrimmon", "Victoria Waterfield" ) + .fact( db ); + episode( 39 ).title( "The Ice Warriors" ) + .doctor( "Patrick Troughton" ) + .companion( "Jamie McCrimmon", "Victoria Waterfield" ) + .enemySpecies( "Ice Warrior" ) + .fact( db ); + episode( 40 ).title( "The Enemy of the World" ) + .doctor( "Patrick Troughton" ) + .companion( "Jamie McCrimmon", "Victoria Waterfield" ) + .fact( db ); + episode( 41 ).title( "The Web of Fear" ) + .doctor( "Patrick Troughton" ) + .companion( "Jamie McCrimmon", "Victoria Waterfield" ) + .fact( db ); + episode( 42 ).title( "Fury from the Deep" ) + .doctor( "Patrick Troughton" ) + .companion( "Jamie McCrimmon", "Victoria Waterfield" ) + .fact( db ); + episode( 43 ).title( "The Wheel in Space" ) + .doctor( "Patrick Troughton" ) + .companion( "Jamie McCrimmon", "Victoria Waterfield" ) + .enemySpecies( "Cyberman" ) + .fact( db ); + } + + private void season04() + { + episode( 30 ).title( "The Power of the Daleks" ) + .doctor( "Patrick Troughton" ) + .companion( "Polly", "Ben Jackson" ) + .enemySpecies( "Dalek" ) + .fact( db ); + episode( 31 ).title( "The Highlanders" ) + .doctor( "Patrick Troughton" ) + .companion( "Polly", "Ben Jackson", "Jamie McCrimmon" ) + .fact( db ); + episode( 32 ).title( "The Underwater Menace" ) + .doctor( "Patrick Troughton" ) + .companion( "Polly", "Ben Jackson", "Jamie McCrimmon" ) + .enemy( "Zaroff" ) + .fact( db ); + episode( 33 ).title( "The Moonbase" ) + .doctor( "Patrick Troughton" ) + .companion( "Polly", "Ben Jackson", "Jamie McCrimmon" ) + .enemySpecies( "Cyberman" ) + .fact( db ); + episode( 34 ).title( "The Macra Terror" ) + .doctor( "Patrick Troughton" ) + .companion( "Polly", "Ben Jackson", "Jamie McCrimmon" ) + .enemySpecies( "Macra" ) + .fact( db ); + episode( 35 ).title( "The Faceless Ones" ) + .doctor( "Patrick Troughton" ) + .companion( "Polly", "Ben Jackson", "Jamie McCrimmon" ) + .fact( db ); + episode( 36 ).title( "The Evil of the Daleks" ) + .doctor( "Patrick Troughton" ) + .companion( "Jamie McCrimmon", "Victoria Waterfield" ) + .enemySpecies( "Dalek" ) + .fact( db ); + } + + private void season03() + { + episode( 18 ).title( "Galaxy 4" ) + .doctor( "William Hartnell" ) + .companion( "Vicki", "Steven Taylor" ) + .enemySpecies( "Drahvin" ) + .fact( db ); + episode( 19 ).title( "Mission to the Unknown" ) + .fact( db ); + episode( 20 ).title( "The Myth Makers" ) + .doctor( "William Hartnell" ) + .companion( "Vicki", "Steven Taylor", "Katarina" ) + .fact( db ); + episode( 21 ).title( "The Daleks' Master Plan" ) + .doctor( "William Hartnell" ) + .companion( "Steven Taylor", "Katarina", "Sara Kingdom" ) + .enemySpecies( "Dalek" ) + .fact( db ); + episode( 22 ).title( "The Massacre of St Bartholomew's Eve" ) + .doctor( "William Hartnell" ) + .companion( "Dodo Chaplet", "Steven Taylor" ) + .fact( db ); + episode( 23 ).title( "The Ark" ) + .doctor( "William Hartnell" ) + .companion( "Dodo Chaplet", "Steven Taylor" ) + .fact( db ); + episode( 24 ).title( "The Celestial Toymaker" ) + .doctor( "William Hartnell" ) + .companion( "Dodo Chaplet", "Steven Taylor" ) + .enemy( "The Toymaker" ) + .fact( db ); + episode( 25 ).title( "The Gunfighters" ) + .doctor( "William Hartnell" ) + .companion( "Dodo Chaplet", "Steven Taylor" ) + .fact( db ); + episode( 26 ).title( "The Savages" ) + .doctor( "William Hartnell" ) + .companion( "Dodo Chaplet", "Steven Taylor" ) + .fact( db ); + episode( 27 ).title( "The War Machines" ) + .doctor( "William Hartnell" ) + .companion( "Dodo Chaplet", "Steven Taylor", "Polly" ) + .enemy( "WOTAN" ) + .fact( db ); + episode( 28 ).title( "The Smugglers" ) + .doctor( "William Hartnell" ) + .companion( "Polly", "Ben Jackson" ) + .fact( db ); + episode( 29 ).title( "The Tenth Planet" ) + .doctor( "William Hartnell" ) + .doctor( "Patrick Troughton" ) + .companion( "Polly", "Ben Jackson" ) + .fact( db ); + } + + private void season02() + { + episode( 9 ).title( "Planet of Giants" ) + .doctor( "William Hartnell" ) + .companion( "Susan Foreman", "Ian Chesterton", "Barbara Wright" ) + .fact( db ); + episode( 10 ).title( "The Dalek Invasion of Earth" ) + .doctor( "William Hartnell" ) + .companion( "Susan Foreman", "Ian Chesterton", "Barbara Wright" ) + .enemySpecies( "Dalek" ) + .fact( db ); + episode( 11 ).title( "The Rescue" ) + .doctor( "William Hartnell" ) + .companion( "Vicki", "Ian Chesterton", "Barbara Wright" ) + .fact( db ); + episode( 12 ).title( "The Romans" ) + .doctor( "William Hartnell" ) + .companion( "Vicki", "Ian Chesterton", "Barbara Wright" ) + .fact( db ); + episode( 13 ).title( "The Web Planet" ) + .doctor( "William Hartnell" ) + .companion( "Susan Foreman", "Ian Chesterton", "Barbara Wright" ) + .fact( db ); + episode( 14 ).title( "The Crusade" ) + .doctor( "William Hartnell" ) + .companion( "Susan Foreman", "Ian Chesterton", "Barbara Wright" ) + .fact( db ); + episode( 15 ).title( "The Space Museum" ) + .doctor( "William Hartnell" ) + .companion( "Susan Foreman", "Ian Chesterton", "Barbara Wright" ) + .enemySpecies( "Dalek" ) + .fact( db ); + episode( 16 ).title( "The Chase" ) + .doctor( "William Hartnell" ) + .companion( "Susan Foreman", "Ian Chesterton", "Barbara Wright", "Steven Taylor" ) + .enemySpecies( "Dalek" ) + .fact( db ); + episode( 17 ).title( "The Time Meddler" ) + .doctor( "William Hartnell" ) + .companion( "Vicki", "Steven Taylor" ) + .enemy( "Meddling Monk" ) + .fact( db ); + } + + private void season01() + { + episode( 1 ).title( "An Unearthly Child" ) + .doctor( "William Hartnell" ) + .companion( "Susan Foreman", "Ian Chesterton", "Barbara Wright" ) + .enemy( "Stone Age Tribe" ) + .fact( db ); + episode( 2 ).title( "The Daleks" ) + .doctor( "William Hartnell" ) + .companion( "Susan Foreman", "Ian Chesterton", "Barbara Wright" ) + .enemySpecies( "Dalek" ) + .fact( db ); + episode( 3 ).title( "The Edge of Destruction" ) + .doctor( "William Hartnell" ) + .companion( "Susan Foreman", "Ian Chesterton", "Barbara Wright" ) + .fact( db ); + episode( 4 ).title( "Marco Polo" ) + .doctor( "William Hartnell" ) + .companion( "Susan Foreman", "Ian Chesterton", "Barbara Wright" ) + .enemy( "Tegana" ) + .fact( db ); + episode( 5 ).title( "The Keys of Marinus" ) + .doctor( "William Hartnell" ) + .companion( "Susan Foreman", "Ian Chesterton", "Barbara Wright" ) + .enemy( "Yartek" ) + .fact( db ); + episode( 6 ).title( "The Aztecs" ) + .doctor( "William Hartnell" ) + .companion( "Susan Foreman", "Ian Chesterton", "Barbara Wright" ) + .enemy( "Tlotoxl" ) + .fact( db ); + episode( 7 ).title( "The Sensorites" ) + .doctor( "William Hartnell" ) + .companion( "Susan Foreman", "Ian Chesterton", "Barbara Wright" ) + .enemySpecies( "Sensorite" ) + .fact( db ); + episode( 8 ).title( "The Reign of Terror" ) + .doctor( "William Hartnell" ) + .companion( "Susan Foreman", "Ian Chesterton", "Barbara Wright" ) + .enemy( "Robespierre" ) + .enemy( "Napoleon" ) + .fact( db ); + } + +} diff --git a/src/main/java/org/neo4j/tutorial/PlanetBuilder.java b/src/main/java/org/neo4j/tutorial/PlanetBuilder.java new file mode 100644 index 0000000..16aabf9 --- /dev/null +++ b/src/main/java/org/neo4j/tutorial/PlanetBuilder.java @@ -0,0 +1,45 @@ +package org.neo4j.tutorial; + +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Node; + +public class PlanetBuilder +{ + + private final String planetName; + + public static PlanetBuilder planet( String planetName ) + { + return new PlanetBuilder( planetName ); + } + + private PlanetBuilder( String planetName ) + { + this.planetName = planetName; + } + + public void fact( GraphDatabaseService db ) + { + ensurePlanetInDb( planetName, db ); + } + + public static Node ensurePlanetInDb( String planet, GraphDatabaseService db ) + { + + Node planetNode = db.index() + .forNodes( "planets" ) + .get( "planet", planet ) + .getSingle(); + + if ( planetNode == null ) + { + planetNode = db.createNode(); + planetNode.setProperty( "planet", planet ); + db.index() + .forNodes( "planets" ) + .add( planetNode, "planet", planet ); + } + + return planetNode; + } +} diff --git a/src/main/java/org/neo4j/tutorial/Planets.java b/src/main/java/org/neo4j/tutorial/Planets.java new file mode 100644 index 0000000..601d05b --- /dev/null +++ b/src/main/java/org/neo4j/tutorial/Planets.java @@ -0,0 +1,476 @@ +package org.neo4j.tutorial; + +import static org.neo4j.tutorial.PlanetBuilder.planet; + +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Transaction; + +public class Planets +{ + + private final GraphDatabaseService db; + + public Planets( GraphDatabaseService db ) + { + this.db = db; + } + + public void insert() + { + Transaction tx = db.beginTx(); + try + { + planet( "4-X-Alpha-4" ).fact( db ); + planet( "Abydos" ).fact( db ); + planet( "Adipose 3" ).fact( db ); + planet( "Agora" ).fact( db ); + planet( "Alfava Metraxis" ).fact( db ); + planet( "Algol" ).fact( db ); + planet( "Alpha Canis One" ).fact( db ); + planet( "Althrace" ).fact( db ); + planet( "Alvega" ).fact( db ); + planet( "Alzarius" ).fact( db ); + planet( "Amanopia" ).fact( db ); + planet( "Anagonia" ).fact( db ); + planet( "Anathema" ).fact( db ); + planet( "Androzani Major" ).fact( db ); + planet( "Aneth" ).fact( db ); + planet( "Anima Persis" ).fact( db ); + planet( "Anura" ).fact( db ); + planet( "Aractus" ).fact( db ); + planet( "Arcadia" ).fact( db ); + planet( "Arcateen V" ).fact( db ); + planet( "Archetryx" ).fact( db ); + planet( "Arden" ).fact( db ); + planet( "Argolis" ).fact( db ); + planet( "Argos" ).fact( db ); + planet( "Aridius" ).fact( db ); + planet( "Arkannis Major" ).fact( db ); + planet( "Arkheon" ).fact( db ); + planet( "Artaris" ).fact( db ); + planet( "Asgard" ).fact( db ); + planet( "Astra" ).fact( db ); + planet( "Atrios" ).fact( db ); + planet( "Augea " ).fact( db ); + planet( "Auros" ).fact( db ); + planet( "Avalon" ).fact( db ); + planet( "Axista Four" ).fact( db ); + planet( "Azure" ).fact( db ); + planet( "Balhoon" ).fact( db ); + planet( "Bandraginus 5" ).fact( db ); + planet( "Bandril" ).fact( db ); + planet( "Bane World" ).fact( db ); + planet( "Barcelona" ).fact( db ); + planet( "Bel" ).fact( db ); + planet( "Belannia II" ).fact( db ); + planet( "Belannia IV" ).fact( db ); + planet( "Belepheron" ).fact( db ); + planet( "Bellaphores" ).fact( db ); + planet( "Bessan" ).fact( db ); + planet( "Beta Two" ).fact( db ); + planet( "Betelgeuse" ).fact( db ); + planet( "Betrushia" ).fact( db ); + planet( "Bi" ).fact( db ); + planet( "Blenhorm Ogin" ).fact( db ); + planet( "Blestinu" ).fact( db ); + planet( "Blini-Gaar" ).fact( db ); + planet( "Bliss" ).fact( db ); + planet( "Bonarcha Anarda" ).fact( db ); + planet( "Boromeo" ).fact( db ); + planet( "Bortresoye" ).fact( db ); + planet( "Bruydac" ).fact( db ); + planet( "Brus" ).fact( db ); + planet( "Bouken" ).fact( db ); + planet( "Calliopticon" ).fact( db ); + planet( "Calufrax" ).fact( db ); + planet( "Calufrax Minor" ).fact( db ); + planet( "Carsus" ).fact( db ); + planet( "Cassius" ).fact( db ); + planet( "Castor 36" ).fact( db ); + planet( "Castrovalva" ).fact( db ); + planet( "Catastrophea" ).fact( db ); + planet( "Catrigan Nova" ).fact( db ); + planet( "Centauri Seven" ).fact( db ); + planet( "Cep Cassalon" ).fact( db ); + planet( "Chavic Five" ).fact( db ); + planet( "Cheem" ).fact( db ); + planet( "Cheetah Planet" ).fact( db ); + planet( "Chelonia" ).fact( db ); + planet( "Chimeria" ).fact( db ); + planet( "Chimera IV" ).fact( db ); + planet( "Chloris" ).fact( db ); + planet( "Chronos" ).fact( db ); + planet( "Cinethon" ).fact( db ); + planet( "Clom" ).fact( db ); + planet( "Colano Alpha" ).fact( db ); + planet( "Collactin" ).fact( db ); + planet( "Cotter Palluni's World" ).fact( db ); + planet( "Crafe Tec Heydra" ).fact( db ); + planet( "Crespallion" ).fact( db ); + planet( "Crestus" ).fact( db ); + planet( "Crinoth" ).fact( db ); + planet( "Cyrennis Minima" ).fact( db ); + planet( "Daemos" ).fact( db ); + planet( "Dar" ).fact( db ); + planet( "Darkheart" ).fact( db ); + planet( "Darp" ).fact( db ); + planet( "Darillium" ).fact( db ); + planet( "Delphon" ).fact( db ); + planet( "Delta Magna'" ).fact( db ); + planet( "Desperus" ).fact( db ); + planet( "Deva Loka" ).fact( db ); + planet( "Dhakan" ).fact( db ); + planet( "Diadem" ).fact( db ); + planet( "Dido" ).fact( db ); + planet( "Dioscuros" ).fact( db ); + planet( "Diplos" ).fact( db ); + planet( "Discurus" ).fact( db ); + planet( "Draconia" ).fact( db ); + planet( "Dramos" ).fact( db ); + planet( "Drahva" ).fact( db ); + planet( "Dravidia" ).fact( db ); + planet( "Dronid" ).fact( db ); + planet( "Duchamp 331" ).fact( db ); + planet( "Dulkis" ).fact( db ); + planet( "Earth" ).fact( db ); + planet( "Eden" ).fact( db ); + planet( "Ephte Major" ).fact( db ); + planet( "Enlandia" ).fact( db ); + planet( "Epsilon Four Zero Gamma" ).fact( db ); + planet( "Esto" ).fact( db ); + planet( "Eudamus" ).fact( db ); + planet( "Exarius" ).fact( db ); + planet( "Exxilon" ).fact( db ); + planet( "Eye of Orion" ).fact( db ); + planet( "Fagiros" ).fact( db ); + planet( "Felspoon" ).fact( db ); + planet( "The Fifth Planet" ).fact( db ); + planet( "Fisar" ).fact( db ); + planet( "Flane" ).fact( db ); + planet( "Florana" ).fact( db ); + planet( "Freytus" ).fact( db ); + planet( "Frontios" ).fact( db ); + planet( "Galaxis Bright" ).fact( db ); + planet( "Gallifrey" ).fact( db ); + planet( "Galsec Seven" ).fact( db ); + planet( "Gameworld Gamma" ).fact( db ); + planet( "Gauda Prime" ).fact( db ); + planet( "Gidu" ).fact( db ); + planet( "Glasson Minor" ).fact( db ); + planet( "Golo" ).fact( db ); + planet( "Gond Homeworld" ).fact( db ); + planet( "Gotta Floco" ).fact( db ); + planet( "Grajick Major" ).fact( db ); + planet( "Granados" ).fact( db ); + planet( "Gratt" ).fact( db ); + planet( "Griffoth" ).fact( db ); + planet( "Griophos" ).fact( db ); + planet( "Grold Homeworld" ).fact( db ); + planet( "Grolon" ).fact( db ); + planet( "Grundle" ).fact( db ); + planet( "Hakol (aka. Harkol)" ).fact( db ); + planet( "Halcya" ).fact( db ); + planet( "Halergan Three" ).fact( db ); + planet( "Hastus Minor" ).fact( db ); + planet( "Heaven" ).fact( db ); + planet( "Hedron" ).fact( db ); + planet( "Heiradi" ).fact( db ); + planet( "Hell" ).fact( db ); + planet( "Hermethica" ).fact( db ); + planet( "Hurala" ).fact( db ); + planet( "Hyspero" ).fact( db ); + planet( "Hydropellica Hydroxi" ).fact( db ); + planet( "Indigo 3" ).fact( db ); + planet( "Inter Minor" ).fact( db ); + planet( "Iphitus" ).fact( db ); + planet( "Jaconda" ).fact( db ); + planet( "Jahoo" ).fact( db ); + planet( "Jalian 17" ).fact( db ); + planet( "Jan Francis IX" ).fact( db ); + planet( "Junk" ).fact( db ); + planet( "Jupiter" ).fact( db ); + planet( "Justicia" ).fact( db ); + planet( "Kalakiki" ).fact( db ); + planet( "Kalaya" ).fact( db ); + planet( "Kaldor" ).fact( db ); + planet( "Kantra" ).fact( db ); + planet( "Kanval" ).fact( db ); + planet( "Kapteyn 5" ).fact( db ); + planet( "Kar-Charrat" ).fact( db ); + planet( "Karfel" ).fact( db ); + planet( "Karn" ).fact( db ); + planet( "Karas don Kazra don Slava" ).fact( db ); + planet( "Karris" ).fact( db ); + planet( "Kas" ).fact( db ); + planet( "Kastopheria" ).fact( db ); + planet( "Kastria" ).fact( db ); + planet( "Katakiki" ).fact( db ); + planet( "Katuria" ).fact( db ); + planet( "Kegron Pluva" ).fact( db ); + planet( "Kem" ).fact( db ); + planet( "Kinjana" ).fact( db ); + planet( "Kirith" ).fact( db ); + planet( "Klechton" ).fact( db ); + planet( "Kolkokron" ).fact( db ); + planet( "Kosnax" ).fact( db ); + planet( "Kreme" ).fact( db ); + planet( "Krillia" ).fact( db ); + planet( "Krontep" ).fact( db ); + planet( "Krop Tor" ).fact( db ); + planet( "Kurhan" ).fact( db ); + planet( "Kylos" ).fact( db ); + planet( "Kyrol" ).fact( db ); + planet( "Lakertya" ).fact( db ); + planet( "Laylora" ).fact( db ); + planet( "Leela's World" ).fact( db ); + planet( "Lelex" ).fact( db ); + planet( "Leophantos" ).fact( db ); + planet( "Levithia" ).fact( db ); + planet( "The Li" ).fact( db ); + planet( "Limus 4" ).fact( db ); + planet( "Livonia" ).fact( db ); + planet( "Loam" ).fact( db ); + planet( "Lo" ).fact( db ); + planet( "Logopolis" ).fact( db ); + planet( "Lonsis" ).fact( db ); + planet( "Lowitelom" ).fact( db ); + planet( "Lucifer" ).fact( db ); + planet( "Lurma" ).fact( db ); + planet( "Lvan (aka. Luan)" ).fact( db ); + planet( "Magellan" ).fact( db ); + planet( "Magla" ).fact( db ); + planet( "Magnus" ).fact( db ); + planet( "Malcassairo" ).fact( db ); + planet( "Manussa" ).fact( db ); + planet( "Marinus" ).fact( db ); + planet( "Marpesia" ).fact( db ); + planet( "Mars" ).fact( db ); + planet( "Mechanus" ).fact( db ); + planet( "Melagophon" ).fact( db ); + planet( "Melissa Majoria" ).fact( db ); + planet( "Mer" ).fact( db ); + planet( "Mesmerus" ).fact( db ); + planet( "Messaline" ).fact( db ); + planet( "Metallurgis 5" ).fact( db ); + planet( "Meta Sigmafolio" ).fact( db ); + planet( "Meta Vorka 6" ).fact( db ); + planet( "Mete" ).fact( db ); + planet( "Metralu" ).fact( db ); + planet( "Miasimia Goria" ).fact( db ); + planet( "Midnight" ).fact( db ); + planet( "Minyos" ).fact( db ); + planet( "Mira" ).fact( db ); + planet( "Mira" ).fact( db ); + planet( "Mo" ).fact( db ); + planet( "Mogar" ).fact( db ); + planet( "Mondaran" ).fact( db ); + planet( "Mondas" ).fact( db ); + planet( "Morestra" ).fact( db ); + planet( "Morok" ).fact( db ); + planet( "Museum of the Last Ones" ).fact( db ); + planet( "Muscolane" ).fact( db ); + planet( "Myarr" ).fact( db ); + planet( "Navaros" ).fact( db ); + planet( "Necros" ).fact( db ); + planet( "Nefrin" ).fact( db ); + planet( "Neogorgon" ).fact( db ); + planet( "New Alexandria" ).fact( db ); + planet( "New Earth" ).fact( db ); + planet( "New Savannah" ).fact( db ); + planet( "New Venus" ).fact( db ); + planet( "Nooma" ).fact( db ); + planet( "Nyrruh 4" ).fact( db ); + planet( "Oberon" ).fact( db ); + planet( "Oblivion" ).fact( db ); + planet( "Ockora" ).fact( db ); + planet( "Ogros" ).fact( db ); + planet( "Olympus" ).fact( db ); + planet( "Omphalos" ).fact( db ); + planet( "One" ).fact( db ); + planet( "Oseidon" ).fact( db ); + planet( "Oskerion" ).fact( db ); + planet( "Othrys" ).fact( db ); + planet( "Overod" ).fact( db ); + planet( "Padrivole Regency 9" ).fact( db ); + planet( "Pandatorea" ).fact( db ); + planet( "Parakon" ).fact( db ); + planet( "Paradost" ).fact( db ); + planet( "Peladon" ).fact( db ); + planet( "Pen Haxico 2" ).fact( db ); + planet( "Peri" ).fact( db ); + planet( "Phaester Osiris" ).fact( db ); + planet( "Pheros" ).fact( db ); + planet( "Phryxus" ).fact( db ); + planet( "Pictos" ).fact( db ); + planet( "Planet 1" ).fact( db ); + planet( "Pluto" ).fact( db ); + planet( "Polymos" ).fact( db ); + planet( "Polongus" ).fact( db ); + planet( "Poosh" ).fact( db ); + planet( "Ponton" ).fact( db ); + planet( "Posikar" ).fact( db ); + planet( "Proamon" ).fact( db ); + planet( "Proxima Centauri" ).fact( db ); + planet( "Pyro Shika" ).fact( db ); + planet( "Pyrovilia" ).fact( db ); + planet( "Qualactin" ).fact( db ); + planet( "Quinnis" ).fact( db ); + planet( "Raaga" ).fact( db ); + planet( "Rago Rago Five Six Rago" ).fact( db ); + planet( "Ralafea" ).fact( db ); + planet( "Ranx" ).fact( db ); + planet( "Ravolox" ).fact( db ); + planet( "Raxacoricofallapatorius" ).fact( db ); + planet( "Re" ).fact( db ); + planet( "Red Rocket Rising" ).fact( db ); + planet( "Red Sky Lost" ).fact( db ); + planet( "Refusis II" ).fact( db ); + planet( "Reja Magnum" ).fact( db ); + planet( "Rex Vox Jax" ).fact( db ); + planet( "Rexel 4" ).fact( db ); + planet( "Ri" ).fact( db ); + planet( "Riftan Five" ).fact( db ); + planet( "Rigel Beta 5" ).fact( db ); + planet( "Rit" ).fact( db ); + planet( "Ruta 3" ).fact( db ); + planet( "Ruta Magnum" ).fact( db ); + planet( "Ry'leh" ).fact( db ); + planet( "S14" ).fact( db ); + planet( "Salarius" ).fact( db ); + planet( "Salostophus" ).fact( db ); + planet( "Salvak" ).fact( db ); + planet( "San Helios" ).fact( db ); + planet( "San Kaloon" ).fact( db ); + planet( "Santiny" ).fact( db ); + planet( "Sant's World" ).fact( db ); + planet( "Sarn" ).fact( db ); + planet( "Saturnyne" ).fact( db ); + planet( "Sava" ).fact( db ); + planet( "Scalpor" ).fact( db ); + planet( "Scrantek" ).fact( db ); + planet( "Scotia" ).fact( db ); + planet( "Segonax" ).fact( db ); + planet( "Sense-Sphere" ).fact( db ); + planet( "Shada" ).fact( db ); + planet( "Shadmoch" ).fact( db ); + planet( "Shallacatop" ).fact( db ); + planet( "Shan Shen" ).fact( db ); + planet( "Shantella Prime" ).fact( db ); + planet( "Sigma" ).fact( db ); + planet( "Siralos" ).fact( db ); + planet( "Sireen" ).fact( db ); + planet( "Sirius IV" ).fact( db ); + planet( "Sirius V" ).fact( db ); + planet( "Skaar" ).fact( db ); + planet( "Skaro" ).fact( db ); + planet( "Skonnos" ).fact( db ); + planet( "Skythros" ).fact( db ); + planet( "The Slough" ).fact( db ); + planet( "Solos" ).fact( db ); + planet( "Sontar" ).fact( db ); + planet( "Spiridon" ).fact( db ); + planet( "Splendurosa" ).fact( db ); + planet( "Starfall" ).fact( db ); + planet( "Stella Stora" ).fact( db ); + planet( "Sto" ).fact( db ); + planet( "Strepto" ).fact( db ); + planet( "Stricium" ).fact( db ); + planet( "Sunday" ).fact( db ); + planet( "Svartos" ).fact( db ); + planet( "Sycorax" ).fact( db ); + planet( "Sylvaniar" ).fact( db ); + planet( "Sza" ).fact( db ); + planet( "Ta" ).fact( db ); + planet( "Tara" ).fact( db ); + planet( "Tarsius" ).fact( db ); + planet( "Taurean Nomeworld" ).fact( db ); + planet( "Telos" ).fact( db ); + planet( "Tenten 10" ).fact( db ); + planet( "Terileptus" ).fact( db ); + planet( "Terra Alpha" ).fact( db ); + planet( "Terra Beta" ).fact( db ); + planet( "Terradon" ).fact( db ); + planet( "Tersurus" ).fact( db ); + planet( "Tetrapyriar" ).fact( db ); + planet( "Thegeros" ).fact( db ); + planet( "Thera" ).fact( db ); + planet( "Therka" ).fact( db ); + planet( "Therra" ).fact( db ); + planet( "Thordon" ).fact( db ); + planet( "Thoros Alpha" ).fact( db ); + planet( "Thrace" ).fact( db ); + planet( "Thuron" ).fact( db ); + planet( "Tiermann's World" ).fact( db ); + planet( "Tigella" ).fact( db ); + planet( "Tigus" ).fact( db ); + planet( "Tisar" ).fact( db ); + planet( "Titan" ).fact( db ); + planet( "Titan 3" ).fact( db ); + planet( "Titania" ).fact( db ); + planet( "Tokl" ).fact( db ); + planet( "Toop" ).fact( db ); + planet( "Torajii Alpha" ).fact( db ); + planet( "Traken" ).fact( db ); + planet( "Trieste" ).fact( db ); + planet( "Tranquela" ).fact( db ); + planet( "Trion" ).fact( db ); + planet( "Triton" ).fact( db ); + planet( "Tythonus" ).fact( db ); + planet( "Um" ).fact( db ); + planet( "Unicepter IV" ).fact( db ); + planet( "Uranus" ).fact( db ); + planet( "Ur" ).fact( db ); + planet( "Usurius" ).fact( db ); + planet( "Utopia" ).fact( db ); + planet( "UX-4732" ).fact( db ); + planet( "Uxarieus" ).fact( db ); + planet( "Vampire Planet" ).fact( db ); + planet( "Vandos" ).fact( db ); + planet( "Varos" ).fact( db ); + planet( "Vardon" ).fact( db ); + planet( "Varnicon" ).fact( db ); + planet( "Vasilip" ).fact( db ); + planet( "Vel Consadine" ).fact( db ); + planet( "Venessia" ).fact( db ); + planet( "Venus" ).fact( db ); + planet( "Verd" ).fact( db ); + planet( "Verticulus" ).fact( db ); + planet( "Veturia" ).fact( db ); + planet( "Vij" ).fact( db ); + planet( "Viperon" ).fact( db ); + planet( "Vita 15" ).fact( db ); + planet( "Voga" ).fact( db ); + planet( "Volag-Noc" ).fact( db ); + planet( "Vollotha" ).fact( db ); + planet( "Voracia" ).fact( db ); + planet( "Vortis" ).fact( db ); + planet( "Vulcan" ).fact( db ); + planet( "Vulpana" ).fact( db ); + planet( "Wilson 1" ).fact( db ); + planet( "Woldyhool" ).fact( db ); + planet( "Woman Wept" ).fact( db ); + planet( "Xenon" ).fact( db ); + planet( "Xeros" ).fact( db ); + planet( "Xeriphas" ).fact( db ); + planet( "Yegros Alpha" ).fact( db ); + planet( "Zaakros" ).fact( db ); + planet( "Zamper" ).fact( db ); + planet( "Zanak" ).fact( db ); + planet( "Zazz" ).fact( db ); + planet( "Zeen 4" ).fact( db ); + planet( "Zeos" ).fact( db ); + planet( "Zephon" ).fact( db ); + planet( "Zeta Major" ).fact( db ); + planet( "Zeta Minor" ).fact( db ); + planet( "Zil" ).fact( db ); + planet( "Zolfa Thura" ).fact( db ); + planet( "Zom" ).fact( db ); + planet( "Zygor" ).fact( db ); + + tx.success(); + } finally + { + tx.finish(); + } + } +} diff --git a/src/main/java/org/neo4j/tutorial/ServerDoctorWhoUniverse.java b/src/main/java/org/neo4j/tutorial/ServerDoctorWhoUniverse.java new file mode 100644 index 0000000..0a7a3a3 --- /dev/null +++ b/src/main/java/org/neo4j/tutorial/ServerDoctorWhoUniverse.java @@ -0,0 +1,80 @@ +package org.neo4j.tutorial; + +import java.util.Map; + +import javax.ws.rs.core.MediaType; + +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.WebResource; +import com.sun.jersey.api.client.config.ClientConfig; +import com.sun.jersey.api.client.config.DefaultClientConfig; +import org.neo4j.server.NeoServerWithEmbeddedWebServer; +import org.neo4j.server.rest.domain.JsonHelper; +import org.neo4j.server.rest.domain.JsonParseException; +import org.neo4j.tutorial.server.ServerBuilder; +import org.neo4j.tutorial.server.rest.FunctionalTestHelper; + +public class ServerDoctorWhoUniverse +{ + + private final NeoServerWithEmbeddedWebServer server; + + public ServerDoctorWhoUniverse( DoctorWhoUniverseGenerator universe ) throws Exception + { + super(); + server = ServerBuilder + .server() + .usingDatabaseDir( universe.getDatabaseDirectory() ) + .build(); + server.start(); + } + + public Map theDoctor() + { + return getJsonFor( getUriFromIndex( "characters", "character", "Doctor" ) ); + } + + public Map getJsonFor( String uri ) + { + ClientConfig config = new DefaultClientConfig(); + Client client = Client.create( config ); + WebResource resource = client.resource( uri ); + String response = resource.accept( MediaType.APPLICATION_JSON ).get( + String.class ); + try + { + return JsonHelper.jsonToMap( response ); + } catch ( JsonParseException e ) + { + throw new RuntimeException( "Invalid response when looking up Doctor node" ); + } + } + + public String getUriFromIndex( String indexName, String key, String value ) + { + ClientConfig config = new DefaultClientConfig(); + Client client = Client.create( config ); + WebResource resource = client.resource( new FunctionalTestHelper( server ) + .indexNodeUri( indexName, key, value ) ); + String response = resource.accept( MediaType.APPLICATION_JSON ).get( + String.class ); + try + { + return JsonHelper.jsonToList( response ).get( 0 ).get( "self" ).toString(); + } catch ( JsonParseException e ) + { + throw new RuntimeException( "Invalid response when looking up node" ); + } + } + + void stop() + { + server.stop(); + } + + public NeoServerWithEmbeddedWebServer getServer() + { + return server; + } + +} diff --git a/src/main/java/org/neo4j/tutorial/Species.java b/src/main/java/org/neo4j/tutorial/Species.java new file mode 100644 index 0000000..5749ef2 --- /dev/null +++ b/src/main/java/org/neo4j/tutorial/Species.java @@ -0,0 +1,101 @@ +package org.neo4j.tutorial; + +import static org.neo4j.tutorial.SpeciesBuilder.species; + +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Transaction; + +public class Species +{ + + private final GraphDatabaseService db; + + public Species( GraphDatabaseService db ) + { + this.db = db; + } + + public void insert() + { + Transaction tx = db.beginTx(); + try + { + species( "Timelord" ).isEnemyOfSpecies( "Dalek" ) + .isFrom( "Gallifrey" ) + .fact( db ); + species( "Abrobvian" ).isEnemyOf( "Doctor" ) + .isFrom( "Clom" ) + .fact( db ); + species( "Android" ).fact( db ); + species( "Auton" ).isEnemyOf( "Doctor" ) + .isEnemyOfSpecies( "Human" ) + .isFrom( "Polymos" ) + .fact( db ); + species( "Axon" ).isEnemyOf( "Doctor" ) + .isEnemyOfSpecies( "Human" ) + .fact( db ); + species( "Devil" ).isEnemyOf( "Doctor", "Rose Tyler" ) + .isFrom( "Impossible Planet" ) + .fact( db ); + species( "Cyberman" ).isEnemyOf( "Doctor" ) + .isEnemyOfSpecies( "Dalek" ) + .isFrom( "Mondas" ) + .fact( db ); + species( "Dalek" ).isEnemyOf( "Doctor" ) + .isEnemyOfSpecies( "Cyberman", "Thaal", "Mechonoids", "Human" ) + .isFrom( "Skaro" ) + .fact( db ); + species( "Gargoyle" ).isEnemyOf( "Doctor" ) + .fact( db ); + species( "Ice Warrior" ).isEnemyOf( "Doctor" ) + .isFrom( "Mars" ) + .fact( db ); + species( "Human" ).isFrom( "Earth" ) + .fact( db ); + species( "Humanoid" ).fact( db ); + species( "Jagrafess" ).isEnemyOf( "Doctor" ) + .fact( db ); + species( "Jagaroth" ).fact( db ); + species( "Kaled" ).isEnemyOf( "Doctor" ) + .isFrom( "Skaro" ) + .fact( db ); + species( "Kastrian" ).isFrom( "Kastria" ) + .fact( db ); + species( "Mechonoids" ).isFrom( "Mechanus" ) + .fact( db ); + species( "Ood" ).isFrom( "Ood Sphere" ) + .fact( db ); + species( "Osiron" ).isEnemyOf( "Doctor" ) + .fact( db ); + species( "Robotic Canine" ).fact( db ); + species( "Sea Devil" ).isEnemyOf( "Doctor" ) + .isEnemyOfSpecies( "Human" ) + .isFrom( "Earth" ) + .fact( db ); + species( "Silurian" ).isEnemyOf( "Doctor" ) + .isEnemyOfSpecies( "Human" ) + .isFrom( "Earth" ) + .fact( db ); + species( "Skarasen" ).isEnemyOf( "Doctor" ) + .fact( db ); + species( "Slitheen" ).isEnemyOf( "Doctor" ) + .isEnemyOfSpecies( "Human" ) + .isFrom( "Raxacoricofallapatorius" ) + .fact( db ); + species( "Sontaran" ).isEnemyOf( "Doctor", "Martha Jones" ) + .isEnemyOfSpecies( "Human" ) + .isFrom( "Sontar" ) + .fact( db ); + species( "Trion" ).isFrom( "Trion" ) + .fact( db ); + species( "Vashta Nerada" ).isEnemyOf( "Doctor", "Donna Noble" ) + .fact( db ); + species( "Voord" ).fact( db ); + tx.success(); + } finally + { + tx.finish(); + } + } + +} diff --git a/src/main/java/org/neo4j/tutorial/SpeciesBuilder.java b/src/main/java/org/neo4j/tutorial/SpeciesBuilder.java new file mode 100644 index 0000000..3edca4d --- /dev/null +++ b/src/main/java/org/neo4j/tutorial/SpeciesBuilder.java @@ -0,0 +1,108 @@ +package org.neo4j.tutorial; + +import static org.neo4j.tutorial.DatabaseHelper.ensureRelationshipInDb; + +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Node; + +public class SpeciesBuilder +{ + + private final String speciesName; + private String planet; + private String[] enemies; + private String[] enemySpecies; + + public static SpeciesBuilder species( String speciesName ) + { + return new SpeciesBuilder( speciesName ); + } + + private SpeciesBuilder( String speciesName ) + { + this.speciesName = speciesName; + } + + public void fact( GraphDatabaseService db ) + { + Node speciesNode = ensureSpeciesInDb( speciesName, db ); + + if ( planet != null ) + { + Node planetNode = PlanetBuilder.ensurePlanetInDb( planet, db ); + ensureRelationshipInDb( speciesNode, DoctorWhoRelationships.COMES_FROM, planetNode ); + } + + if ( enemies != null ) + { + for ( String enemy : enemies ) + { + Node enemyNode = CharacterBuilder.ensureCharacterIsInDb( enemy, db ); + ensureRelationshipInDb( enemyNode, DoctorWhoRelationships.ENEMY_OF, speciesNode ); + ensureRelationshipInDb( speciesNode, DoctorWhoRelationships.ENEMY_OF, enemyNode ); + } + } + + if ( enemySpecies != null ) + { + for ( String eSpecies : enemySpecies ) + { + Node enemySpeciesNode = ensureSpeciesInDb( eSpecies, db ); + ensureRelationshipInDb( enemySpeciesNode, DoctorWhoRelationships.ENEMY_OF, speciesNode ); + ensureRelationshipInDb( speciesNode, DoctorWhoRelationships.ENEMY_OF, enemySpeciesNode ); + } + } + } + + public static Node ensureSpeciesInDb( String theSpecies, GraphDatabaseService db ) + { + ensureArgumentsAreSane( theSpecies, db ); + + Node speciesNode = db.index() + .forNodes( "species" ) + .get( "species", theSpecies ) + .getSingle(); + + if ( speciesNode == null ) + { + speciesNode = db.createNode(); + speciesNode.setProperty( "species", theSpecies ); + db.index() + .forNodes( "species" ) + .add( speciesNode, "species", theSpecies ); + } + + return speciesNode; + } + + private static void ensureArgumentsAreSane( String theSpecies, GraphDatabaseService db ) + { + if ( theSpecies == null ) + { + throw new RuntimeException( "Must provide a value for the species to the species builder" ); + } + + if ( db == null ) + { + throw new RuntimeException( "Must provide a value for the universe to the species builder" ); + } + } + + public SpeciesBuilder isFrom( String planet ) + { + this.planet = planet; + return this; + } + + public SpeciesBuilder isEnemyOf( String... enemies ) + { + this.enemies = enemies; + return this; + } + + public SpeciesBuilder isEnemyOfSpecies( String... enemySpecies ) + { + this.enemySpecies = enemySpecies; + return this; + } +} diff --git a/src/main/java/org/neo4j/tutorial/server/LocalhostAddressResolver.java b/src/main/java/org/neo4j/tutorial/server/LocalhostAddressResolver.java new file mode 100644 index 0000000..95fa71a --- /dev/null +++ b/src/main/java/org/neo4j/tutorial/server/LocalhostAddressResolver.java @@ -0,0 +1,11 @@ +package org.neo4j.tutorial.server; + +import org.neo4j.server.AddressResolver; + +public class LocalhostAddressResolver extends AddressResolver +{ + public String getHostname() + { + return "localhost"; + } +} diff --git a/src/main/java/org/neo4j/tutorial/server/ServerBuilder.java b/src/main/java/org/neo4j/tutorial/server/ServerBuilder.java new file mode 100644 index 0000000..4915266 --- /dev/null +++ b/src/main/java/org/neo4j/tutorial/server/ServerBuilder.java @@ -0,0 +1,273 @@ +package org.neo4j.tutorial.server; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.neo4j.tutorial.server.ServerTestUtils.createTempDir; +import static org.neo4j.tutorial.server.ServerTestUtils.createTempPropertyFile; +import static org.neo4j.tutorial.server.ServerTestUtils.writePropertiesToFile; +import static org.neo4j.tutorial.server.ServerTestUtils.writePropertyToFile; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Properties; + +import org.neo4j.server.AddressResolver; +import org.neo4j.server.NeoServerBootstrapper; +import org.neo4j.server.NeoServerWithEmbeddedWebServer; +import org.neo4j.server.configuration.Configurator; +import org.neo4j.server.configuration.PropertyFileConfigurator; +import org.neo4j.server.configuration.validation.DatabaseLocationMustBeSpecifiedRule; +import org.neo4j.server.configuration.validation.Validator; +import org.neo4j.server.modules.DiscoveryModule; +import org.neo4j.server.modules.ManagementApiModule; +import org.neo4j.server.modules.RESTApiModule; +import org.neo4j.server.modules.ServerModule; +import org.neo4j.server.modules.ThirdPartyJAXRSModule; +import org.neo4j.server.modules.WebAdminModule; +import org.neo4j.server.startup.healthcheck.StartupHealthCheck; +import org.neo4j.server.startup.healthcheck.StartupHealthCheckRule; +import org.neo4j.server.web.Jetty6WebServer; + +public class ServerBuilder +{ + + private String portNo = "7474"; + private String dbDir = null; + private String webAdminUri = "/db/manage/"; + private String webAdminDataUri = "/db/data/"; + private StartupHealthCheck startupHealthCheck; + private AddressResolver addressResolver = new LocalhostAddressResolver(); + private final HashMap thirdPartyPackages = new HashMap(); + + private static enum WhatToDo + { + CREATE_GOOD_TUNING_FILE, + CREATE_DANGLING_TUNING_FILE_PROPERTY, + CREATE_CORRUPT_TUNING_FILE + } + + private WhatToDo action; + private List> serverModules = null; + + public static ServerBuilder server() + { + return new ServerBuilder(); + } + + @SuppressWarnings("unchecked") + public NeoServerWithEmbeddedWebServer build() throws IOException + { + if ( dbDir == null ) + { + this.dbDir = createTempDir().getAbsolutePath(); + } + File configFile = createPropertiesFiles(); + + if ( serverModules == null ) + { + withSpecificServerModulesOnly( RESTApiModule.class, WebAdminModule.class, ManagementApiModule.class, + ThirdPartyJAXRSModule.class, DiscoveryModule.class ); + } + + if ( startupHealthCheck == null ) + { + startupHealthCheck = mock( StartupHealthCheck.class ); + when( startupHealthCheck.run() ).thenReturn( true ); + } + + return new NeoServerWithEmbeddedWebServer( new NeoServerBootstrapper(), addressResolver, startupHealthCheck, + new PropertyFileConfigurator( new Validator( new DatabaseLocationMustBeSpecifiedRule() ), configFile ), + new Jetty6WebServer(), serverModules ); + + } + + public File createPropertiesFiles() throws IOException + { + File temporaryConfigFile = createTempPropertyFile(); + + createPropertiesFile( temporaryConfigFile ); + createTuningFile( temporaryConfigFile ); + + return temporaryConfigFile; + } + + private void createPropertiesFile( File temporaryConfigFile ) + { + writePropertyToFile( Configurator.DATABASE_LOCATION_PROPERTY_KEY, dbDir, temporaryConfigFile ); + if ( portNo != null ) + { + writePropertyToFile( Configurator.WEBSERVER_PORT_PROPERTY_KEY, portNo, temporaryConfigFile ); + } + writePropertyToFile( Configurator.MANAGEMENT_PATH_PROPERTY_KEY, webAdminUri, temporaryConfigFile ); + writePropertyToFile( Configurator.REST_API_PATH_PROPERTY_KEY, webAdminDataUri, temporaryConfigFile ); + + if ( thirdPartyPackages.keySet() + .size() > 0 ) + { + writePropertiesToFile( Configurator.THIRD_PARTY_PACKAGES_KEY, thirdPartyPackages, temporaryConfigFile ); + } + } + + private void createTuningFile( File temporaryConfigFile ) throws IOException + { + if ( action == WhatToDo.CREATE_GOOD_TUNING_FILE ) + { + File databaseTuningPropertyFile = createTempPropertyFile(); + writePropertyToFile( "neostore.nodestore.db.mapped_memory", "25M", databaseTuningPropertyFile ); + writePropertyToFile( "neostore.relationshipstore.db.mapped_memory", "50M", databaseTuningPropertyFile ); + writePropertyToFile( "neostore.propertystore.db.mapped_memory", "90M", databaseTuningPropertyFile ); + writePropertyToFile( "neostore.propertystore.db.strings.mapped_memory", "130M", databaseTuningPropertyFile ); + writePropertyToFile( "neostore.propertystore.db.arrays.mapped_memory", "130M", databaseTuningPropertyFile ); + writePropertyToFile( Configurator.DB_TUNING_PROPERTY_FILE_KEY, + databaseTuningPropertyFile.getAbsolutePath(), temporaryConfigFile ); + } else if ( action == WhatToDo.CREATE_DANGLING_TUNING_FILE_PROPERTY ) + { + writePropertyToFile( Configurator.DB_TUNING_PROPERTY_FILE_KEY, createTempPropertyFile().getAbsolutePath(), + temporaryConfigFile ); + } else if ( action == WhatToDo.CREATE_CORRUPT_TUNING_FILE ) + { + File corruptTuningFile = trashFile(); + writePropertyToFile( Configurator.DB_TUNING_PROPERTY_FILE_KEY, corruptTuningFile.getAbsolutePath(), + temporaryConfigFile ); + } + } + + private File trashFile() throws IOException + { + File f = createTempPropertyFile(); + + FileWriter fstream = new FileWriter( f, true ); + BufferedWriter out = new BufferedWriter( fstream ); + + for ( int i = 0; i < 100; i++ ) + { + out.write( (int) System.currentTimeMillis() ); + } + + out.close(); + return f; + } + + private ServerBuilder() + { + } + + public ServerBuilder onPort( int portNo ) + { + this.portNo = String.valueOf( portNo ); + return this; + } + + public ServerBuilder usingDatabaseDir( String dbDir ) + { + this.dbDir = dbDir; + return this; + } + + public ServerBuilder withRelativeWebAdminUriPath( String webAdminUri ) + { + try + { + URI theUri = new URI( webAdminUri ); + if ( theUri.isAbsolute() ) + { + this.webAdminUri = theUri.getPath(); + } else + { + this.webAdminUri = theUri.toString(); + } + } catch ( URISyntaxException e ) + { + throw new RuntimeException( e ); + } + return this; + } + + public ServerBuilder withRelativeWebDataAdminUriPath( String webAdminDataUri ) + { + try + { + URI theUri = new URI( webAdminDataUri ); + if ( theUri.isAbsolute() ) + { + this.webAdminDataUri = theUri.getPath(); + } else + { + this.webAdminDataUri = theUri.toString(); + } + } catch ( URISyntaxException e ) + { + throw new RuntimeException( e ); + } + return this; + } + + public ServerBuilder withoutWebServerPort() + { + portNo = null; + return this; + } + + public ServerBuilder withNetworkBoundHostnameResolver() + { + addressResolver = new AddressResolver(); + return this; + } + + public ServerBuilder withFailingStartupHealthcheck() + { + startupHealthCheck = mock( StartupHealthCheck.class ); + when( startupHealthCheck.run() ).thenReturn( false ); + when( startupHealthCheck.failedRule() ).thenReturn( new StartupHealthCheckRule() + { + + public String getFailureMessage() + { + return "mockFailure"; + } + + public boolean execute( Properties properties ) + { + return false; + } + } ); + return this; + } + + public ServerBuilder withDefaultDatabaseTuning() throws IOException + { + action = WhatToDo.CREATE_GOOD_TUNING_FILE; + return this; + } + + public ServerBuilder withNonResolvableTuningFile() throws IOException + { + action = WhatToDo.CREATE_DANGLING_TUNING_FILE_PROPERTY; + return this; + } + + public ServerBuilder withCorruptTuningFile() throws IOException + { + action = WhatToDo.CREATE_CORRUPT_TUNING_FILE; + return this; + } + + public ServerBuilder withThirdPartyJaxRsPackage( String packageName, String mountPoint ) + { + thirdPartyPackages.put( packageName, mountPoint ); + return this; + } + + public ServerBuilder withSpecificServerModulesOnly( Class... modules ) + { + serverModules = Arrays.asList( modules ); + return this; + } +} diff --git a/src/main/java/org/neo4j/tutorial/server/ServerTestUtils.java b/src/main/java/org/neo4j/tutorial/server/ServerTestUtils.java new file mode 100644 index 0000000..645a4e4 --- /dev/null +++ b/src/main/java/org/neo4j/tutorial/server/ServerTestUtils.java @@ -0,0 +1,134 @@ +package org.neo4j.tutorial.server; + +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Map; +import java.util.Properties; +import java.util.Random; + +import org.neo4j.kernel.AbstractGraphDatabase; +import org.neo4j.kernel.EmbeddedGraphDatabase; +import org.neo4j.server.database.GraphDatabaseFactory; + +public class ServerTestUtils +{ + public static final GraphDatabaseFactory EMBEDDED_GRAPH_DATABASE_FACTORY = new GraphDatabaseFactory() + { + public AbstractGraphDatabase createDatabase( String databaseStoreDirectory, + Map databaseProperties ) + { + return new EmbeddedGraphDatabase( databaseStoreDirectory, databaseProperties ); + } + }; + + public static File createTempDir( String prefix, String suffix ) throws IOException + { + File d = File.createTempFile( prefix, suffix ); + if ( !d.delete() ) + { + throw new RuntimeException( "temp config directory pre-delete failed" ); + } + if ( !d.mkdirs() ) + { + throw new RuntimeException( "temp config directory not created" ); + } + return d; + } + + public static File createTempDir() throws IOException + { + return createTempDir( "neo4j-test", "dir" ); + } + + public static File createTempPropertyFile() throws IOException + { + return createTempPropertyFile( createTempDir() ); + } + + public static void writePropertiesToFile( String outerPropertyName, Map properties, + File propertyFile ) + { + writePropertyToFile( outerPropertyName, asOneLine( properties ), propertyFile ); + } + + private static String asOneLine( Map properties ) + { + StringBuilder builder = new StringBuilder(); + for ( Map.Entry property : properties.entrySet() ) + { + builder.append( (builder.length() > 0 ? "," : "") ); + builder.append( property.getKey() ); + builder.append( "=" ); + builder.append( property.getValue() ); + } + return builder.toString(); + } + + public static void writePropertyToFile( String name, String value, File propertyFile ) + { + Properties properties = loadProperties( propertyFile ); + properties.setProperty( name, value ); + storeProperties( propertyFile, properties ); + } + + private static void storeProperties( File propertyFile, Properties properties ) + { + OutputStream out = null; + try + { + out = new FileOutputStream( propertyFile ); + properties.store( out, "" ); + } catch ( IOException e ) + { + throw new RuntimeException( e ); + } finally + { + safeClose( out ); + } + } + + private static Properties loadProperties( File propertyFile ) + { + Properties properties = new Properties(); + if ( propertyFile.exists() ) + { + InputStream in = null; + try + { + in = new FileInputStream( propertyFile ); + properties.load( in ); + } catch ( IOException e ) + { + throw new RuntimeException( e ); + } finally + { + safeClose( in ); + } + } + return properties; + } + + private static void safeClose( Closeable closeable ) + { + if ( closeable != null ) + { + try + { + closeable.close(); + } catch ( IOException e ) + { + e.printStackTrace(); + } + } + } + + public static File createTempPropertyFile( File parentDir ) throws IOException + { + return new File( parentDir, "test-" + new Random().nextInt() + ".properties" ); + } +} diff --git a/src/main/java/org/neo4j/tutorial/server/rest/BatchCommandBuilder.java b/src/main/java/org/neo4j/tutorial/server/rest/BatchCommandBuilder.java new file mode 100644 index 0000000..668c252 --- /dev/null +++ b/src/main/java/org/neo4j/tutorial/server/rest/BatchCommandBuilder.java @@ -0,0 +1,112 @@ +package org.neo4j.tutorial.server.rest; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +public class BatchCommandBuilder +{ + private static final String CreateNodeTemplate = "{\"method\":\"POST\",\"to\":\"/node\",\"body\":{%s}%s}"; + private static final String CreateRelationshipTemplate = "{\"method\":\"POST\",\"to\":\"%s\",\"body\":{\"to\":\"%s\",\"type\":\"%s\"%s}%s}"; + private static final String DeleteTemplate = "{\"method\":\"DELETE\",\"to\":\"%s\"}"; + + private final List commands = new ArrayList(); + + public BatchCommandBuilder createNode( int jobId, Map bodyParams ) + { + return formatCreateNode( bodyParams, jobId ); + } + + public BatchCommandBuilder createNode( Map bodyParams ) + { + return formatCreateNode( bodyParams, null ); + } + + public BatchCommandBuilder createRelationship( int jobId, String startNodeRelationshipUri, String endNodeUri, + String relType, Map dataParams ) + { + return formatCreateRelationship( startNodeRelationshipUri, endNodeUri, relType, dataParams, jobId ); + } + + public BatchCommandBuilder createRelationship( String startNodeRelationshipUri, String endNodeUri, String relType ) + { + return formatCreateRelationship( startNodeRelationshipUri, endNodeUri, relType, null, null ); + } + + public BatchCommandBuilder createRelationship( String startNodeRelationshipUri, String endNodeUri, String relType, + Map dataParams ) + { + return formatCreateRelationship( startNodeRelationshipUri, endNodeUri, relType, dataParams, null ); + } + + public BatchCommandBuilder deleteNodeOrRelationship( String uri ) + { + commands.add( String.format( DeleteTemplate, uri ) ); + return this; + } + + private BatchCommandBuilder formatCreateNode( Map bodyParams, Integer jobId ) + { + commands.add( String.format( CreateNodeTemplate, formatParams( bodyParams ), formatJobId( jobId ) ) ); + return this; + } + + private BatchCommandBuilder formatCreateRelationship( String startNodeRelationshipUri, String endNodeUri, + String relType, Map dataParams, Integer jobId ) + { + commands.add( String.format( CreateRelationshipTemplate, startNodeRelationshipUri, endNodeUri, relType, + createData( dataParams ), formatJobId( jobId ) ) ); + return this; + } + + private String formatJobId( Integer jobId ) + { + if ( jobId == null ) + { + return ""; + } + return String.format( ",\"id\":%s", jobId ); + } + + private String createData( Map dataParams ) + { + if ( dataParams == null || dataParams.isEmpty() ) + { + return ""; + } + return ",\"data\":{" + formatParams( dataParams ) + "}"; + } + + private String formatParams( Map params ) + { + if ( params == null ) + { + return ""; + } + StringBuilder sb = new StringBuilder(); + Iterator keys = params.keySet() + .iterator(); + while ( keys.hasNext() ) + { + String key = keys.next(); + sb.append( String.format( "\"%s\":\"%s\"", key, params.get( key ) ) ); + sb.append( keys.hasNext() ? "," : "" ); + } + return sb.toString(); + } + + public String build() + { + StringBuilder sb = new StringBuilder(); + sb.append( "[" ); + Iterator cmds = commands.iterator(); + while ( cmds.hasNext() ) + { + sb.append( cmds.next() ); + sb.append( cmds.hasNext() ? "," : "" ); + } + sb.append( "]" ); + return sb.toString(); + } +} diff --git a/src/main/java/org/neo4j/tutorial/server/rest/FunctionalTestHelper.java b/src/main/java/org/neo4j/tutorial/server/rest/FunctionalTestHelper.java new file mode 100644 index 0000000..0bdeff0 --- /dev/null +++ b/src/main/java/org/neo4j/tutorial/server/rest/FunctionalTestHelper.java @@ -0,0 +1,170 @@ +package org.neo4j.tutorial.server.rest; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; + +import org.neo4j.kernel.AbstractGraphDatabase; +import org.neo4j.server.NeoServerWithEmbeddedWebServer; +import org.neo4j.server.rest.domain.JsonHelper; +import org.neo4j.server.rest.domain.JsonParseException; + +public final class FunctionalTestHelper +{ + private final NeoServerWithEmbeddedWebServer server; + + public FunctionalTestHelper( NeoServerWithEmbeddedWebServer server ) + { + if ( server.getDatabase() == null ) + { + throw new RuntimeException( "Server must be started before using " + getClass().getName() ); + } + this.server = server; + } + + void assertLegalJson( String entity ) throws IOException, JsonParseException + { + JsonHelper.jsonToMap( entity ); + } + + public String dataUri() + { + return server.baseUri() + .toString() + "db/data/"; + } + + public String nodeUri() + { + return dataUri() + "node"; + } + + public String nodeUri( long id ) + { + return nodeUri() + "/" + id; + } + + public String nodePropertiesUri( long id ) + { + return nodeUri( id ) + "/properties"; + } + + public String nodePropertyUri( long id, String key ) + { + return nodePropertiesUri( id ) + "/" + key; + } + + public String relationshipUri() + { + return dataUri() + "relationship"; + } + + public String relationshipUri( long id ) + { + return relationshipUri() + "/" + id; + } + + public String relationshipPropertiesUri( long id ) + { + return relationshipUri( id ) + "/properties"; + } + + public String relationshipPropertyUri( long id, String key ) + { + return relationshipPropertiesUri( id ) + "/" + key; + } + + public String relationshipsUri( long nodeId, String dir, String... types ) + { + StringBuilder typesString = new StringBuilder(); + for ( String type : types ) + { + typesString.append( typesString.length() > 0 ? "&" : "" ); + typesString.append( type ); + } + return nodeUri( nodeId ) + "/relationships/" + dir + "/" + typesString; + } + + public String indexUri() + { + return dataUri() + "index/"; + } + + public String nodeIndexUri() + { + return indexUri() + "node/"; + } + + public String relationshipIndexUri() + { + return indexUri() + "relationship/"; + } + + public String mangementUri() + { + return server.baseUri() + .toString() + "db/manage"; + } + + public String indexNodeUri( String indexName ) + { + return nodeIndexUri() + indexName; + } + + public String indexRelationshipUri( String indexName ) + { + return relationshipIndexUri() + indexName; + } + + public String indexNodeUri( String indexName, String key, Object value ) + { + return indexNodeUri( indexName ) + "/" + key + "/" + value.toString() + .replace( " ", "%20" ); + } + + public String indexRelationshipUri( String indexName, String key, Object value ) + { + try + { + return indexRelationshipUri( indexName ) + "/" + key + "/" + URLEncoder.encode( value.toString(), "UTF-8" ); + } catch ( UnsupportedEncodingException e ) + { + return indexRelationshipUri( indexName ) + "/" + key + "/" + value; + } + } + + public String extensionUri() + { + return dataUri() + "ext"; + } + + public String extensionUri( String name ) + { + return extensionUri() + "/" + name; + } + + public String graphdbExtensionUri( String name, String method ) + { + return extensionUri( name ) + "/graphdb/" + method; + } + + public String nodeExtensionUri( String name, String method, long id ) + { + return extensionUri( name ) + "/node/" + id + "/" + method; + } + + public String relationshipExtensionUri( String name, String method, long id ) + { + return extensionUri( name ) + "/relationship/" + id + "/" + method; + } + + public AbstractGraphDatabase getDatabase() + { + return server.getDatabase().graph; + } + + public String getWebadminUri() + { + return server.baseUri() + .toString() + "webadmin"; + } +} diff --git a/src/main/java/org/neo4j/tutorial/server/rest/RelationshipDescription.java b/src/main/java/org/neo4j/tutorial/server/rest/RelationshipDescription.java new file mode 100644 index 0000000..54192b6 --- /dev/null +++ b/src/main/java/org/neo4j/tutorial/server/rest/RelationshipDescription.java @@ -0,0 +1,45 @@ +package org.neo4j.tutorial.server.rest; + +public class RelationshipDescription +{ + + public static final String OUT = "out"; + public static final String IN = "in"; + public static final String BOTH = "both"; + private String type; + private String direction; + + public String toJsonCollection() + { + StringBuilder sb = new StringBuilder(); + sb.append( "{ " ); + sb.append( " \"type\" : \"" + type + "\"" ); + if ( direction != null ) + { + sb.append( ", \"direction\" : \"" + direction + "\"" ); + } + sb.append( " }" ); + return sb.toString(); + } + + public RelationshipDescription( String type, String direction ) + { + setType( type ); + setDirection( direction ); + } + + public RelationshipDescription( String type ) + { + this( type, null ); + } + + public void setType( String type ) + { + this.type = type; + } + + public void setDirection( String direction ) + { + this.direction = direction; + } +} diff --git a/src/main/java/org/neo4j/tutorial/server/rest/TraversalDescription.java b/src/main/java/org/neo4j/tutorial/server/rest/TraversalDescription.java new file mode 100644 index 0000000..911b4e4 --- /dev/null +++ b/src/main/java/org/neo4j/tutorial/server/rest/TraversalDescription.java @@ -0,0 +1,77 @@ +package org.neo4j.tutorial.server.rest; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class TraversalDescription +{ + + public static final String DEPTH_FIRST = "depth_first"; + public static final String NODE = "node"; + public static final String ALL = "all"; + + private String uniqueness = NODE; + private int maxDepth = 1; + private String returnFilter = ALL; + private String order = DEPTH_FIRST; + private List relationships = new ArrayList(); + + public void setOrder( String order ) + { + this.order = order; + } + + public void setUniqueness( String uniqueness ) + { + this.uniqueness = uniqueness; + } + + public void setMaxDepth( int maxDepth ) + { + this.maxDepth = maxDepth; + } + + public void setReturnFilter( String returnFilter ) + { + this.returnFilter = returnFilter; + } + + public void setRelationships( RelationshipDescription... relationships ) + { + this.relationships = Arrays.asList( relationships ); + } + + public String toJson() + { + StringBuilder sb = new StringBuilder(); + sb.append( "{ " ); + sb.append( " \"order\" : \"" + order + "\"" ); + sb.append( ", " ); + sb.append( " \"uniqueness\" : \"" + uniqueness + "\"" ); + sb.append( ", " ); + if ( relationships.size() > 0 ) + { + sb.append( "\"relationships\" : [" ); + for ( int i = 0; i < relationships.size(); i++ ) + { + sb.append( relationships.get( i ) + .toJsonCollection() ); + if ( i < relationships.size() - 1 ) + { // Miss off the final comma + sb.append( ", " ); + } + } + sb.append( "], " ); + } + sb.append( "\"return_filter\" : { " ); + sb.append( "\"language\" : \"javascript\", " ); + sb.append( "\"body\" : \"" ); + sb.append( returnFilter ); + sb.append( "\" }, " ); + sb.append( "\"max_depth\" : " ); + sb.append( maxDepth ); + sb.append( " }" ); + return sb.toString(); + } +} diff --git a/src/main/java/org/neo4j/tutorial/spring/SpeciesEntity.java b/src/main/java/org/neo4j/tutorial/spring/SpeciesEntity.java new file mode 100644 index 0000000..165c12b --- /dev/null +++ b/src/main/java/org/neo4j/tutorial/spring/SpeciesEntity.java @@ -0,0 +1,14 @@ +package org.neo4j.tutorial.spring; + +//import org.springframework.data.graph.annotation.NodeEntity; +//import org.springframework.data.graph.neo4j.annotation.Indexed; +// +//@NodeEntity +//public class SpeciesEntity{ +// @Indexed +// String species; +// +// public String getName(){ +// return species; +// } +// } diff --git a/src/main/resources/spring/tutorialContext.xml b/src/main/resources/spring/tutorialContext.xml new file mode 100644 index 0000000..c987931 --- /dev/null +++ b/src/main/resources/spring/tutorialContext.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/scripts/release.sh b/src/main/scripts/release.sh new file mode 100755 index 0000000..311635f --- /dev/null +++ b/src/main/scripts/release.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +# Naive release script that copies the Koans, without the Git repo, to the specified release directory and runs the sed scripts to strip out the Java code, thus readying the Koans for use + +CURRENT_DIR=`pwd` + +TEMP_DIR=/tmp/$RANDOM +mkdir -p $TEMP_DIR +echo using temp directory [$TEMP_DIR] +echo delivering gzipped tarball to [$1] + +cp -r presentation $TEMP_DIR +cp -r lib $TEMP_DIR +cp -r build.xml $TEMP_DIR +cp -r settings $TEMP_DIR +cp -r src $TEMP_DIR +cp -r tools $TEMP_DIR + +cd $TEMP_DIR + +bash src/main/scripts/remove_snippets.sh + +TAR_DIR=$1 +mkdir -p $TAR_DIR + +tar -cf /$TAR_DIR/koan.tar . +gzip /$TAR_DIR/koan.tar + +cd $CURRENT_DIR \ No newline at end of file diff --git a/src/main/scripts/remove_snippets.sed b/src/main/scripts/remove_snippets.sed new file mode 100644 index 0000000..ebb904c --- /dev/null +++ b/src/main/scripts/remove_snippets.sed @@ -0,0 +1,10 @@ +:t +/SNIPPET_START/,/SNIPPET_END/ { + /SNIPPET_END/!{ + $!{ + N; + bt + } + } + /.*/d; +} diff --git a/src/main/scripts/remove_snippets.sh b/src/main/scripts/remove_snippets.sh new file mode 100755 index 0000000..ce1d13d --- /dev/null +++ b/src/main/scripts/remove_snippets.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +# Script to remove any code between //SNIPPET_START and //SNIPPET_END in Java unit test files + +find src/koan/java -name *.java -print | xargs sed -f src/main/scripts/remove_snippets.sed -i "" \ No newline at end of file diff --git a/src/main/scripts/run-neo4jshell.bat b/src/main/scripts/run-neo4jshell.bat new file mode 100644 index 0000000..b1a11f8 --- /dev/null +++ b/src/main/scripts/run-neo4jshell.bat @@ -0,0 +1,8 @@ +REM Set this to your own project directory + +set PROJECT_DIR=Z:\MYDO~0F4\neo\development\neo4j-tutorial +set LIB_DIR=%PROJECT_DIR%\lib + + +java -cp %LIB_DIR%\neo4j-shell.jar;%LIB_DIR%\neo4j-kernel.jar;%LIB_DIR%\geronimo-jta_1.1_spec.jar org.neo4j.shell.StartClient -path %1 + diff --git a/src/main/scripts/run-neo4jshell.sh b/src/main/scripts/run-neo4jshell.sh new file mode 100755 index 0000000..f51213a --- /dev/null +++ b/src/main/scripts/run-neo4jshell.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +# Set this to your own project directory +PROJECT_DIR=/Users/jim/neo/development/neo4j-tutorial +LIB_DIR=$PROJECT_DIR/lib + +echo $LIB_DIR/neo4j-community.jar + +java -cp $LIB_DIR/neo4j-shell.jar:$LIB_DIR/neo4j-kernel.jar:$LIB_DIR/geronimo-jta_1.1_spec.jar org.neo4j.shell.StartClient -path $1 diff --git a/src/test/java/org/neo4j/tutorial/DoctorWhoUniverseGeneratorTest.java b/src/test/java/org/neo4j/tutorial/DoctorWhoUniverseGeneratorTest.java new file mode 100644 index 0000000..e40cb46 --- /dev/null +++ b/src/test/java/org/neo4j/tutorial/DoctorWhoUniverseGeneratorTest.java @@ -0,0 +1,494 @@ +package org.neo4j.tutorial; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.List; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.neo4j.graphalgo.GraphAlgoFactory; +import org.neo4j.graphalgo.PathFinder; +import org.neo4j.graphdb.Direction; +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Path; +import org.neo4j.graphdb.Relationship; +import org.neo4j.graphdb.index.Index; +import org.neo4j.graphdb.index.IndexHits; +import org.neo4j.graphdb.traversal.Evaluation; +import org.neo4j.graphdb.traversal.Evaluator; +import org.neo4j.graphdb.traversal.Traverser; +import org.neo4j.kernel.Traversal; +import org.neo4j.kernel.Uniqueness; + +/** + * Be careful when adding tests here - each test in this class uses the same + * database instance and so can pollute. This was done for performance reasons + * since loading the database for each test takes a long time, even on fast + * hardware. + */ +public class DoctorWhoUniverseGeneratorTest +{ + + private static EmbeddedDoctorWhoUniverse universe; + private static GraphDatabaseService database; + private static DatabaseHelper databaseHelper; + + @BeforeClass + public static void startDatabase() throws Exception + { + universe = new EmbeddedDoctorWhoUniverse( new DoctorWhoUniverseGenerator() ); + database = universe.getDatabase(); + databaseHelper = new DatabaseHelper( database ); + } + + @AfterClass + public static void stopDatabase() + { + database.shutdown(); + } + + @SuppressWarnings("unused") + @Test + public void shouldHaveCorrectNumberOfPlanetsInIndex() + { + IndexHits indexHits = universe.getDatabase() + .index() + .forNodes( "planets" ) + .query( "planet", "*" ); + int planetCount = 0; + for ( Node n : indexHits ) + { + planetCount++; + } + + int numberOfPlanetsMentionedInTVEpisodes = 447; + assertEquals( numberOfPlanetsMentionedInTVEpisodes, planetCount ); + } + + @Test + public void theDoctorsRegenerationsShouldBeDated() + { + Node nextDoctor = getActorIndex().get( "actor", "William Hartnell" ) + .getSingle(); + + boolean allDoctorsHaveRegenerationYears = true; + + do + { + Relationship relationship = nextDoctor.getSingleRelationship( DoctorWhoRelationships.REGENERATED_TO, + Direction.OUTGOING ); + if ( relationship == null ) + { + break; + } + + allDoctorsHaveRegenerationYears = relationship.hasProperty( "year" ) && allDoctorsHaveRegenerationYears; + + nextDoctor = relationship.getEndNode(); + + } + while ( nextDoctor != null ); + + assertTrue( allDoctorsHaveRegenerationYears ); + } + + @Test + public void shouldHaveCorrectNumberOfHumans() + { + Node humanSpeciesNode = universe.getDatabase() + .index() + .forNodes( "species" ) + .get( "species", "Human" ) + .getSingle(); + int numberOfHumansFriendliesInTheDB = databaseHelper.countRelationships( humanSpeciesNode.getRelationships( + DoctorWhoRelationships.IS_A, Direction.INCOMING ) ); + + int knownNumberOfHumans = 46; + assertEquals( knownNumberOfHumans, numberOfHumansFriendliesInTheDB ); + } + + @Test + public void shouldBe8Timelords() + { + Node timelordSpeciesNode = universe.getDatabase() + .index() + .forNodes( "species" ) + .get( "species", "Timelord" ) + .getSingle(); + + int numberOfTimelordsInTheDb = databaseHelper.countRelationships( timelordSpeciesNode.getRelationships( + DoctorWhoRelationships.IS_A, Direction.INCOMING ) ); + + int knownNumberOfTimelords = 8; + assertEquals( knownNumberOfTimelords, numberOfTimelordsInTheDb ); + } + + @SuppressWarnings("unused") + @Test + public void shouldHaveCorrectNumberOfSpecies() + { + IndexHits indexHits = universe.getDatabase() + .index() + .forNodes( "species" ) + .query( "species", "*" ); + int speciesCount = 0; + for ( Node n : indexHits ) + { + speciesCount++; + } + + int numberOfSpecies = 53; + assertEquals( numberOfSpecies, speciesCount ); + } + + @Test + public void shouldHave12ActorsThatHavePlayedTheDoctor() + { + int numberOfDoctors = 12; // 12 Because the first doctor was played by 2 + // actors over the course of the franchise + + Node theDoctor = universe.getDatabase() + .index() + .forNodes( "characters" ) + .get( "character", "Doctor" ) + .getSingle(); + assertNotNull( theDoctor ); + assertEquals( numberOfDoctors, databaseHelper.countRelationships( theDoctor.getRelationships( + DoctorWhoRelationships.PLAYED, Direction.INCOMING ) ) ); + } + + @Test + public void shouldBeTenRegenerationRelationshipsBetweenTheElevenDoctors() + { + int numberOfDoctorsRegenerations = 10; + + IndexHits indexHits = getActorIndex().get( "actor", "William Hartnell" ); + assertEquals( 1, indexHits.size() ); + + Node firstDoctor = indexHits.getSingle(); + assertNotNull( firstDoctor ); + assertEquals( numberOfDoctorsRegenerations, countRelationships( firstDoctor ) ); + } + + @Test + public void shouldBeSevenRegenerationRelationshipsBetweenTheEightMasters() + { + int numberOfMastersRegenerations = 7; + + IndexHits indexHits = getActorIndex().get( "actor", "Roger Delgado" ); + assertEquals( 1, indexHits.size() ); + + Node currentMaster = indexHits.getSingle(); + assertEquals( numberOfMastersRegenerations, countRelationships( currentMaster ) ); + } + + private int countRelationships( Node timelord ) + { + int regenerationCount = 0; + while ( true ) + { + List relationships = databaseHelper.toListOfRelationships( timelord.getRelationships( + DoctorWhoRelationships.REGENERATED_TO, Direction.OUTGOING ) ); + + if ( relationships.size() == 1 ) + { + Relationship regeneratedTo = relationships.get( 0 ); + timelord = regeneratedTo.getEndNode(); + regenerationCount++; + } else + { + break; + } + } + return regenerationCount; + } + + @Test + public void shouldHave8Masters() + { + int numberOfMasters = 8; + Node theMaster = universe.getDatabase() + .index() + .forNodes( "characters" ) + .get( "character", "Master" ) + .getSingle(); + + assertNotNull( theMaster ); + assertEquals( numberOfMasters, databaseHelper.countRelationships( theMaster.getRelationships( + DoctorWhoRelationships.PLAYED, Direction.INCOMING ) ) ); + } + + @Test + public void timelordsShouldComeFromGallifrey() + { + Node gallifrey = getPlanetIndex().get( "planet", "Gallifrey" ) + .getSingle(); + Node timelord = getSpeciesIndex().get( "species", "Timelord" ) + .getSingle(); + assertNotNull( gallifrey ); + assertNotNull( timelord ); + + Iterable relationships = timelord.getRelationships( DoctorWhoRelationships.COMES_FROM, + Direction.OUTGOING ); + List listOfRelationships = databaseHelper.toListOfRelationships( relationships ); + + assertEquals( 1, listOfRelationships.size() ); + assertTrue( listOfRelationships.get( 0 ) + .getEndNode() + .equals( gallifrey ) ); + } + + @Test + public void shortestPathBetweenDoctorAndMasterShouldBeLengthOneTypeEnemyOf() + { + Node theMaster = universe.getDatabase() + .index() + .forNodes( "characters" ) + .get( "character", "Master" ) + .getSingle(); + Node theDoctor = universe.getDatabase() + .index() + .forNodes( "characters" ) + .get( "character", "Doctor" ) + .getSingle(); + + int maxDepth = 5; // No more than 5, or we find Kevin Bacon! + PathFinder shortestPathFinder = GraphAlgoFactory.shortestPath( Traversal.expanderForAllTypes(), maxDepth ); + + Path shortestPath = shortestPathFinder.findSinglePath( theDoctor, theMaster ); + assertEquals( 1, shortestPath.length() ); + assertTrue( shortestPath.lastRelationship() + .isType( DoctorWhoRelationships.ENEMY_OF ) ); + } + + @Test + public void daleksShouldBeEnemiesOfTheDoctor() + { + Node dalek = getSpeciesIndex().get( "species", "Dalek" ) + .getSingle(); + assertNotNull( dalek ); + Iterable enemiesOf = dalek.getRelationships( DoctorWhoRelationships.ENEMY_OF, Direction.OUTGOING ); + assertTrue( containsTheDoctor( enemiesOf ) ); + } + + @Test + public void cybermenShouldBeEnemiesOfTheDoctor() + { + Node cyberman = getSpeciesIndex().get( "species", "Cyberman" ) + .getSingle(); + assertNotNull( cyberman ); + Iterable enemiesOf = cyberman.getRelationships( DoctorWhoRelationships.ENEMY_OF, + Direction.OUTGOING ); + assertTrue( containsTheDoctor( enemiesOf ) ); + } + + private boolean containsTheDoctor( Iterable enemiesOf ) + { + Node theDoctor = universe.getDatabase() + .index() + .forNodes( "characters" ) + .get( "character", "Doctor" ) + .getSingle(); + for ( Relationship r : enemiesOf ) + { + if ( r.getEndNode() + .equals( theDoctor ) ) + { + return true; + } + } + return false; + } + + @Test + public void shouldFindEnemiesOfTheMastersEnemies() + { + + Node theMaster = universe.getDatabase() + .index() + .forNodes( "characters" ) + .get( "character", "Master" ) + .getSingle(); + Node dalek = getSpeciesIndex().get( "species", "Dalek" ) + .getSingle(); + Node cyberman = getSpeciesIndex().get( "species", "Cyberman" ) + .getSingle(); + Node silurian = getSpeciesIndex().get( "species", "Silurian" ) + .getSingle(); + Node sontaran = getSpeciesIndex().get( "species", "Sontaran" ) + .getSingle(); + + Traverser traverser = Traversal.description() + .expand( Traversal.expanderForTypes( DoctorWhoRelationships.ENEMY_OF, Direction.OUTGOING ) ) + .depthFirst() + .evaluator( new Evaluator() + { + public Evaluation evaluate( Path path ) + { + // Only include if we're at depth 2, don't want any mere + // enemies + if ( path.length() == 2 ) + { + return Evaluation.INCLUDE_AND_PRUNE; + } else if ( path.length() > 2 ) + { + return Evaluation.EXCLUDE_AND_PRUNE; + } else + { + return Evaluation.EXCLUDE_AND_CONTINUE; + } + } + } ) + .uniqueness( Uniqueness.NODE_GLOBAL ) + .traverse( theMaster ); + + Iterable nodes = traverser.nodes(); + assertNotNull( nodes ); + + List enemiesOfEnemies = databaseHelper.toListOfNodes( nodes ); + + int numberOfIndividualAndSpeciesEnemiesInTheDatabase = 141; + assertEquals( numberOfIndividualAndSpeciesEnemiesInTheDatabase, enemiesOfEnemies.size() ); + assertTrue( isInList( dalek, enemiesOfEnemies ) ); + assertTrue( isInList( cyberman, enemiesOfEnemies ) ); + assertTrue( isInList( silurian, enemiesOfEnemies ) ); + assertTrue( isInList( sontaran, enemiesOfEnemies ) ); + } + + private boolean isInList( Node candidateNode, List listOfNodes ) + { + for ( Node n : listOfNodes ) + { + if ( n.equals( candidateNode ) ) + { + return true; + } + } + return false; + } + + @Test + public void shouldBeCorrectNumberOfEnemySpecies() + { + int numberOfEnemySpecies = 41; + Node theDoctor = universe.getDatabase() + .index() + .forNodes( "characters" ) + .get( "character", "Doctor" ) + .getSingle(); + + Iterable relationships = theDoctor.getRelationships( DoctorWhoRelationships.ENEMY_OF, + Direction.INCOMING ); + int enemySpeciesFound = 0; + for ( Relationship rel : relationships ) + { + if ( rel.getStartNode() + .hasProperty( "species" ) ) + { + enemySpeciesFound++; + } + } + + assertEquals( numberOfEnemySpecies, enemySpeciesFound ); + } + + @Test + public void shouldHaveCorrectNumberOfCompanionsInTotal() + { + int numberOfCompanions = 45; + + Node theDoctor = universe.getDatabase() + .index() + .forNodes( "characters" ) + .get( "character", "Doctor" ) + .getSingle(); + assertNotNull( theDoctor ); + + assertEquals( numberOfCompanions, databaseHelper.countRelationships( theDoctor.getRelationships( + DoctorWhoRelationships.COMPANION_OF, Direction.INCOMING ) ) ); + } + + @Test + public void shouldHaveCorrectNumberofIndividualEnemyCharactersInTotal() + { + int numberOfEnemies = 101; + + Node theDoctor = universe.getDatabase() + .index() + .forNodes( "characters" ) + .get( "character", "Doctor" ) + .getSingle(); + assertNotNull( theDoctor ); + + int count = 0; + Iterable relationships = theDoctor.getRelationships( DoctorWhoRelationships.ENEMY_OF, + Direction.INCOMING ); + for ( Relationship rel : relationships ) + { + if ( rel.getStartNode() + .hasProperty( "character" ) ) + { + count++; + } + } + + assertEquals( numberOfEnemies, count ); + } + + private Index getActorIndex() + { + return database.index() + .forNodes( "actors" ); + } + + private Index getPlanetIndex() + { + return database.index() + .forNodes( "planets" ); + } + + private Index getSpeciesIndex() + { + return database.index() + .forNodes( "species" ); + } + + @Test + public void severalSpeciesShouldBeEnemies() + { + assertTrue( areMututalEnemySpecies( "Dalek", "Cyberman" ) ); + assertTrue( areMututalEnemySpecies( "Dalek", "Human" ) ); + assertTrue( areMututalEnemySpecies( "Human", "Auton" ) ); + assertTrue( areMututalEnemySpecies( "Timelord", "Dalek" ) ); + } + + private boolean areMututalEnemySpecies( String enemy1, String enemy2 ) + { + Index speciesIndex = database.index() + .forNodes( "species" ); + + Node n1 = speciesIndex.get( "species", enemy1 ) + .getSingle(); + Node n2 = speciesIndex.get( "species", enemy2 ) + .getSingle(); + + return isEnemyOf( n1, n2 ) && isEnemyOf( n2, n1 ); + } + + private boolean isEnemyOf( Node n1, Node n2 ) + { + for ( Relationship r : n1.getRelationships( DoctorWhoRelationships.ENEMY_OF, Direction.OUTGOING ) ) + { + if ( r.getEndNode() + .equals( n2 ) ) + { + return true; + } + } + return false; + } +} diff --git a/src/test/java/org/neo4j/tutorial/server/rest/BatchCommandBuilderTests.java b/src/test/java/org/neo4j/tutorial/server/rest/BatchCommandBuilderTests.java new file mode 100644 index 0000000..c69fee7 --- /dev/null +++ b/src/test/java/org/neo4j/tutorial/server/rest/BatchCommandBuilderTests.java @@ -0,0 +1,102 @@ +package org.neo4j.tutorial.server.rest; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.neo4j.helpers.collection.MapUtil; + +public class BatchCommandBuilderTests +{ + + @Test + public void shouldFormatCreateNodeRequestCorrectly() + { + String result = new BatchCommandBuilder().createNode( 1, MapUtil.stringMap( "key", "value" ) ) + .build(); + assertEquals( "[{\"method\":\"POST\",\"to\":\"/node\",\"body\":{\"key\":\"value\"},\"id\":1}]", result ); + + } + + @Test + public void shouldFormatCreateNodeRequestWithoutJobIdCorrectly() + { + String result = new BatchCommandBuilder().createNode( MapUtil.stringMap( "key", "value" ) ) + .build(); + assertEquals( "[{\"method\":\"POST\",\"to\":\"/node\",\"body\":{\"key\":\"value\"}}]", result ); + } + + @Test + public void shouldFormatCreateNodeRequestWithEmptyParamsCorrectly() + { + String result = new BatchCommandBuilder().createNode( MapUtil.stringMap() ) + .build(); + assertEquals( "[{\"method\":\"POST\",\"to\":\"/node\",\"body\":{}}]", result ); + + } + + @Test + public void shouldFormatCreateNodeRequestWithNullParamsCorrectly() + { + String result = new BatchCommandBuilder().createNode( null ) + .build(); + assertEquals( "[{\"method\":\"POST\",\"to\":\"/node\",\"body\":{}}]", result ); + + } + + @Test + public void shouldFormatCreateRelationshipRequestCorrectly() + { + String result = new BatchCommandBuilder().createRelationship( 1, "/node/1", "node/2", "KNOWS", + MapUtil.stringMap( "key", "value" ) ) + .build(); + assertEquals( + "[{\"method\":\"POST\",\"to\":\"/node/1\",\"body\":{\"to\":\"node/2\",\"type\":\"KNOWS\",\"data\":{\"key\":\"value\"}},\"id\":1}]", + result ); + } + + @Test + public void shouldFormatCreateRelationshipRequestWithoutJobIdCorrectly() + { + String result = new BatchCommandBuilder().createRelationship( "/node/1", "node/2", "KNOWS", + MapUtil.stringMap( "key", "value" ) ) + .build(); + assertEquals( + "[{\"method\":\"POST\",\"to\":\"/node/1\",\"body\":{\"to\":\"node/2\",\"type\":\"KNOWS\",\"data\":{\"key\":\"value\"}}}]", + result ); + } + + @Test + public void shouldFormatCreateRelationshipRequestWithoutJobIdOrDataCorrectly() + { + String result = new BatchCommandBuilder().createRelationship( "/node/1", "node/2", "KNOWS" ) + .build(); + assertEquals( "[{\"method\":\"POST\",\"to\":\"/node/1\",\"body\":{\"to\":\"node/2\",\"type\":\"KNOWS\"}}]", + result ); + } + + @Test + public void shouldFormatCreateRelationshipRequestWithEmptyParamsCorrectly() + { + String result = new BatchCommandBuilder().createRelationship( "/node/1", "node/2", "KNOWS", MapUtil.stringMap() ) + .build(); + assertEquals( "[{\"method\":\"POST\",\"to\":\"/node/1\",\"body\":{\"to\":\"node/2\",\"type\":\"KNOWS\"}}]", + result ); + } + + @Test + public void shouldFormatCreateRelationshipRequestWithNullParamsCorrectly() + { + String result = new BatchCommandBuilder().createRelationship( "/node/1", "node/2", "KNOWS", null ) + .build(); + assertEquals( "[{\"method\":\"POST\",\"to\":\"/node/1\",\"body\":{\"to\":\"node/2\",\"type\":\"KNOWS\"}}]", + result ); + } + + @Test + public void shouldFormatDeleteRequestCorrectly() + { + String result = new BatchCommandBuilder().deleteNodeOrRelationship( "/node/123" ) + .build(); + assertEquals( "[{\"method\":\"DELETE\",\"to\":\"/node/123\"}]", result ); + } +}