diff --git a/src/docbkx/index.xml b/src/docbkx/index.xml
index fa1a12816..66bf90cd1 100644
--- a/src/docbkx/index.xml
+++ b/src/docbkx/index.xml
@@ -51,14 +51,17 @@
Tutorial
- The first part of the book provides a tutorial that walks through the creation of a complete Web application
- called cineasts.net built with Spring Data Graph and Neo4j.
- It uses a domain that should be familiar - movies. So for cineasts.net we decided to add a social
- touch to rating movies, allowing friends to share their scores and get recommendations
- for new friends and movies.
-
- The tutorial walks the steps necessary to create the application.
- It provides the configuration and code examples that are needed to understand what's happening in Spring Data Graph. Of course the complete source code for the app is available at github.
+ The first part of the book provides a tutorial that walks through the creation of a complete web
+ application called cineasts.net, built with Spring Data Graph and Neo4j.
+ Cineasts are people who love movies, and the site is a gathering place for these people. For
+ cineasts.net we decided to add a social aspect to the rating of movies, allowing friends to
+ share their scores and get recommendations for new friends and movies.
+
+
+ The tutorial takes the reader through the steps necessary to create the application.
+ It provides the configuration and code examples that are needed to understand what's
+ happening in Spring Data Graph. The complete source code for the app is available on
+ Github.
diff --git a/src/docbkx/introduction/preface.xml b/src/docbkx/introduction/preface.xml
index 440873886..b1fc32beb 100644
--- a/src/docbkx/introduction/preface.xml
+++ b/src/docbkx/introduction/preface.xml
@@ -23,14 +23,14 @@
About this Guide Book
Welcome to the Spring Data Graph Guide Book. Thank you for taking the time to get an in depth look into Spring Data Graph Library. Spring Data Graph is part of the Spring Data project which
- brings the convenient programming model of the Spring Framework to modern (mainly NoSQL) datastores.
+ brings the convenient programming model of the Spring Framework to modern (mainly NOSQL) datastores.
Spring Data Graph currently provides integration for the Neo4j Graph Database.
It was written by developers for developers. So hopefully we've created a documentation that is well received by
our peers.
If you have any feedback to the Spring Data Graph Library or this book, please provide it via SpringSource JIRA,
- the SpringSource NoSQL Forum, SpringSource NOSQL Forum, github comments or issues or the Neo4j mailing list.
This book is presented as a duplex book, a term coined by Martin Fowler. A duplex book consists of at least two
diff --git a/src/docbkx/introduction/why-sd-graph.xml b/src/docbkx/introduction/why-sd-graph.xml
index 9c42214df..1a932be31 100644
--- a/src/docbkx/introduction/why-sd-graph.xml
+++ b/src/docbkx/introduction/why-sd-graph.xml
@@ -4,7 +4,7 @@
Why Spring Data Graph?
- NOSQL
+ NOSQL
stores provide novel storage solutions to suit the
data storage requirements of modern applications.
diff --git a/src/docbkx/reference/spring-data.xml b/src/docbkx/reference/spring-data.xml
index 405838a78..4cd65eb24 100644
--- a/src/docbkx/reference/spring-data.xml
+++ b/src/docbkx/reference/spring-data.xml
@@ -4,7 +4,7 @@
Spring Data
Spring Data is a SpringSource project that aims to
- provide Spring's convenient programming model and well known conventions for NoSQL databases. Currently there
+ provide Spring's convenient programming model and well known conventions for NOSQL databases. Currently there
is support for Graph (e.g. Neo4j), Key-Value (e.g. Redis), Document (e.g. MongoDB) and Relational (e.g. Oracle)
databases. Mark Pollack, the author of Spring.NET is the project lead for the Spring Data project.
diff --git a/src/docbkx/tutorial/about-tutorial.xml b/src/docbkx/tutorial/about-tutorial.xml
index 20d77d233..8960222d5 100644
--- a/src/docbkx/tutorial/about-tutorial.xml
+++ b/src/docbkx/tutorial/about-tutorial.xml
@@ -1,24 +1,36 @@
- Allow me to introduce - Cineasts.net
+ Introducing Cineasts.net
+ "- In a galaxy, far, far away..."
-Once upon a time we wanted to build a social movie database. First things first - we had a name: "Cineasts" - the cinema enthusiasts who are
-crazy about movies. So we went ahead and got the domain, cineasts.net and the project was almost complete.
-
-We had some ideas about the domain too. Of course there should be actors who play roles in movies. We needed the Cineast, too, someone
-to rate the movies. And while they were there, they could also make friends. Find someone to accompany them to the cinema or share
-movie preferences. Even better, the engine behind all that should recommend new friends and movies to cineasts, based on their interests and
-existing friends.
-
-
-
-
-
-
-When we looked for possible sources for data, IMDB was our first stop, but they're a little expensive for our tastes, charging 15k USD for data access. Fortunately we found TheMoviedb.org which provides user-generated data for free. The also have liberal terms and conditions and a nice API for fetching the data.
-
-There were many more ideas but we wanted to get something done quickly. And this is how it should look.
+ Once upon a time we wanted to build a social movie database. At first there was only the name: Cineasts,
+ the cinema enthusiasts who have a burning passion for movies. So we went ahead and bought the domain
+ cineasts.net, and the project was almost complete.
+
+
+ We had some ideas about the domain model too. There would obviously be actors playing roles in movies.
+ We also needed someone to rate the movies - enter the cineast. And cineasts being the social people they are,
+ they wanted to make friends with other fellow cineasts. Imagine instantly finding someone to watch a movie
+ with, or share movie preferences with. Even better, finding new friends and movies based on what you and
+ your friends like.
+
+
+
+
+
+
+
+
+
+ When we looked for possible sources of data, IMDB was our first stop. But they're a bit expensive
+ for our taste, charging $15k USD for data access. Fortunately, we found
+ TheMoviedb.org which provides user-generated data for free.
+ They also have liberal terms and conditions, and a nice API for retrieving the data.
+
+
+ We had many more ideas, but we wanted to get something out there quickly. Here is how we envisioned
+ the final website:
diff --git a/src/docbkx/tutorial/annotations.xml b/src/docbkx/tutorial/annotations.xml
index b79d4f93a..55331d2da 100644
--- a/src/docbkx/tutorial/annotations.xml
+++ b/src/docbkx/tutorial/annotations.xml
@@ -1,35 +1,70 @@
- Decorations - Annotated Domain
+ Annotating the domain
+ "- Suitable quote"
- Looking at the documentation again, we found a simple Hello-World example and tried to understand it.
- The entities were annotated with @NodeEntity, that was simple, so we added the annotation to our domain classes too.
- Relationships got their own annotation named @RelationshipEntity. Property fields are taken care of automatically.
-
- It's time to put this to a test. How can we be assured that a field is persisted to the graph store? There seemed to be two possibilities. First was to get a
- GraphDatabaseContext injected and use its getById() method. The other one was a Repository approach. But let's try to keep things simple.
- How can we persist an entity and how to get its id? Looking at the documentation revealed that there are a bunch of methods introduced to the
- entities by the aspects. That's not obvious, but we found the two that would help here - entity.persist() and entity.getNodeId().
-
- So our test looked like this.
-
+ Looking at the Spring Data Graph documentation, we found a simple
+ Hello World example
+ and tried to understand it. The entity classes were annotated with @NodeEntity.
+ That was simple, so we added the annotation to our domain classes too. Entity classes representing
+ relationships were instead annotated with @RelationshipEntity.
+ Property fields were taken care of automatically.
+
+
+ It was time to put our application to a test. First we needed to figure out how to persist our
+ entity to the database. It turns out that the aspect introduced new methods into the annotated
+ entities. For persisting the entity to the database, we used entity.persist().
+
+
+ How could we now be assured that a field really was persisted to the graph store?
- getById(entityId)
+ method, or use a more versatile Repository. We decided to keep things simple for now. To get an
+ entity's ID, we simply used the aspect-introduced method entity.getNodeId().
+
+ Looking at the documentation revealed that there are a bunch of methods introduced to the
+ entities by the aspects. It's not entirely obvious, but we found two that would do the job:
+ entity.persist() entity.getNodeId().
+
+
+ So here's what our test ended up looking like:
+
+
+
+ First test case
+
-
-
- That worked! But what about transactions? We didn't declare the test to be transactional. After further reading we learned that persist() creates an
- implicit transaction - so that was like an EntityManager would behave. Ok, now we're getting somewhere. We also learned that for more complex operations on the entities we'd need
- external transactions.
-
+]]>
+
+
+
+ It worked! But hold on, what about transactions? After all, we had not declared the test to be
+ transactional. After some further reading we learned that calling persist() outside
+ of a transaction automatically creates an implicit transaction. Very much like an EntityManager
+ would behave. We also learned that when performing more complex operations on the entities we'd
+ need external transactions, but not for this simple test.
+
+
+ Our domain model had now evolved.
+
+ Movie class
+ cast;
+}
+]]>
+
+
diff --git a/src/docbkx/tutorial/domain.xml b/src/docbkx/tutorial/domain.xml
index c53eb01d6..a8e53689d 100644
--- a/src/docbkx/tutorial/domain.xml
+++ b/src/docbkx/tutorial/domain.xml
@@ -1,27 +1,30 @@
- Setting the Stage - Movies Domain
+ The domain model
+ "- Suitable quote"
+
+ We wanted to outline the domain model before diving into library details. We also looked at the
+ data model of the TheMoviedb.org data to confirm that it matched our expectations.
+
- The domain model was the next thing we planned to work on. We wanted to sketch it out first before diving into library details. We also looked at the datamodel of core themoviedb data to
- confirm that it matched our expectations.
-
-
-
+
-
+
+
In Java code this looks pretty straightforward:
-
-
-
-
+
+
+ Domain model
+ filmography;
- Role playedIn(Movie movie, String role);
+ Role playedIn(Movie movie, String role) { ... }
}
class Role {
@@ -48,8 +51,8 @@ class User {
String password;
Set ratings;
Set friends;
- Rating rate(Movie movie, int stars, String comment);
- void befriend(User user);
+ Rating rate(Movie movie, int stars, String comment) { ... }
+ void befriend(User user) { ... }
}
class Rating {
@@ -59,8 +62,9 @@ class Rating {
String comment;
}
]]>
-
-
- We then wrote some tests to show the basic plumbing works.
-
+
+
+
+ Then we wrote some tests to show how the basic plumbing works.
+
diff --git a/src/docbkx/tutorial/import.xml b/src/docbkx/tutorial/import.xml
index e56ba4062..5ca83131e 100644
--- a/src/docbkx/tutorial/import.xml
+++ b/src/docbkx/tutorial/import.xml
@@ -1,12 +1,18 @@
- The dusty archives - Importing Data
+ Importing Data
+ "- Suitable quote"
- Then it was time to pull the data from themoviedb.org. Registering there and getting an API key was simple, using the API on the command line with curl too.
- Looking at the JSON returned for movies and people we decided to enhance our domain model and add some more fields to enrich the UI.
-
- themoviedb.org.
+ Registering there and getting an API key was simple, as was using the API on the command-line with
+ curl. Looking at the JSON returned for movies and people, we decided to enhance our
+ domain model and add some more fields to enrich the UI.
+
+
+
+ JSON movie response
+
-
+
+ JSON actor response
+
-
- For the import process we created a separate importer using Jackson (a JSON library) to fetch and parse the data and then some transactional methods in the MovieDbImportService to actually
- insert it as movies, roles and actors.
-
- The importer used a simple caching mechanism, to keep downloaded actor and movie data on the filesystem, so that we didn't have to overload the remote API.
- In the code below you can see, that we've changed the actor to a person so that we can also accommodate the other folks that participate in movie production.
+
-
+ The importer used a simple caching mechanism to keep downloaded actor and movie data on the
+ filesystem, so that we didn't have to overload the remote API.
+ In the code below you can see that we've changed the actor to a person so that we can also accommodate
+ the other folks that participate in movie production.
+
+
+
+ Importing the data
+
-
-
- The last part involved adding a protected URI to the MovieController to allow importing ranges of movies.
- During testing it became obvious that the calls to themoviedb were a limiting factor. As soon as
- the data was stored locally it took only subseconds to create the data in the Neo4j graph database.
+
+
+
+ The last part involved adding a protected URI to the MovieController to allow importing ranges
+ of movies. During testing, it became obvious that the calls to TheMoviedb.org were a limiting
+ factor. As soon as the data was stored locally, the Neo4j import was a sub-second deal.
-
diff --git a/src/docbkx/tutorial/indexing.xml b/src/docbkx/tutorial/indexing.xml
index 66300e46b..f21e6ab7f 100644
--- a/src/docbkx/tutorial/indexing.xml
+++ b/src/docbkx/tutorial/indexing.xml
@@ -1,38 +1,36 @@
- Do I know you? - Indexing
+ Indexing
+ "- Find them and destroy them."
- There an @Indexed annotation for fields. We wanted to try this out, and use it to guide the next test. We added an @Indexed to the id field of the movie.
- This field is intended to represent the external id that will be used in URIs and will stable over database imports and updates. This time we went with the
- default NodeGraphRepository (previously Finder) to retrieve the indexed movie.
-
-
-
-
+
+
+
+ movieRepository = graphRepositoryFactory.createNodeEntityRepository(Movie.class);
// REMINDER, the "null" stands for an optional index name
- Movie retrievedMovie = movieRepository.findByPropertyValue(null, "id",id);
- assertEqual("retrieved movie matches persisted one",forrestGump,retrievedMovie);
- assertEqual("retrieved movie title matches","Forrest Gump",retrievedMovie.getTitle());
+ Movie retrievedMovie = movieRepository.findByPropertyValue(null, "id", id);
+ assertEqual("retrieved movie matches persisted one", forrestGump, retrievedMovie);
+ assertEqual("retrieved movie title matches", "Forrest Gump", retrievedMovie.getTitle());
}
-
]]>
-
-
- Surprisingly, this failed with an exception about not being in a transaction, which means we forgot to add the @Transactional annotation. That's easy enough to add to the test, and resume the test/code cycle.
-
+
+
diff --git a/src/docbkx/tutorial/neo4j.xml b/src/docbkx/tutorial/neo4j.xml
index d47402804..ef703c1ae 100644
--- a/src/docbkx/tutorial/neo4j.xml
+++ b/src/docbkx/tutorial/neo4j.xml
@@ -1,27 +1,47 @@
- Graphs ahead - Learning Neo4j
+ Learning Neo4j
+ "- Welcome, to the real world."
- Now came the unknown - how to put these domain objects into the graph. First we read up about graph databases, especially Neo4j.
- The Neo4j datamodel consists of nodes and relationships, both of which can have properties. Relationships are first class citizens in Neo4j, meaning we can link together nodes into semantically rich networks - we really liked that.
- Then we found we could index nodes and relationships by {name, value} pairs to quickly get hold of them as starting points for further processing. We also found we could imperatively traverse of relationships using the core API, and in a declarative way using a query-like Traversal Description.
-
- We also learned that Neo4j was fully transactional and completely upholds ACID guarantees for out data. This is unusual for NoSQL databases, but easier for us to get
- our head around than non-transactional eventual consistency. It also makes us feel safe, though it also means that we had to manage transactions. Keep that in mind.
-
- Initially we used the core Neo4j API to get a feeling for that. And also to see, how (probably) the domain might look when it's saved in the graph store. After adding the Maven
- dependency, it was ready to go.
-
- Neo4j.
+ The Neo4j data model consists of nodes and relationships, both of which can have key/value-style
+ properties. Relationships are first-class citizens in Neo4j, meaning we can link together nodes
+ into semantically rich networks. This really appealed to us. Then we found that we were also able to
+ index nodes and relationships
+ by {key, value} pairs. We also found that we could traverse relationships both imperatively using
+ the core API, and declaratively using a query-like
+ Traversal Description.
+
+
+ We also learned that Neo4j is fully transactional and therefore upholds ACID guarantees for our data.
+ This is unusual for NOSQL databases, but easier for us to get our head around than non-transactional
+ eventual consistency. It also made us feel safe, though it also meant that we had to manage transactions.
+ Something to keep in mind for later.
+
+
+ We started out by doing some prototyping with the Neo4j core API to get a feeling for that. And also
+ to see, what the domain might look like when it's saved in the graph database. After adding the Maven
+ dependency for Neo4j, we were ready to go.
+
+
+
+ Neo4j Maven dependency
+
org.neo4j
neo4j
1.3.M05
- ]]>
-
-
+
+
+
+
+ Neo4j core API (transaction code omitted)
+
+
diff --git a/src/docbkx/tutorial/recommendations.xml b/src/docbkx/tutorial/recommendations.xml
index 48e4860a6..5f78ee663 100644
--- a/src/docbkx/tutorial/recommendations.xml
+++ b/src/docbkx/tutorial/recommendations.xml
@@ -1,27 +1,30 @@
- Movies! Friends! Bargains! - Recommendations
+ Recommendations
+ "- Suitable quote"
- In the last part of this exercise we wanted to add recommendations to the app. One obvious recommendation is
- movies that our friends liked
- (and their friends too, but with less importance). The second was recommendations for new friends that also
- liked the movies that we liked most.
+ In the last part of this exercise we wanted to add recommendations to the app. One
+ obvious recommendation was movies that our friends liked (and their friends too, but
+ with less importance). The second recommendation was for new friends that also liked
+ the movies that we liked most.
- Doing this kind of ranking algorithms is really fun with graph databases. They are applied to the graph by
- traversing it in a certain order, collecting information on the go and deciding which paths to follow and what
- to include in the results.
+ Doing these kinds of ranking algorithms is a lot of fun with graph databases. The algorithms
+ are implemented by traversing the graph in a certain order, collecting information on the go,
+ and deciding which paths to follow and what to include in the results.
- Lets say we're only interested in the recommendations of a certain degree of friends.
+ We were only interested in recommendations of a certain degree of friends.
-
+ Recommendations
+ recommendMovies(User user, final int ratingDistance) {
- final DynamicRelationshipType RATED = withName(User.RATED);
- final Map ratings=new HashMap();
- TraversalDescription traversal= Traversal.description().breadthFirst()
+ final DynamicRelationshipType RATED = withName(User.RATED);
+ final Map ratings=new HashMap();
+ TraversalDescription traversal= Traversal.description().breadthFirst()
.relationships(withName(User.FRIEND)).relationships(RATED, OUTGOING).evaluator(new Evaluator() {
public Evaluation evaluate(Path path) {
@@ -43,20 +46,21 @@ public Map recommendMovies(User user, final int ratingDistance) {
}
return Evaluation.EXCLUDE_AND_CONTINUE;
}
- });
+ });
- Map result=new HashMap();
- final Iterable movies = movieRepository.findAllByTraversal(user, traversal); // lazy traversal results
- for (Movie movie : movies) { // assign movie to averaged rating
+ Map result=new HashMap();
+ final Iterable movies = movieRepository.findAllByTraversal(user, traversal); // lazy traversal results
+ for (Movie movie : movies) { // assign movie to averaged rating
final int[] stars = ratings.get(movie.getNodeId());
result.put(movie, stars[0]/stars[1]);
- }
- return result;
+ }
+ return result;
}
- ]]>
+]]>
+
- The UserController just calls this method, adds it's results to the the model and the view renders the
- recommendation alongside with your own ratings.
+ The UserController simply called this method, added its results to the model, and the
+ view rendered the recommendation alongside the user's own ratings.
diff --git a/src/docbkx/tutorial/relationships.xml b/src/docbkx/tutorial/relationships.xml
index aefcf645a..0e1079779 100644
--- a/src/docbkx/tutorial/relationships.xml
+++ b/src/docbkx/tutorial/relationships.xml
@@ -1,54 +1,74 @@
- A convincing act - Relationships
+ Relationships
+ "- Suitable quote"
+
+ Our application was not yet very much fun, just storing movies and actors. After all,
+ the power is in the relationships between them.
+
- Value in Relationships - Creating them
+ Creating relationships
- Next were relationships. Direct relationships didn't require any annotation. Unfortunately we had none of those, because ours had more semantics.
- So we went for the Role relationship between Movie and Actor.
- It had to be annotated with @RelationshipEntity and the @StartNode and @EndNode had to be marked. So our Role looked like this:
+ Relationships without properties ("anonymous" relationships) don't require any @RelationshipEntity
+ classes. Unfortunately we had none of those, because our relationships were richer.
+ Therefore we went with the Role relationship between Movie and Actor.
+ It had to be annotated with @RelationshipEntity and the @StartNode and @EndNode had to be marked.
+ So our Role looked like this:
-
+ Role class
+
-
- When writing a test for that we tried to create the relationship entity with new, but got an exception saying that this is not allowed. This must be a strange restriction
-about having only correctly constructed RelationshipEntities. To fix it, we had to recall the relateTo method from the introduced methods on the NodeEntities. After
-checking it turned out to be exactly what we needed. We then added the method for connecting movies and actors to the actor - which seems a more natural fit.
+]]>
+
- new
+ keyword, but we got an exception saying that it was not allowed. At first this surprised us, but
+ then we realized that a relationship entity must have a starting entity and ending entity. It
+ turned out that the aspect had introduced a entity.relateTo method in the node entities.
+ It turned out to be exactly what we needed. We simply added a method to the Actor class, connecting
+ it to movies.
+
+
+
+ Relating actors to movies
+
+ public Role playedIn(Movie movie, String roleName) {
+ Role role = relateTo(movie, Role.class, "ACTS_IN");
+ role.setRole(roleName);
+ return role;
+ }
+}
+]]>
+
- Who's there ? - Accessing related entities
+ Accessing related entities
- What was left? Accessing those relationships. We already had the appropriate fields in both classes. Time to annotate them correctly. For the fields providing
-access to the entities on the each side of the relationship this was straightforward. Providing the target type again (thanks to Java's type erasure) and the
-relationship type (learned from the Neo4j lesson before) there was only the direction left. Which defaults to OUTGOING so only for the movie we had to
-specify it.
+ Now we wanted to find connected entities.
+ We already had fields for the relationships in both classes. Now it was time to annotate them
+ correctly. It turned out that we needed to provide the target type of the fields again, due to
+ Java's type erasure. The Neo4j relationship type and direction were easy to figure out.
+ The direction even defaulted to outgoing, so we only had to specify it for the movie.
-
+ @RelatedTo usage
+ cast;
@@ -69,41 +88,51 @@ class Actor {
return role;
}
}
- ]]>
+]]>
+
- While reading about those relationship-sets we learned that they are handled by managed collections of Spring Data Graph. So whenever we add something to the
- set or remove it, it automatically reflects that in the underlying relationships. Neat. But this also meant we mustn't initialize the fields. Something we will
- certainly forget not to do in the future, so watch out for it.
+ While reading about these relationship collections, we learned that they are actually
+ Spring Data Graph-managed sets. So whenever we add or remove something from the set,
+ it automatically gets reflected in the underlying relationships. That's neat! But this
+ also meant we did not need to initialize the fields. That could be easy to forget.
- We made sure to add a test for those, so are assured that the collections worked as advertised (and also ran into the intialization problem above).
+ We made sure to add a test for those, so we were assured that the collections worked as
+ advertised.
- May I introduce ? - Accessing Relationships themselves
-
- But we still couldn't access the Role relationships. There was more to read about this. For accessing the relationship in between the nodes there was a separate
- annotation @RelatedToVia. And we had to declare the field as readonly Iterable<Role>. That should make sure that we never tried to add Roles (which I couldn't create on my own anyway)
- to this field. Otherwise the annotation attributes were similar to those used for @RelatedTo. So off we went, creating our first real relationship (just kidding).
-
- Accessing the relationship entities
+
+ But we still couldn't access the Role relationships. It turned out that there was a separate
+ annotation @RelatedToVia for accessing the actual relationship entities . And we had
+ to declare the field as an Iterable<Role>, with read-only semantics. This appeared to mean
+ that we were not able to add new roles though the field. Adding relationship entities seemed like
+ it had to be done by using entity.relateTo(). The annotation attributes were similar to
+ those used for @RelatedTo. So off we went, creating our first real relationship (just kidding).
+
+
+
+ @RelatedToVia usage
+ cast;
+ Set cast;
@RelatedToVia(elementClass = Role.class, type = "ACTS_IN", direction = Direction.INCOMING)
Iterable roles;
}
- ]]>
-
- After the tests proved that those relationship fields really mirrored the underlying relationships in the graph and instantly reflected additions and removals we were
- pretty satisfied with our domain.
+]]>
+
+
+
+ After watching the tests pass, we were confident that the relationship fields really mirrored
+ the underlying relationships in the graph. We were pretty satisfied with our domain.
diff --git a/src/docbkx/tutorial/repository.xml b/src/docbkx/tutorial/repository.xml
index 188c4ac05..ebbf70c2e 100644
--- a/src/docbkx/tutorial/repository.xml
+++ b/src/docbkx/tutorial/repository.xml
@@ -1,22 +1,33 @@
- Serving a good cause - Repository
-
- That was the first method to add to the brand new cineasts repository.
- First step was to create an (still empty) repository interface for Movie (and Actor). We added the repository configuration
- to our application context.
- Then we created a repository for the application, annotated it with @Repository and @Transactional and injected the movie repository.
- We did the same for the Actor.
-
-
- {
- // findById(String id) - automatic derived finder for a future SDG release
-}
-
+ Repositories
+ "- Suitable quote"
+
+ We wanted to add repositories with domain-specific operations. We started by creating a
+ movie-specific repository, simply by creating an empty interface. It is more convenient to
+ work with a named interface rather than different versions of a generic one.
+
+ Movie repository
+ {}
+]]>
+
+ Then we added it to the Spring context configuration by simply adding:
+
+ Repository context configuration
+
-
+]]>
+
+ We then created the domain-specific repository class, annotating it with @Repository and
+ @Transactional, and injected the movie repository.
+
+
+
+ Domain-specific repository
+
-
+]]>
+
+
+
+ We did the same for the actors and users.
+
diff --git a/src/docbkx/tutorial/running.xml b/src/docbkx/tutorial/running.xml
index 65595340c..a4de80d88 100644
--- a/src/docbkx/tutorial/running.xml
+++ b/src/docbkx/tutorial/running.xml
@@ -1,50 +1,60 @@
- Curtains Up! - Get it running
+ Get it running
+ "- Suitable quote"
+
+ Now we had a pretty complete application. It was time to put it to the test.
+
- Requisites - Populating the database
+ Populating the database
- Time to put this on display. But we needed some test data first. So we wrote a small class for populating the database which could be called from our controller.
- To make it safe to call several times we added index lookups to check for existing entries. A simple /populate endpoint for the controller that called it would
- be enough for now.
+ Before we opened the gates we needed to add some movie data. So we wrote a small class for
+ populating the database which could be called from our controller. To make it safe to call
+ several times we added index lookups to check for existing entries. A simple /populate endpoint
+ for the controller that called it would be enough for now.
-
+ Populating the database - Controller
+ populateDatabase() {
- Actor tomHanks = new Actor("1", "Tom Hanks").persist();
- Movie forestGump = new Movie("1", "Forrest Gump").persist();
- tomHanks.playedIn(forestGump,"Forrest");
- return asList(forestGump);
- }}
+ @Transactional
+ public List populateDatabase() {
+ Actor tomHanks = new Actor("1", "Tom Hanks").persist();
+ Movie forestGump = new Movie("1", "Forrest Gump").persist();
+ tomHanks.playedIn(forestGump,"Forrest");
+ return asList(forestGump);
+ }
+}
@Controller
public class MovieController {
- private DatabasePopulator populator;
+ private DatabasePopulator populator;
- @Autowired
- public MovieController(DatabasePopulator populator) {
- this.populator = populator;
- }
+ @Autowired
+ public MovieController(DatabasePopulator populator) {
+ this.populator = populator;
+ }
- @RequestMapping(value = "/populate", method = RequestMethod.GET)
- public String populateDatabase(Model model) {
- Collection movies=populator.populateDatabase();
- model.addAttribute("movies",movies);
- return "/movies/list";
- }
+ @RequestMapping(value = "/populate", method = RequestMethod.GET)
+ public String populateDatabase(Model model) {
+ Collection movies = populator.populateDatabase();
+ model.addAttribute("movies",movies);
+ return "/movies/list";
+ }
}
-
- ]]>
-
+
+
+ Populating the database - JSP
+
<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
@@ -66,50 +76,63 @@ public class MovieController {
No Movie with id ${id} found!
- ]]>
-
- See the misused GET parameter for that (don't do this at home, the REST guys will be upset). This is only for running it from the browser
- address line. Better use POST and curl for the call. So we called the URI and it showed the single added movie
- on screen.
+]]>
+
+
+ Accessing the URI showed the single added movie on screen.
+
+
+ Pardon the misused GET parameter for that (don't try this at home, the
+ REST guys will be hunt you down). This is only for
+ running it from the browser address line. The next iteration of this website would use a button
+ with POST.
+
- Behind the scenes - Peeking at the Datastore
-
+ Inspecting the datastore
+
+ Being the geeks we are, we also wanted to inspect the raw data in the database. Reading the
+ Neo4j docs, there were a couple of different ways of going about this.
+
- Eye candy - Neoclipse visualization
+ Neoclipse visualization
- After filling the database we wanted to see what the graph looked like. So we checked out two tools that are available for inspecting the graph. First Neoclipse, an
- eclipse RCP application or plugin that connects to existing graph stores and visualizes their content. After getting an exception about concurrent access, I learned
- that I have to use Neoclipse in readonly mode when my webapp had an active connection to the store. Good to know.
-
-
-
-
-
-
-
+ First we tried Neoclipse, an Eclipse RCP application/plugin that opens an existing graph store
+ and visualizes its content. After getting an exception about concurrent access, we learned that
+ we have to use Neoclipse in read-only mode when our webapp was still running. Good to know.
+
+
+
+
+
+
+
- Hardcore "Hacking" - Neo4j Shell
+ The Neo4j Shell
- Besides our movies and actors connected by ACTS_IN relationships there were some other nodes. The reference node which is an automatically provided "root node" in Neo4j and can be used
- to anchor subgraphs for easier access. And Spring Data Graph also represented the type hierarchy of my entities in the graph. Obviously for some internal housekeeping
- and type checking.
-
- For console junkies there is also a shell that can reach into a running neo4j store (if that one was started with enableRemoteShell) or provide readonly access
- to a graph store directory.
-
-
-
+
+
+ Starting the Neo4j Shell
+
-
- It uses some shell metaphors like cd and ls to navigate the graph. There are also more advanced commands like using indexes and traversals. I tried to play around with them
- in this shell sesson.
-
-
+
+
+
+ The shell was very similar to a standard Bash shell. We were able to cd to
+ between the nodes, and ls the relationships and properties. There were also
+ more advanced commands for indexing and traversals.
+
+
+
+ Neo4j Shell usage
+ (3)
(me) --[SUBREF_org.neo4j.cineasts.domain.Movie]-> (6)
@@ -148,6 +171,7 @@ neo4j-sh[readonly] (The Matrix,93)$ ls
(me) --[INSTANCE_OF]-> (6)
(me) <-[RATED]-- (Micha,1)
]]>
+
diff --git a/src/docbkx/tutorial/security.xml b/src/docbkx/tutorial/security.xml
index 410d1745d..501d8e7ec 100644
--- a/src/docbkx/tutorial/security.xml
+++ b/src/docbkx/tutorial/security.xml
@@ -1,63 +1,66 @@
- Protecting Assets - Adding Security
+ Adding Security
+ "- I love the smell of napalm in the morning."
- To have a user in the webapp we had to put it in the session and add login and registration pages. Of course the pages that only worked with a valid user
-account had to be secured as well.
-
-We used Spring Security for that, writing a simple UserDetailsService that used a repository for looking up the users and validating their credentials. The config is located
-in a separate applicationContext-security.xml. But first, as always, Maven and web.xml setup.
+ To have a user in the webapp we had to put it in the session and add login and registration
+ pages. Of course the pages that were only meant for logged-in users had to be secured as well.
-
- pom.xml for spring-security
-
- org.springframework.security
- spring-security-web
- ${spring.version}
-
-
- org.springframework.security
- spring-security-config
- ${spring.version}
-
- ]]>
-
+ Being Spring users, we naturally used Spring Security for this. We wrote a simple UserDetailsService
+ that used a repository for looking up the users and validating their credentials. The config is located
+ in a separate applicationContext-security.xml. But first, as always, Maven and web.xml setup.
-
- web.xml
-
- contextConfigLocation
-
- /WEB-INF/applicatioContext-security.xml
- /WEB-INF/applicationContext.xml
-
-
-
-
- org.springframework.web.context.ContextLoaderListener
-
-
-
- springSecurityFilterChain
- org.springframework.web.filter.DelegatingFilterProxy
-
-
-
- springSecurityFilterChain
- /*
-
- ]]>
-
+
+ Spring Security pom.xml
+
+ org.springframework.security
+ spring-security-web
+ ${spring.version}
+
+
+ org.springframework.security
+ spring-security-config
+ ${spring.version}
+
+]]>
+
-
- applicationContext-security.xml
-
+ Spring Security web.xml
+
+ contextConfigLocation
+
+ /WEB-INF/applicationContext-security.xml
+ /WEB-INF/applicationContext.xml
+
+
+
+
+ org.springframework.web.context.ContextLoaderListener
+
+
+
+ springSecurityFilterChain
+ org.springframework.web.filter.DelegatingFilterProxy
+
+
+
+ springSecurityFilterChain
+ /*
+
+]]>
+
+
+
+
+ Spring Security applicationContext-security.xml
+
@@ -83,10 +86,10 @@ in a separate applicationContext-security.xml. But first, as always, Maven and w
]]>
-
-
- UserDetailsService and UserDetails implementation
-
+
+ UserDetailsService and UserDetails implementation
+
-
+]]>
+
- After that a logged in user was available in the session and could so be used for all the social interactions. Most of the work done next was adding controller methods
-and JSPs for the views. We used the helper method getUserFromSession() in the controllers to access the logged in user and put it in the model for rendering.
- As a teaser we'd like to show off the user profile page, as it will be rendered after UX heavy lifting.
+ Any logged-in user was now available in the session, and could be used for all the social
+ interactions. The remaining work for this was mainly adding controller methods and JSPs
+ for the views. We used the helper method getUserFromSession() in the controllers
+ to access the logged-in user and put it in the model for rendering.
+ Here's what the UI had evolved to:
diff --git a/src/docbkx/tutorial/setup.xml b/src/docbkx/tutorial/setup.xml
index 604166c64..d7e443648 100644
--- a/src/docbkx/tutorial/setup.xml
+++ b/src/docbkx/tutorial/setup.xml
@@ -1,31 +1,38 @@
- Scope: Spring
+ The Spring stack
+ "- a light from the shadows shall spring"
- Being Spring developers, we would, of course, choose components of the Spring Framework to do most of the work. We'd already come up with the ideas -
- that should be enough.
-
- What database would fit both the complex network of cineasts, movies, actors, roles, ratings and friends? And also be able to support the
- recommendation algorithms that we had in mind? We had no idea.
-
- But, wait, there is the new Spring Data project, started in 2010, which brings
- the convenience of the Spring programming model to NoSQL databases. That should fit our experience and help us to get started. We looked
- at the list of projects supporting the different NoSQL databases. Only one mentioned the kind of social network we were thinking of -
- Spring Data Graph for Neo4j, a graph database. Neo4j's pitch of "value in relationships" and the accompanying docs looked like what we needed.
+ Being Spring developers, we naturally choose components from the Spring stack to do all the
+ heavy lifting. After all, we have the concept etched out, so we're already halfway there.
+
+
+ What database would fit both the complex network of cineasts, movies, actors, roles, ratings,
+ and friends, while also being able to support the recommendation algorithms that we had in mind?
+ We had no idea.
+
+
+ But hold your horses, there is this new Spring Data project, started in 2010, which brings
+ the convenience of the Spring programming model to NOSQL databases. That should be in line with
+ what we already know, providing us with a quick start. We had a look at the list of projects
+ supporting the different NOSQL databases out there. Only one of them mentioned the kind of social
+ network we were thinking of - Spring Data Graph for Neo4j, a graph database. Neo4j's slogan of
+ "value in relationships" and the accompanying docs looked like what we needed.
We decided to give it a try.
- Preparations - Required Setup
+ Required setup
- To setup the project we created a public github account and began setting up the infrastructure for a spring web project using Maven as build
- system. So we added the dependencies for the Spring Framework libraries, put the web.xml for the DispatcherServlet and the applicationContext.xml
- in the webapp directory.
-
-
-
- pom.xml
-
+
+
+ Project pom.xml
+
3.0.5.RELEASE
@@ -57,13 +64,12 @@
+]]>
+
- ]]>
-
-
-
- web.xml
-
+ Project web.xml
+
org.springframework.web.context.ContextLoaderListener
@@ -78,19 +84,22 @@
dispatcherServlet
/
- ]]>
-
-
-
- With this setup we were ready for the first spike: creating a simple MovieController showing a static view. Check. Next was the setup for Spring Data Graph.
- We looked at the README at github and then checked it with the manual. Quite a lot of Maven setup for AspectJ but otherwise not so much to add.
- Time to add a few lines to our Spring configuration.
-
-
-
-
- applicationContext.xml
-
+
+
+
+ With this setup in place we were ready for the first spike: creating a simple MovieController
+ showing a static view. See the Spring Framework documentation for information on doing this.
+
+
+
+
+
+
+
+
+ Project applicationContext.xml
+
]]>
-
-
- dispatcherServlet-servlet.xml
-
+
+ Project dispatcherServlet-servlet.xml
+
@@ -125,10 +134,12 @@
]]>
-
+
-
- We spun up Jetty to see if there were any obvious issues with the config. It all seemed to work just fine. Check.
+
+
+ We spun up Jetty by doing mvn jetty:run to see if there were any obvious
+ issues with the config. It all seemed to work just fine.
diff --git a/src/docbkx/tutorial/social.xml b/src/docbkx/tutorial/social.xml
index 1c5b5f1ce..6aafc16e7 100644
--- a/src/docbkx/tutorial/social.xml
+++ b/src/docbkx/tutorial/social.xml
@@ -1,29 +1,34 @@
- Movies 2.0 - Adding social
+ Adding social
+ "- Suitable quote"
- But this was just a plain old movie database (POMD). Our idea of socializing this business wasn't yet realized.
+ So far, the website had only been a plain old movie database (POMD?). We now wanted to add
+ a touch of social to it.
- Look, mom a Cineast! - Users
+ Users
- So we took the User class that we'd already coded and made it a full fledged Spring Data Graph member.
- We added the ability to make friends and to rate movies. With that there was also a simple UserRepository that
- was able to look up users by id.
+ So we started out by taking the User class that we'd already coded and made it a
+ full-fledged Spring Data Graph entity. We added the ability to make friends and to
+ rate movies. With that we also added a simple UserRepository that was able to look
+ up users by ID.
-
+ Social entities
+ ratings;
- @RelatedTo(elementClass=User.class, type="FRIEND")
+ @RelatedToVia(elementClass = Rating.class, type = RATED)
+ Iterable ratings;
+
+ @RelatedTo(elementClass = User.class, type = "FRIEND", direction=Direction.BOTH)
Set friends;
public Rating rate(Movie movie, int stars, String comment) {
@@ -33,6 +38,7 @@ class User {
this.friends.add(user);
}
}
+
@RelationshipEntity
class Rating {
@StartNode User user;
@@ -40,60 +46,70 @@ class Rating {
int stars;
String comment;
public Rating rate(int stars, String comment) {
- this.stars=stars; this.comment = comment;
+ this.stars = stars; this.comment = comment;
return this;
}
}
- ]]>
-
+]]>
+
- We extended my DatabasePopulator to add some users and ratings to the initial setup.
+ We extended the DatabasePopulator to add some users and ratings to the initial setup.
-
+ Populate users and ratings
+ populateDatabase() {
Actor tomHanks = new Actor("1", "Tom Hanks").persist();
Movie forestGump = new Movie("1", "Forrest Gump").persist();
- tomHanks.playedIn(forestGump,"Forrest");
+ tomHanks.playedIn(forestGump, "Forrest");
- User me = new User("micha", "Micha", "password", User.Roles.ROLE_ADMIN,User.Roles.ROLE_USER).persist();
+ User me = new User("micha", "Micha", "password",
+ User.Roles.ROLE_ADMIN, User.Roles.ROLE_USER).persist();
Rating awesome = me.rate(forestGump, 5, "Awesome");
- User ollie = new User("ollie", "Olliver", "password",User.Roles.ROLE_USER).persist();
+ User ollie = new User("ollie", "Olliver", "password", User.Roles.ROLE_USER).persist();
ollie.rate(forestGump, 2, "ok");
me.addFriend(ollie);
return asList(forestGump);
}
- ]]>
+]]>
+
- Beware, Critics - Rating
+ Ratings for movies
- We also put a ratings field into the movie to be able to show its ratings. And a method to average its star rating.
+ We also put a ratings field into the Movie class to be able to get a movie's ratings,
+ and also a method to average its star rating.
-
+ Getting the rating of a movie
+ ratings;
public int getStars() {
- int stars, int count;
+ int stars = 0, count = 0;
for (Rating rating : ratings) {
stars += rating.getStars(); count++;
}
return count == 0 ? 0 : stars / count;
}
}
- ]]>
+]]>
+
- Fortunately our tests highlighted the division by zero error when calculating the stars for a movie without ratings.
- Next steps were to add this information to the UI of movie and create a user profile page. But for that to happen
- they must be able to log in.
+ Fortunately our tests highlighted the division by zero error when calculating the stars for
+ a movie without ratings. The next steps were to add this information to the movie presentation in
+ the UI, and creating a user profile page. But for that to happen, users must first be able to log in.
diff --git a/src/docbkx/tutorial/spring-data-graph.xml b/src/docbkx/tutorial/spring-data-graph.xml
index 224a1291c..e476b9416 100644
--- a/src/docbkx/tutorial/spring-data-graph.xml
+++ b/src/docbkx/tutorial/spring-data-graph.xml
@@ -1,18 +1,24 @@
- Conjuring Magic - Spring Data Graph
+ Spring Data Graph
+ "- Do you think that's air you're breathing now?"
- That was the pure graph database. Using this in our domain would pollute our classes with lots of graph
- database details. We don't want that. Spring Data Graph
- promised to do the heavy lifting for us. So we checked that next.
-
- Spring Data Graph depends heavily on AspectJ magic. Some parts of our classes would behave differently,
- but it would not be visible in our code. We were going to give it a try.
+ So far it had all been pure Spring Framework and Neo4j. However, using the Neo4j code in our domain
+ classes polluted them with graph database details. For this application, we wanted to keep the domain
+ classes clean. Spring Data Graph promised to do the heavy lifting for us, so we continued with
+ investigating it.
- First step was lots of Maven configuration.
-
+
+ The first step was to configure Maven:
+
+ Spring Data Graph Maven configuration
+
1.6.11.RELEASE
@@ -70,18 +76,25 @@
1.6
- ]]>
-
- The Spring configuration was much easier, thanks to a provided namespace.
-
-
+
+
+
+ The Spring context configuration was much easier, thanks to a provided namespace:
+
+
+
+ Spring Data Graph context configuration
+
-
+ xmlns:datagraph="http://www.springframework.org/schema/data/graph"
+ xsi:schemaLocation="... http://www.springframework.org/schema/data/graph
+ http://www.springframework.org/schema/data/graph/datagraph-1.0.xsd">
+ ...
+ ...
- ]]>
+]]>
+
diff --git a/src/docbkx/tutorial/user-experience.xml b/src/docbkx/tutorial/user-experience.xml
index 5b92a7ccc..8859d6844 100644
--- a/src/docbkx/tutorial/user-experience.xml
+++ b/src/docbkx/tutorial/user-experience.xml
@@ -1,15 +1,16 @@
- Oh the Glamour - More UI
+ More UI
+ "- Suitable quote"
- To create a nice user experience, we wanted to have a nice looking app, not something that looked like a toddler
- made it. So we got some UX people involved and the results were impressive. This sections presents some of the
- remaining screenshots of cineasts.net.
+ To create a nice user experience, we wanted to have a nice looking app. Not something that
+ looked like a toddler made it. So we got some user experience people involved and the results
+ were impressive. This sections presents some of the remaining screen shots of Cineasts.net.
- Some of the noteworthy things. As Spring Data Graph does a read-through to the datastore for property and relationship
- access we tried to minimize that by using <c:var/> several times.
+ Some noteworthy things. Since Spring Data Graph reads through down to the database for property
+ and relationship access, we tried to minimize that by using <c:var/> several times.
The app contains very little javascript / ajax code right now, that will change when it moves ahead.
diff --git a/src/docbkx/tutorial/webapp.xml b/src/docbkx/tutorial/webapp.xml
index 4c5405c07..ebe38969f 100644
--- a/src/docbkx/tutorial/webapp.xml
+++ b/src/docbkx/tutorial/webapp.xml
@@ -1,12 +1,18 @@
- Showing off - Web views
+ Web views
+ "- Suitable quote"
- After we had the means to put some data in the graph database, we also wanted to show it. So adding the controller method to show a single movie with its attributes
-and cast in a jsp was straightforward. Actually just using the repository to look the movie up and add it to the model. Then forward to the /movies/show view and voilá.
-
-
+
+
+ Controller for showing movies
+
-
-
- Later the nice UI would look like that:
-
-
-
-
-
-
+
+
+
+ The UI had now evolved to this:
+
+
+
+
+
+
- What was his name? - Searching
+ Searching
- The next thing was to allow users to search for some movies. So we needed some fulltext-search capabilities. As the index provider implementation of Neo4j builds on
-lucene we were delighted to see that fulltext indexes are supported out of the box.
+ The next thing was to allow users to search for movies, so we needed some fulltext search
+ capabilities. As the index provider implementation of Neo4j is based on Apache Lucene,
+ we were delighted to see that fulltext indexes were supported out of the box.
- We happily annotated the title field of my Movie class with @Index(fulltext=true) and was told with an exception that we have to specify a separate index name for that.
-So it became @Indexed(fulltext = true, indexName = "search"). The corresponding graphRepository method is called findAllByQuery. So there was our second repository method for
-searching movies. To restrict the size of the returned set we just added a limit for now that truncates the result after so many entries.
+ We happily annotated the title field of the Movie class with @Indexed(fulltext = true).
+ We got an exception back telling us that we have to specify a separate index name.
+ So we simply changed it to @Indexed(fulltext = true, indexName = "search"). The corresponding
+ repository method is called findAllByQuery. To restrict the size of the returned set we simply
+ added a limit that truncates the result.
- findMovies(String query, int count) {
- List movies=new ArrayList(count);
- for (Movie movie : movieRepository.findAllByQuery("title", query)) {
- movies.add(movie);
- if (count-- == 0) break;
+
+ Searching for movies
+ findMovies(String query, int count) {
+ List movies=new ArrayList(count);
+ for (Movie movie : movieRepository.findAllByQuery("title", query)) {
+ movies.add(movie);
+ if (count-- == 0) break;
+ }
+ return movies;
}
- return movies;
}
]]>
+
- Look what we've found - Listing Results
-
+ Listing results
- We then used this result in the controller to render a list of movies driven by a search box. The movie properties and the cast was accessed by the getters in the
-domain classes.
+ We then used this result in the controller to render a list of movies, driven by a
+ search box. The movie properties and the cast were accessible through the getters
+ in the domain classes.
-
+ Search controller
+ movies = repository.findMovies(query, 20);
@@ -68,9 +86,12 @@ public String findMovies(Model model, @RequestParam("q") String query) {
return "/movies/list";
}
]]>
+
-
+
+ Movies
@@ -91,14 +112,15 @@ public String findMovies(Model model, @RequestParam("q") String query) {
]]>
+
+
+
+ The UI now looked like this:
+
+
+
+
+
-
- Here is another teaser, what the final UX would look like for that:
-
-
-
-
-
-