added tutorial content to guidebook

This commit is contained in:
Michael Hunger
2011-03-14 16:58:04 +01:00
parent ad56408986
commit 93c415f9f6
22 changed files with 708 additions and 143 deletions

View File

@@ -170,6 +170,11 @@
<version>4.4</version>
<scope>runtime</scope>
</dependency>
<!--dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>fop</artifactId>
<version>0.93</version>
</dependency-->
<dependency>
<groupId>javax.media</groupId>
<artifactId>jai-core</artifactId>
@@ -183,7 +188,9 @@
<foCustomization>${project.basedir}/src/docbkx/resources/xsl/fopdf.xsl</foCustomization>
<!-- original spring-build fopdf xsl
<foCustomization>src/docbkx/resources/xsl/pdf/fopdf.xsl</foCustomization> -->
<htmlStylesheet>css/html.css</htmlStylesheet>
<htmlStylesheet>${project.basedir}/src/docbkx/resources/css/html.css</htmlStylesheet>
<imgSrcPath>${project.basedir}/src/docbkx/resources/images/</imgSrcPath>
<admonGraphicsPath>${project.basedir}/src/docbkx/resources/images/</admonGraphicsPath>
<!-- produce single-page html output -->
<chunkedOutput>false</chunkedOutput>
<htmlCustomization>${project.basedir}/src/docbkx/resources/xsl/html.xsl</htmlCustomization>

View File

@@ -37,7 +37,7 @@
<toc/>
<preface id="foreword:rod">
<!--preface id="foreword:rod">
<title>Foreword: Rod Johnson, CEO of SpringSource</title>
<para>
Rod ...
@@ -49,7 +49,7 @@
<para>
Emil ...
</para>
</preface>
</preface-->
<preface id="about-book">
<title>About this book</title>
@@ -69,14 +69,21 @@
</para>
</partintro>
<xi:include href="tutorial/about-spring-data.xml"/>
<xi:include href="tutorial/about-tutorial.xml"/>
<xi:include href="tutorial/setup.xml"/>
<xi:include href="tutorial/domain.xml"/>
<xi:include href="tutorial/neo4j.xml"/>
<xi:include href="tutorial/spring-data-graph.xml"/>
<xi:include href="tutorial/annotations.xml"/>
<xi:include href="tutorial/data-access.xml"/>
<xi:include href="tutorial/indexing.xml"/>
<xi:include href="tutorial/repository.xml"/>
<xi:include href="tutorial/relationships.xml"/>
<xi:include href="tutorial/running.xml"/>
<xi:include href="tutorial/webapp.xml"/>
<xi:include href="tutorial/security.xml"/>
<xi:include href="tutorial/social.xml"/>
<xi:include href="tutorial/user-experience.xml"/>
<xi:include href="tutorial/import.xml"/>
<xi:include href="tutorial/recommendations.xml"/>
</part>

View File

@@ -24,7 +24,7 @@
<para>Executing the application creates the following graph in the Graph Database:</para>
<mediaobject>
<imageobject>
<imagedata fileref="images/helloworlds.png" />
<imagedata fileref="helloworlds.png" format="PNG"/>
</imageobject>
</mediaobject>
</section>
@@ -41,11 +41,15 @@
<para>See the readme file for instruction on how to compile and run the application.</para>
<para>An excerpt of the data stored in the Graph Database after executing the application:
</para>
<mediaobject>
<imageobject>
<imagedata fileref="images/imdb.png" />
</imageobject>
</mediaobject>
<para>
<screenshot>
<mediaobject>
<imageobject>
<imagedata fileref="imdb.png"/>
</imageobject>
</mediaobject>
</screenshot>
</para>
</section>
<!--
@@ -58,7 +62,7 @@
</para>
<mediaobject>
<imageobject>
<imagedata fileref="images/restaurant.png" />
<imagedata fileref="restaurant.png" />
</imageobject>
</mediaobject>
</section>
@@ -74,7 +78,7 @@
</para>
<mediaobject>
<imageobject>
<imagedata fileref="images/restaurant-social.png" />
<imagedata fileref="restaurant-social.png" />
</imageobject>
</mediaobject>
</section>

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<chapter id="tutorial:about-spring-data">
<title>About Spring Data</title>
<para>
* What is spring data?
* What is spring data graph?
* Why should I use it?
</para>
</chapter>

View File

@@ -1,8 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<chapter id="tutorial:about-tutorial">
<title>About this tutorial</title>
<title>Allow me to introduce - Cineasts.net</title>
<para>
* What is Cineasts.net
Once upon a time I wanted to build a social movie database myself. First things first - I had a name: "Cineasts" - the people
crazy about movies. So I went ahead and bought the domain, cineasts.net. So, the project was almost done.
</para><para>
I had some ideas as well. Of course there should be Actors who play Roles in Movies. I needed the Cineast, too, someone
had to rate the movies after all. 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 could recommend new friends and movies to them, derived from their interests and
existing friends.
</para><para>
<mediaobject>
<imageobject>
<imagedata fileref="cineasts.png"/>
</imageobject>
</mediaobject>
</para><para>I looked for possible sources for data, IMDB was my first stop, but they charge 15k for data usage. Fortunately I found themoviedb.org which has
liberal terms and conditions and a nice API for fetching the data.
</para><para>
There were many more ideas but I wanted to get something done over the course of one day. So this was the scope I was going to tackle. * What is Cineasts.net
</para>
</chapter>

View File

@@ -1,27 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<chapter id="tutorial:annotations">
<title>Annotating the domain model</title>
<title>Decorations - Annotated Domain</title>
<para>
...
</para>
<section>
<title>Persistent entities</title>
<para>
...
</para>
</section>
<section>
<title>Indexing</title>
<para>
...
</para>
</section>
<section>
<title>Relationships</title>
<para>
...
</para>
</section>
I looked at the documentation again, found a simple Hello-World example and tried to understand it. The entities were annotated with @NodeEntity, that was simple, so
I added it too. Relationships got their own annotation named @RelationshipEntity. Property fields should be taken care of automatically.
</para><para>
Ok lets put this into a test. How to assure that a field was 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 Finder approach which I ignored for new. Lets keep things simple.
How to persist an entity and how to get its id? No idea. So further study of the documentation revealed that there were a bunch of methods introduced to the
entities by the aspects. That was not obvious. But I found the two that would help me here - entity.persist() and entity.getNodeId().
</para><para>
So my test looked like this.
</para><para>
<programlisting language="java" ><![CDATA[
@Autowired GraphDatabaseContext graphDatabaseContext;
@Test public void persistedMovieShouldBeRetrievableFromGraphDb() {
Movie forestGump = new Movie("Forest Gump", 1994).persist();
Movie retrievedMovie = graphDatabaseContext.getById(forestGump.getNodeId());
assertEqual("retrieved movie matches persisted one",forestGump,retrievedMovie);
assertEqual("retrieved movie title matches","Forest Gump",retrievedMovie.getTitle());
}
]]></programlisting>
</para><para>
That worked, cool. But what about transactions I didn't declare the test to be transactional? After further reading I learned that persist() creates an
implicit transaction - so that was like an EntityManager would behave. Ok for me. I also learned that for more complex operations on the entities I needed
external transactions.
</para>
</chapter>

View File

@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<chapter id="tutorial:data-access">
<title>Accessing data</title>
<para>
...
</para>
<section>
<title>Importing data</title>
<para>
...
</para>
</section>
<section>
<title>Repository</title>
<para>
...
</para>
</section>
<section>
<title>Controllers</title>
<para>
...
</para>
</section>
</chapter>

View File

@@ -1,38 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<chapter id="tutorial:domain">
<title>The domain</title>
<title>Setting the Stage - Movies Domain</title>
<para>
...
</para>
<section>
<title>Movie</title>
<para>
...
The domain model was the next thing I planned to work on. I wanted to flesh it out first before diving into library details. Going along the ideas outlined before I
came up with this. I also peeked in the datamodel of my import data source themoviedb to confirm that it matched my expectations.
<!--
http://yuml.me/diagram/scruffy;dir:lr/class/%23%20Cineasts.net%20Domain,%20%5BActor%7Cname;%7CplayedIn()%5D*-ACTS_IN-%3E%5BRole%7Cname;%5D,%20%5BRole%5D-ACTS_IN-%3E*%5BMovie%7Ctitle;year%5D,%20%5BUser%7Clogin;name;password;%7Crate();befriend();%5D*-RATED-%3E%5BRating%7Cstars;comment;%5D,%20%5BRating%5D-RATED-%3E*%5BMovie%5D,%20%5BUser%5D*-FRIEND-%3E*%5BUser%5D.png
-->
</para><para>
<mediaobject>
<imageobject>
<imagedata fileref="domain.png"/>
</imageobject>
</mediaobject>
</para><para>
In Java code this looked like. Pretty straightforward.
</para><para>
<programlisting language="java" ><![CDATA[
class Movie {
int id;
String title;
int year;
Set<Role> cast;
}
class Actor {
int id;
String name;
Set<Movie> filmography;
Role playedIn(Movie movie, String role);
}
class Role {
Movie movie;
Actor actor;
String role;
}
class User {
String login;
String name;
String password;
Set<Rating> ratings;
Set<User> friends;
Rating rate(Movie movie, int stars, String comment);
void befriend(User user);
}
class Rating {
User user;
Movie movie;
int stars;
String comment;
}
]]></programlisting>
</para><para>
I wrote some basic tests to assure that the basic plumbing worked. Check.
</para>
</section>
<section>
<title>Role</title>
<para>
...
</para>
</section>
<section>
<title>Actor</title>
<para>
...
</para>
</section>
<section>
<title>User</title>
<para>
...
</para>
</section>
<section>
<title>Rating</title>
<para>
...
</para>
</section>
</chapter>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<chapter id="tutorial:import">
<title>The dusty archives - Importing Data</title>
<para>
Now it was time to pull the data from themoviedb.org. Registering there and getting an API key was simple, using the API on the commandline with curl too.
Looking at the JSON returned for movies and people I decided to pimp my domain model and add some more fields so that the representation in the UI was worth
the effort.
</para><para>
For the import process I created a separate importer that used HttpClient and JSON to fetch and parse the data and then some transactional methods to actually
insert it as movies, roles and actors. User data was not available so I created an anonymous user called 'Cineast' that I attributed all the ratings and comments
to. I also created a version of the importer that read the json files from local disk, so that I didn't have to strain the remote API that much and that often.
</para>
<para>
<programlisting language="javascript" ><![CDATA[
JSON sample for movie & actor
]]></programlisting>
<programlisting language="java" ><![CDATA[
Code for importer & mapper
]]></programlisting>
</para>
</chapter>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<chapter id="tutorial:indexing">
<title>Do I know you? - Indexing</title>
<para>
Then there was an @Indexed annotation for fields. I wanted to try this too. That would guide the next test. I 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 I went with the
Finder to retrieve my indexed movie.
</para><para>
<programlisting language="java" ><![CDATA[
@NodeEntity
class Movie {
@Indexed
int id;
String title;
int year;
}
@Autowired FinderFactory finderFactory;
@Test public void persistedMovieShouldBeRetrievableFromGraphDb() {
int id=1;
Movie forestGump = new Movie(id, "Forest Gump", 1994).persist();
NodeFinder<Movie> movieFinder = finderFactory.createNodeEntityFinder(Movie.class);
Movie retrievedMovie = movieFinder.getByPropertyValue(id);
assertEqual("retrieved movie matches persisted one",forestGump,retrievedMovie);
assertEqual("retrieved movie title matches","Forest Gump",retrievedMovie.getTitle());
}
]]></programlisting>
</para><para>
TODO This failed with an exception about not being in a transaction. Oh, I forgot to add the @Transactional. So I added it to the test.
</para>
</chapter>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<chapter id="tutorial:neo4j">
<title>Graphs ahead - Learning Neo4j</title>
<para>
Then came the unknown - how to put these domain objects into the graph. First I read up about graph databases, especially Neo4j. Their datamodel consists
of nodes and relationships all of which can have properties. Relationships as first class citizens - I liked that. Then there was the possibility to index
both by field, value pairs to quickly get hold of them as starting points for further processing. Other useful operations were manual traversal of relationships
and a powerful traversal based on a query like Traversal Description. That all seemed pretty easy.
</para><para>
I also learned that Neo4j was transactional and provided the known ACID guarantees for my data. This was unsual for a NoSQL database but easier for me to get
my head around than non-transactional eventual persistence. That also meant that I had to manage transactions somehow. Keep that in mind.
</para><para>
<programlisting language="java" ><![CDATA[
enum RelationshipTypes implements RelationshipType { ACTS_IN };
GraphDatabaseService gds = new EmbeddedGraphDatabase("/path/to/store");
Node forest=gds.createNode();
forest.setProperty("title","Forest Gump");
forest.setProperty("year",1994);
gds.index().forNodes("movies").add(forest,"id",1);
Node tom=gds.createNode();
tom.setProperty("Tom Hanks");
Relationship role=tom.createRelationshipTo(forest,ACTS_IN);
role.setProperty("role","Forest Gump");
Node movie=gds.index().forNodes("movies").get("id",1).getSingle();
print(movie.getProperty("title"));
for (Relationship role : movie.getRelationships(ACTS_IN,INCOMING)) {
Node actor=role.getOtherNode(movie);
print(actor.getProperty("name") +" as " + role.getProperty("role"));
}
]]></programlisting>
</para>
</chapter>

View File

@@ -1,8 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<chapter id="tutorial:recommendations">
<title>Adding recommendations</title>
<title>Movies! Friends! Bargains! - Recommendations</title>
<para>
In the last part of this exercise I wanted to add some recommendation algorithms to my app. One was the recommendation of movies that my friends liked very much
(and their friends in descending importance). The second was recommendations for new friends that also liked the movies that I liked most.
</para><para>
Doing this kind of ranking algorithms is the real 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.
</para><para>
Lets say I'm only interested in the top 10 recommendations each.
</para><para>
<programlisting language="java" ><![CDATA[
// TODO Work In Progress 1/path.length()*stars
user.breathFirst().relationship(FRIEND, OUTGOING).relationship(RATED, OUTGOING).evaluate(new Evaluator(Path path) {
if (path.length > 5) return EXCLUDE_AND_STOP;
Relationship rating = path.lastRelationship();
if (rating.getType().equals(RATED)) {
rating.getProperty()
return INCLUDE_AND_STOP;
}
return INCLUDE_AND_CONTINUE;
})
]]></programlisting>
</para>
</chapter>

View File

@@ -0,0 +1,107 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<chapter id="tutorial:relationships">
<title>A convincing act - Relationships</title>
<section>
<title>Value in Relationships - Creating them</title>
<para>
Next were relationships. Direct relationships didn't require any annotation. Unfortunately I had none of those. So I 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 my Role looked like this:
</para>
<para>
<programlisting language="java"><![CDATA[
@RelationshipEntity
class Role {
@EndNode
Movie movie;
@StartNode
Actor actor;
String role;
}
]]></programlisting>
</para><para>
When writing a test for that I tried to create the relationship entity with new, but got an exception saying that this was not allowed. Some weird restriction
about having only correctly constructed RelationshipEntities. So I remembered a relateTo method from the list of introduced methods on the NodeEntities. After
quickly checking it turned out to be exactly what I needed. I added the method for connecting movies and actors to the actor - seemed more natural.
</para>
<para>
<programlisting language="java" ><![CDATA[
public Role playedIn(Movie movie, String roleName) {
Role role = relateTo(movie, Role.class, "ACTS_IN");
role.setRole(roleName);
return role;
}
]]></programlisting>
</para>
</section>
<section>
<title>Who's there ? - Accessing related entities</title>
<para>
What was left - accessing those relationships. I already had the appropriate fields in both classes. Time to annotate them correctly. For the fields providing
access to the entities on the other side of the relationship this was straightforward. Providing the target type again (thanks to Java's type erasure) and the
relationship type (that I learned from the Neo4j lesson before) there was only the direction left. Which defaults to OUTGOING so only for the movie I had to
specify it.
</para>
<para>
<programlisting language="java" ><![CDATA[
@NodeEntity
class Movie {
@Indexed
int id;
String title;
int year;
@RelatedTo(elementClass = Actor.class, type = "ACTS_IN", direction = Direction.INCOMING)
Set<Actor> cast;
}
@NodeEntity
class Actor {
@Indexed
int id;
String name;
@RelatedTo(elementClass = Movie.class, type = "ACTS_IN")
Set<Movie> cast;
public Role playedIn(Movie movie, String roleName) {
Role role = relateTo(movie, Role.class, "ACTS_IN");
role.setRole(roleName);
return role;
}
}
]]></programlisting>
</para>
</section>
<section>
<title>May I introduce ? - Accessing Relationships themselves</title>
<para>
While reading about those relationship-sets I learned that they are handled by managed collections of spring data graph. So whenever I add something to the
set or remove it, it automatically reflects that in the underlying relationships. Neat. But this also meant I mustn't initialize the fields. Something I will
certainly forget not to do in the future, so watch out for it.
</para><para>
I didn't forget to add test for those. So I could assure that the collections worked as advertised (and also ran into the intialization problem above).
</para><para>
But I 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 I had to declare the field as readonly Iterable&lt;Role&gt;. That should make sure that I 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 I went, creating my first real relationship (just kidding).
</para><para>
<programlisting language="java" ><![CDATA[
@NodeEntity
class Movie {
@Indexed
int id;
String title;
int year;
@RelatedTo(elementClass = Actor.class, type = "ACTS_IN", direction = Direction.INCOMING)
Set<Actor> cast;
@RelatedToVia(elementClass = Role.class, type = "ACTS_IN", direction = Direction.INCOMING)
Iterable<Roles> roles;
}
]]></programlisting>
</para><para>
After the tests proved that those relationship fields really mirrored the underlying relationships in the graph and instantly reflected additions and removals I was
satisfied with my domain so far and went for some coffee and chocolate.
</para>
</section>
</chapter>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<chapter id="tutorial:repository">
<title>Serving a good cause - Repository</title>
<para>
That was the first method to add to the repository. So I created a repository for my application, annotated
it with @Repository and @Transactional.
</para>
<para>
<programlisting language="java"><![CDATA[
@Repository @Transactional
public class CineastsRepostory {
FinderFactory finderFactory;
Finder<Movie> movieFinder;
@Autowired
public CineastsRepostory(FinderFactory finderFactory) {
this.finderFactory = finderFactory;
this.movieFinder = finderFactory.createNodeEntityFinder(Movie.class);
}
public Movie getMovie(int id) {
return movieFinder.getById(id);
}
}
]]></programlisting>
</para>
</chapter>

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<chapter id="tutorial:running">
<title>Curtains Up! - Get it running</title>
<section>
<title>Requisites - Populating the database</title>
<para>
Time to put this on display. But I needed some test data first. So I wrote a small class for populating the database which could be called from my controller.
To make it safe to call it several times I added index lookups to check for existing entries. A simple /populate endpoint for the controller that called it would
be enough for now.
</para>
<para>
<programlisting language="java"><![CDATA[
TODO code
]]></programlisting>
</para>
</section>
<section>
<title>Behind the scenes - Peeking at the Datastore</title>
<section>
<title>Eye candy - Neoclipse visualization</title>
<para>
After filling the database I wanted to see what the graph looked like. So I 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.
</para><para>
TODO neoclipse image
</para>
</section>
<section>
<title>Hardcore "Hacking" - Neo4j Shell</title>
<para>
Besides my movies and actors connected by ACTS_IN relationships there were some other nodes. The reference node which is kind of a 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.
</para><para>
For us 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.
</para><para>
<programlisting language="shell" ><![CDATA[
neo4j-shell -readonly -path /path/to/my/graphdb
]]></programlisting>
</para><para>
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.
</para><para>
<programlisting language="shell" ><![CDATA[
TODO shell session
]]></programlisting>
</para>
</section>
</section>
</chapter>

View File

@@ -1,8 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<chapter id="tutorial:security">
<title>Adding security</title>
<title>Protecting Assets - Adding Security</title>
<para>
To use the user in the webapp I 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.
</para><para>
I used Spring Security to that, writing a simple UserDetailsService that used my repository for looking up the users and validating their credentials. The config is located
in a separate applicationContext-security.xml.
</para>
<para>
<programlisting language="xml" ><![CDATA[
<security:global-method-security secured-annotations="enabled">
</security:global-method-security>
<security:http auto-config="true" access-denied-page="/auth/denied"> <!-- use-expressions="true" -->
<security:intercept-url pattern="/admin/*" access="ROLE_ADMIN"/>
<security:intercept-url pattern="/import/*" access="ROLE_ADMIN"/>
<security:intercept-url pattern="/user/*" access="ROLE_USER"/>
<security:intercept-url pattern="/auth/login" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<security:intercept-url pattern="/auth/register" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<security:intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<security:form-login login-page="/auth/login" authentication-failure-url="/auth/login?login_error=true"
default-target-url="/user"/>
<security:logout logout-url="/auth/logout" logout-success-url="/" invalidate-session="true"/>
</security:http>
<security:authentication-manager>
<security:authentication-provider user-service-ref="userDetailsService">
<security:password-encoder hash="md5">
<security:salt-source system-wide="cewuiqwzie"/>
</security:password-encoder>
</security:authentication-provider>
</security:authentication-manager>
<bean id="userDetailsService" class="org.neo4j.movies.service.CineastsUserDetailsService"/> ]]></programlisting>
<programlisting language="java" ><![CDATA[
@Service
public class CineastsUserDetailsService implements UserDetailsService, InitializingBean {
@Autowired private FinderFactory finderFactory;
private NodeFinder<User> userFinder;
@Override
public void afterPropertiesSet() throws Exception {
userFinder = finderFactory.createNodeEntityFinder(User.class);
}
@Override
public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException, DataAccessException {
final User user = findUser(login);
if (user==null) throw new UsernameNotFoundException("Username not found",login);
return new CineastsUserDetails(user);
}
public User findUser(String login) {
return userFinder.findByPropertyValue("users","login",login);
}
}
public class CineastsUserDetails implements UserDetails {
private final User user;
public CineastsUserDetails(User user) {
this.user = user;
}
@Override
public Collection<GrantedAuthority> getAuthorities() {
User.Roles[] roles = user.getRoles();
if (roles ==null) return Collections.emptyList();
return Arrays.<GrantedAuthority>asList(roles);
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getLogin();
}
....
public User getUser() {
return user;
}
}
]]></programlisting>
</para>
<para>
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.
</para>
</chapter>

View File

@@ -1,44 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<chapter id="tutorial:setup">
<title>Project setup</title>
<title>Scope: Spring</title>
<para>
...
Being a Spring Developer, I would, of course, choose components of the Spring Framework to do most of the work. I'd already come up with the ideas -
that should be enough.
</para><para>
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 I thought of? I had no idea.
</para><para>
But, wait, there was the new Spring Data project that started in 2010 bringing
the convenience of the Spring programming model to NoSQL databases. That should fit my experience and help me getting started. I looked
at the list of projects supporting the different NoSQL databases. Only one mentioned the kind of social network I was thinking of -
Spring Data Graph for Neo4j, a graph database. Neo4j's pitch of "value in relationships" and the accompanying docs looked like what I needed.
I decided to give it a try.
</para>
<section>
<title>Spring Data Graph</title>
<title>Preparations - Required Setup</title>
<para>
...
To setup the project I created a public github account and began setting up the infrastructure for a spring web project using maven as build
system. So I added the dependencies for the springframework libraries, put the web.xml for the DispatcherServlet and the applicationContext.xml
in the webapp directory.
</para><para>
<programlisting language="xml" ><![CDATA[
TODO setup code?
]]></programlisting>
</para><para>
With this setup I was ready for the first spike: creating a simple MovieController showing a static view. Check. Next was the setup for Spring Data Graph.
I 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.
I added just a few lines to my spring configuration.
</para><para>
<programlisting language="xml" ><![CDATA[
TODO config code?
]]></programlisting>
</para><para>
I spun up jetty to see if there were any obvious issues with the config. Check.
</para>
<section>
<title>Maven configuration</title>
<para>
...
</para>
</section>
<section>
<title>Spring configuration</title>
<para>
...
</para>
</section>
</section>
<section>
<title>Spring MVC</title>
<para>
...
</para>
<section>
<title>Maven configuration</title>
<para>
...
</para>
</section>
<section>
<title>Spring configuration</title>
<para>
...
</para>
</section>
</section>
</chapter>

View File

@@ -1,8 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<chapter id="tutorial:social">
<title>Adding social</title>
<title>Movies 2.0 - Adding social</title>
<para>
...
But this was just a plain old movie database (POMD). My idea of socializing this business was not realized.
</para>
<section>
<title>See, mom a Cineast! - Users</title>
<para>
So I took the User class that I already coded up before and made it a full fledged Spring Data Graph member.
</para>
<para>
<programlisting language="java" ><![CDATA[
@NodeEntity
class User {
@Indexed
String login;
String name;
String password;
@RelatedTo(elementClass=Movie.class, type="RATED")
Set<Rating> ratings;
@RelatedTo(elementClass=User.class, type="FRIEND")
Set<User> friends;
public Rating rate(Movie movie, int stars, String comment) {
return relateTo(movie, Rating.class, "RATED").rate(stars, comment);
}
public void befriend(User user) {
this.friends.add(user);
}
}
class Rating {
@StartNode User user;
@EndNode Movie movie;
int stars;
String comment;
public Rating rate(int stars, String comment) {
this.stars=stars; this.comment = comment;
return this;
}
}
]]></programlisting>
</para>
</section>
<section>
<title>Beware, Critics - Rating</title>
<para>
I also put a ratings field into the movie to be able to show its ratings. And a method to average the stars it got.
</para>
<para>
<programlisting language="java" ><![CDATA[
class Movie {
@RelatedToVia(elementClass=Rating.class, type="RATED", direction = Direction.INCOMING)
Iterable<Rating> ratings;
public int getStars() {
int stars, int count;
for (Rating rating : ratings) {
stars += rating.getStars(); count++;
}
return count == 0 ? 0 : stars / count;
}
}
]]></programlisting>
</para>
<para>
Fortunately my tests showed my the division by zero error when calculating the stars for a movie without ratings. I also added a few user and ratings to the
database population code. And three methods to rate movies, lookup users and add friends to the repository.
</para>
<para>
<programlisting language="java" ><![CDATA[
TODO code
]]></programlisting>
</para>
</section>
</chapter>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<chapter id="tutorial:about-spring-data">
<title>Conjuring Magic - Spring Data Graph</title>
<para>
But that was the pure graph database. Using this in my domain would pollute my classes with lots of graph database details. I didn't want that. Spring Data Graph
promised to do the heavy lifting for me. So I checked that next. Obviously it heavily depended on aspectj magic. So there would be certain behavour that was
just observable without being visible in my code. But I was going to give it a try.
</para>
</chapter>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<chapter id="tutorial:user-experience">
<title>Oh the Glamour - More UI</title>
<para>
TODO screenshots
</para>
</chapter>

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<chapter id="tutorial:webapp">
<title>Showing off - Web views</title>
<para>
After I had the means to put some data in the graph database, I 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á.
</para><para>
TODO screenshot of movie display
</para>
<section>
<title>What was his name? - Searching</title>
<para>
The next thing was to allow users to search for some movies. So I needed some fulltext-search capabilities. As the index provider implementation of Neo4j builds on
lucene I was delighted to see that fulltext indexes are supported out of the box.
</para>
<para>
So I happily annotated the title field of my Movie class with @Index(fulltext=true) and was told with an exception that I have to specify a separate index name for that.
So it became @Indexed(fulltext = true, indexName = "search"). The corresponding finder method is called findAllByQuery. So there was my second repository method for
searching movies. To restrict the size of the returned set I just added a limit for now that cuts the result after that many entries.
</para>
<para>
<programlisting language="java"><![CDATA[
public void List<Movie> searchForMovie(String query, int count) {
List<Movie> movies=new ArrayList<Movie>(count);
for (Movie movie : movieFinder.findAllByQuery("title", query)) {
movies.add(movie);
if (count-- == 0) break;
}
return movies;
}
]]></programlisting>
</para>
</section>
<section>
<title>Look what i've found - Listing Results</title>
<para>
I 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.
</para>
<para>
<programlisting language="jsp" ><![CDATA[
TODO jsp fragment
]]></programlisting>
</para>
</section>
</chapter>