updated documentation and tutorial

This commit is contained in:
Michael Hunger
2011-03-17 15:02:59 +01:00
parent 1411260622
commit 7f22307545
23 changed files with 932 additions and 303 deletions

186
pom.xml
View File

@@ -150,80 +150,126 @@
</extension>
</extensions>
<plugins>
<plugin>
<groupId>com.agilejava.docbkx</groupId>
<artifactId>docbkx-maven-plugin</artifactId>
<version>2.0.7</version>
<executions>
<execution>
<goals>
<goal>generate-html</goal>
<goal>generate-pdf</goal>
</goals>
<phase>pre-site</phase>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.docbook</groupId>
<artifactId>docbook-xml</artifactId>
<version>4.4</version>
<scope>runtime</scope>
</dependency>
<!--dependency>
<plugin>
<groupId>com.agilejava.docbkx</groupId>
<artifactId>docbkx-maven-plugin</artifactId>
<version>2.0.8</version>
<executions>
<execution>
<id>single-page</id>
<goals>
<goal>generate-html</goal>
</goals>
<configuration>
<keepRelativeImageUris>true</keepRelativeImageUris>
<htmlCustomization>${basedir}/src/docbkx/resources/xsl/html.xsl</htmlCustomization>
<postProcess>
<move tofile="${basedir}/target/site/reference/html/index.html"
file="${basedir}/target/docbkx/html/index.html"/>
<copy todir="${basedir}/target/site/reference/html" flatten="true">
<fileset dir="${basedir}/src/docbkx/">
<exclude name="${basedir}/src/docbkx/resources/*"/>
<include name="**/*.png"/>
<include name="**/*.gif"/>
<include name="**/*.jpg"/>
</fileset>
</copy>
<copy todir="${basedir}/target/site/reference/html">
<fileset dir="${basedir}/src/docbkx/resources">
<include name="**/*.css"/>
<include name="**/*.png"/>
<include name="**/*.gif"/>
<include name="**/*.jpg"/>
</fileset>
</copy>
</postProcess>
</configuration>
<phase>pre-site</phase>
</execution>
<execution>
<id>single-pdf</id>
<goals>
<goal>generate-pdf</goal>
</goals>
<configuration>
<imgSrcPath>${basedir}/src/docbkx/</imgSrcPath>
<admonGraphicsPath>${basedir}/src/docbkx/resources/images/</admonGraphicsPath>
<postProcess>
<copy file="${basedir}/target/docbkx/pdf/index.pdf"
tofile="${basedir}/target/site/reference/pdf/spring-data-graph.pdf"
failonerror="false"/>
</postProcess>
</configuration>
<phase>pre-site</phase>
</execution>
<execution>
<id>multi-page</id>
<goals>
<goal>generate-html</goal>
</goals>
<configuration>
<keepRelativeImageUris>true</keepRelativeImageUris>
<chunkedOutput>true</chunkedOutput>
<htmlCustomization>${basedir}/src/docbkx/resources/xsl/html_chunk.xsl</htmlCustomization>
<targetDirectory>${basedir}/target/docbkx/multi/</targetDirectory>
<postProcess>
<move todir="${basedir}/target/site/reference/multi">
<fileset dir="${basedir}/target/docbkx/multi">
<include name="**/*.html"/>
</fileset>
</move>
<copy todir="${basedir}/target/site/reference/multi">
<fileset dir="${basedir}/src/docbkx/resources">
<include name="**/*.png"/>
<include name="**/*.gif"/>
<include name="**/*.jpg"/>
</fileset>
</copy>
<copy todir="${basedir}/target/site/reference/multi" flatten="true">
<fileset dir="${basedir}/src/docbkx">
<exclude name="${basedir}/src/docbkx/resources/*"/>
<include name="**/*.css"/>
<include name="**/*.png"/>
<include name="**/*.gif"/>
<include name="**/*.jpg"/>
</fileset>
</copy>
</postProcess>
</configuration>
<phase>pre-site</phase>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.docbook</groupId>
<artifactId>docbook-xml</artifactId>
<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>
<version>1.1.3</version>
<scope>compile</scope>
</dependency>
</dependencies>
<configuration>
<includes>index.xml</includes>
<xincludeSupported>true</xincludeSupported>
<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>${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>
<!-- produce multi-page html output
<chunkedOutput>false</chunkedOutput>
<htmlCustomization>${project.basedir}/src/docbkx/resources/xsl/html_chunk.xsl</htmlCustomization> -->
<entities>
<entity>
<name>version</name>
<value>${pom.version}</value>
</entity>
</entities>
<postProcess>
<copy todir="${project.basedir}/target/site/reference">
<fileset dir="${project.basedir}/target/docbkx">
<include name="**/*.html" />
<include name="**/*.pdf" />
</fileset>
</copy>
<copy todir="${project.basedir}/target/site/reference/html">
<fileset dir="${project.basedir}/src/docbkx/resources">
<include name="**/*.css" />
<include name="**/*.png" />
<include name="**/*.gif" />
<include name="**/*.jpg" />
</fileset>
</copy>
<move file="${project.basedir}/target/site/reference/pdf/index.pdf"
tofile="${project.basedir}/target/site/reference/pdf/spring-data-graph-reference.pdf"
failonerror="false" />
</postProcess>
</configuration>
</plugin>
</dependencies>
<configuration>
<includes>index.xml</includes>
<chunkedOutput>false</chunkedOutput>
<draftWatermarkImage/>
<htmlStylesheet>css/html.css</htmlStylesheet>
<sourceDirectory>${basedir}/src/docbkx/</sourceDirectory>
<foCustomization>${basedir}/src/docbkx/resources/xsl/fopdf.xsl</foCustomization>
<xincludeSupported>true</xincludeSupported>
<entities>
<entity>
<name>version</name>
<value>${project.version}</value>
</entity>
</entities>
</configuration>
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.5</version>

View File

@@ -34,8 +34,8 @@
<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/security.xml"/>
<xi:include href="tutorial/user-experience.xml"/>
<xi:include href="tutorial/import.xml"/>
<xi:include href="tutorial/recommendations.xml"/>

View File

@@ -27,8 +27,11 @@
</author>
</authorgroup>
<graphic fileref="springdatagraph.png" scalefit="true"/>
<mediaobject>
<imageobject>
<imagedata fileref="springdatagraph.png" scalefit="1" contentwidth="15cm"/>
</imageobject>
</mediaobject>
<legalnotice>
<para>
Copies of this document may be made for your own use and for distribution

View File

@@ -2,22 +2,24 @@
<!DOCTYPE preface PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<!--preface id="foreword_rod">
<title>Foreword: Rod Johnson, CEO of SpringSource</title>
<para>
Rod ...
</para>
</preface>
<preface id="foreword_emil">
<title>Foreword: Emil Eifrem, CEO of Neo Technology</title>
<para>
Emil ...
</para>
</preface-->
<preface id="about-book">
<title>Preface</title>
<para>
<mediaobject>
<imageobject>
<imagedata fileref="springdatagraph.png" contentwidth="15cm" scalefit="1"/>
</imageobject>
</mediaobject>
</para>
<section>
<title>Foreword: Rod Johnson, CEO of SpringSource</title>
<para></para>
</section>
<section>
<title>Foreword: Emil Eifrem, CEO of Neo Technology</title>
<para></para>
</section>
<section>
<title>About this Guide Book</title>
<para>
Welcome to the Spring Data Graph Guide Book. Thank you for taking the time to get an in depth look into the
@@ -46,4 +48,5 @@
for the object-graph mapping and the template approach. The reference docs should be mainly used to look up
concrete bits of information or to dig deeper into certain topics.
</para>
</section>
</preface>

View File

@@ -14,12 +14,12 @@
<title>Partial graph persistence</title>
<para>
Partial graph persistence is achieved by restricting the Spring Data Graph aspects to explicitly annotated parts of
the entity. Those fields have to be made transient so that JPA ignores them and won't try to persist those
the entity. Those fields will be made transient by the aspect so that JPA ignores them and won't try to persist those
attributes.
</para>
<para>
A backing node in the graph store is created when the entity has been assigned a JPA id. Only then will the connection between the
two stores be kept. Until the entity has been persisted, its state is just kept inside the POJO and flushed to the
A backing node in the graph store is only created when the entity has been assigned a JPA id. Only then will the connection between the
two stores be kept. Until the entity has been persisted, its state is just kept inside the POJO (detached state) and flushed to the
backing graph store afterwards.
</para>
<para>
@@ -66,15 +66,12 @@
private String lastName;
@GraphProperty
@Transient
String nickname;
@RelatedTo(type = "friends", elementClass = UserAccount.class)
@Transient
Set<UserAccount> friends;
@RelatedToVia(type = "recommends", elementClass = Recommendation.class)
@Transient
Iterable<Recommendation> recommendations;
@Temporal(TemporalType.TIMESTAMP)
@@ -91,13 +88,12 @@
@Transactional
public void knows(UserAccount friend) {
relateTo(friend, DynamicRelationshipType.withName("friends"));
relateTo(friend, "friends");
}
@Transactional
public Recommendation rate(Restaurant restaurant, int stars, String comment) {
Recommendation recommendation = (Recommendation) relateTo(restaurant,
Recommendation.class, "recommends");
Recommendation recommendation = relateTo(restaurant, Recommendation.class, "recommends");
recommendation.rate(stars, comment);
return recommendation;
}
@@ -130,8 +126,8 @@
<context:annotation-config/>
<datagraph:config storeDirectory="target/config-test"
entityManagerFactory="entityManagerFactory"/>
<datagraph:config storeDirectory="target/config-test"
entityManagerFactory="entityManagerFactory"/>
<bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
id="entityManagerFactory">

View File

@@ -7,6 +7,14 @@
entities that make common tasks easier. Unfortunately these methods are not generified yet, so the
results have to be casted to the correct return type.
<variablelist>
<varlistentry>
<term>persisting the node-entity initially and after changes outside of a transaction,
persist participates in a transaction or creates its own implict transaction.
</term>
<listitem>
<para><code>nodeEntity.persist()</code></para>
</listitem>
</varlistentry>
<varlistentry>
<term>accessing node and relationship ids</term>
<listitem>
@@ -26,17 +34,29 @@
</listitem>
</varlistentry>
<varlistentry>
<term>creating relationships to a target node entity</term>
<term>creating relationships to a target node entity and returning the relationship-entity instance</term>
<listitem>
<para><code>nodeEntity.relateTo(targetEntity, relationshipClass, relationshipType)</code></para>
</listitem>
</varlistentry>
<varlistentry>
<term>retrieving a single relationship</term>
<term>retrieving a single relationship-entity</term>
<listitem>
<para><code>nodeEntity.getRelationshipTo(targetEnttiy, relationshipClass, relationshipType)</code></para>
</listitem>
</varlistentry>
<varlistentry>
<term>creating relationships to a target node entity and returning the relationship</term>
<listitem>
<para><code>nodeEntity.relateTo(targetEntity, relationshipType)</code></para>
</listitem>
</varlistentry>
<varlistentry>
<term>retrieving a single relationship</term>
<listitem>
<para><code>nodeEntity.getRelationshipTo(targetEnttiy, relationshipType)</code></para>
</listitem>
</varlistentry>
<varlistentry>
<term>removing a single relationship</term>
<listitem>

View File

@@ -61,7 +61,7 @@
<author space-before="0.5em"
font-size="&hsize2;"/> -->
<mediaobject space-before="2em" space-after="2em"/>
<mediaobject space-before="2em" space-after="2em"/>
<releaseinfo space-before="5em" font-size="&hsize2;"/>
<copyright space-before="1.5em"
font-weight="normal"

View File

@@ -1,5 +1,5 @@
<?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">
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.docbook.org/xml/4.4/docbookx.dtd">
<chapter id="tutorial_about-tutorial">
<title>Allow me to introduce - Cineasts.net</title>
<para>
@@ -13,7 +13,7 @@ existing friends.
</para><para>
<mediaobject>
<imageobject>
<imagedata fileref="cineasts.png" scalefit="true" align="center"/>
<imagedata fileref="cineasts.png" scale="80" align="center"/>
</imageobject>
</mediaobject>
</para><para>I looked for possible sources for data, IMDB was my first stop, but they charge 15k USD for data usage. Fortunately I found <ulink url="http://themoviedb.org">TheMoviedb.org</ulink> which has
@@ -24,7 +24,7 @@ There were many more ideas but I wanted to get something done over the course of
</para>
<mediaobject>
<imageobject>
<imagedata fileref="cineasts_main.png" scalefit="true" align="center"/>
<imagedata fileref="cineasts_main.png" scale="80" align="center"/>
</imageobject>
</mediaobject>

View File

@@ -1,13 +1,14 @@
<?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">
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.docbook.org/xml/4.4/docbookx.dtd">
<chapter id="tutorial_annotations">
<title>Decorations - Annotated Domain</title>
<para>
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.
I looked at the documentation again, found a simple <ulink url="http://github.com/SpringSource/spring-data-graph-examples/tree/master/hello-worlds">Hello-World example</ulink> and tried to understand it.
The entities were annotated with @NodeEntity, that was simple, so I added the annotation to my domain classes 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.
GraphDatabaseContext injected and use its getById() method. The other one was a Finder approach which I ignored for now. Let's 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>
@@ -15,14 +16,14 @@
</para><para>
<programlisting language="java" ><![CDATA[
@Autowired GraphDatabaseContext graphDatabaseContext;
@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());
}
@Test public void persistedMovieShouldBeRetrievableFromGraphDb() {
Movie forrestGump = new Movie("Forrest Gump", 1994).persist();
Movie retrievedMovie = graphDatabaseContext.getById(forrestGump.getNodeId());
assertEqual("retrieved movie matches persisted one",forrestGump,retrievedMovie);
assertEqual("retrieved movie title matches","Forrest Gump",retrievedMovie.getTitle());
}
]]></programlisting>
</para><para>

View File

@@ -1,5 +1,5 @@
<?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">
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.docbook.org/xml/4.4/docbookx.dtd">
<chapter id="tutorial_domain">
<title>Setting the Stage - Movies Domain</title>
<para>
@@ -21,40 +21,40 @@
</para><para>
<programlisting language="java" ><![CDATA[
class Movie {
int id;
String title;
int year;
Set<Role> cast;
}
<programlisting lang="java" language="java" role="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;
}
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>

View File

@@ -1,23 +1,135 @@
<?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">
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.docbook.org/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.
Now it was time to pull the data from <ulink url="http://themoviedb.org">themoviedb.org</ulink>. 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.
<programlisting language="javascript" ><![CDATA[
[{"popularity":3,
"translated":true, "adult":false, "language":"en",
"original_name":"[Rec]", "name":"[Rec]", "alternative_name":"[REC]",
"movie_type":"movie",
"id":8329, "imdb_id":"tt1038988", "url":"http://www.themoviedb.org/movie/8329",
"votes":11, "rating":7.2,
"status":"Released",
"tagline":"One Witness. One Camera",
"certification":"R",
"overview":"\"REC\" turns on a young TV reporter and her cameraman who cover the night shift at the local fire station...
"keywords":["terror", "lebende leichen", "obsession", "camcorder", "firemen", "reality tv ", "bite", "cinematographer",
"attempt to escape", "virus", "lodger", "live-reportage", "schwerverletzt"],
"released":"2007-08-29",
"runtime":78,
"budget":0,
"revenue":0,
"homepage":"http://www.3l-filmverleih.de/rec",
"trailer":"http://www.youtube.com/watch?v=YQUkX_XowqI",
"genres":[{"type":"genre",
"url":"http://themoviedb.org/genre/horror",
"name":"Horror",
"id":27}],
"studios":[{"url":"http://www.themoviedb.org/company/2270", "name":"Filmax Group", "id":2270}],
"languages_spoken":[{"code":"es", "name":"Spanish", "native_name":"Espa\u00f1ol"}],
"countries":[{"code":"ES", "name":"Spain", "url":"http://www.themoviedb.org/country/es"}],
"posters":[{"image":{"type":"poster",
"size":"original", "height":1000, "width":706,
"url":"http://cf1.imgobject.com/posters/3a0/4cc8df415e73d650240003a0/rec-original.jpg", "id":"4cc8df415e73d650240003a0"}},
....
"cast":[{"name":"Manuela Velasco",
"job":"Actor", "department":"Actors",
"character":"Angela Vidal",
"id":34793, "order":0, "cast_id":1,
"url":"http://www.themoviedb.org/person/34793",
"profile":"http://cf1.imgobject.com/profiles/390/4c0157fa017a3c702d001390/manuela-velasco-thumb.jpg"},
...
{"name":"Gl\u00f2ria Viguer",
"job":"Costume Design", "department":"Costume \u0026 Make-Up",
"character":"",
"id":54531, "order":0, "cast_id":21,
"url":"http://www.themoviedb.org/person/54531",
"profile":""}],
"version":150, "last_modified_at":"2011-02-20 23:16:57"}]
]]></programlisting>
<programlisting language="javascript" ><![CDATA[
[{"popularity":3,
"name":"Glenn Strange", "known_as":[{"name":"George Glenn Strange"}, {"name":"Glen Strange"},
{"name":"Glen 'Peewee' Strange"}, {"name":"Peewee Strange"}, {"name":"'Peewee' Strange"}],
"id":30112,
"biography":"",
"known_movies":4,
"birthday":"1899-08-16", "birthplace":"Weed, New Mexico, USA",
"url":"http://www.themoviedb.org/person/30112",
"filmography":[{"name":"Bud Abbott Lou Costello Meet Frankenstein",
"id":3073,
"job":"Actor", "department":"Actors",
"character":"The Frankenstein Monster",
"cast_id":23,
"url":"http://www.themoviedb.org/movie/3073",
"poster":"http://cf1.imgobject.com/posters/4ca/4bc9185d017a3c57fe0094ca/bud-abbott-lou-costello-meet-frankenstein-cover.jpg",
"adult":false, "release":"1948-06-15"},
...],
"profile":[],
"version":19, "last_modified_at":"2011-03-07 13:02:35"}]
]]></programlisting>
</para><para>
For the import process I created a separate importer that used Jackson to fetch and parse the data and then some transactional methods in the MovieDbImportService 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.-->
The importer used a simple caching mechanism, to keep downloaded actor and movie data on the filesystem, so that I didn't have to strain the remote API that much and that often.
In the code below you can see, that I changed the actor to a person so that I can also accomodate for all the other
people that contribute in movie making.
</para>
<para>
<programlisting language="javascript" ><![CDATA[
JSON sample for movie & actor
]]></programlisting>
<programlisting language="java" ><![CDATA[
Code for importer & mapper
@Transactional
public Movie importMovie(String movieId) {
Movie movie = moviesRepository.getMovie(movieId);
if (movie == null) { // Not found: Create fresh
movie = new Movie(movieId,null);
}
Map data = loadMovieData(movieId);
if (data.containsKey("not_found")) throw new RuntimeException("Data for Movie "+movieId+" not found.");
movieDbJsonMapper.mapToMovie(data, movie);
movie.persist();
relatePersonsToMovie(movie, data);
return movie;
}
private void relatePersonsToMovie(Movie movie, Map data) {
Collection<Map> cast = (Collection<Map>) data.get("cast");
for (Map entry : cast) {
String id = entry.get("id");
Roles job = entry.get("job");
Person person = importPerson(id);
switch (job) {
case DIRECTED:
person.directed(movie);
break;
case ACTS_IN:
person.playedIn(movie, (String) entry.get("character"));
break;
}
}
}
public void mapToMovie(Map data, Movie movie) {
movie.setTitle((String) data.get("name"));
movie.setLanguage((String) data.get("language"));
movie.setTagline((String) data.get("tagline"));
movie.setReleaseDate(toDate(data, "released", "yyyy-MM-dd"));
...
movie.setImageUrl(selectImageUrl((List<Map>) data.get("posters"), "poster", "mid"));
}
]]></programlisting>
</para>
<para>
The last part was adding some protected URI to the MovieController to allow to import ranges of movies.
During testing it became obvious that the calls to themoviedb where the limiting factor. As soon as
the data was stored locally it took only subseconds to create the data in the Neo4j graph database.
</para>
</chapter>

View File

@@ -1,5 +1,5 @@
<?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">
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.docbook.org/xml/4.4/docbookx.dtd">
<chapter id="tutorial_indexing">
<title>Do I know you? - Indexing</title>
<para>
@@ -10,24 +10,25 @@
</para><para>
<programlisting language="java" ><![CDATA[
@NodeEntity
class Movie {
@Indexed
int id;
String title;
int year;
}
@NodeEntity
class Movie {
@Indexed
int id;
String title;
int year;
}
@Autowired FinderFactory finderFactory;
@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());
}
@Test [@Transactional] public void persistedMovieShouldBeRetrievableFromGraphDb() {
int id=1;
Movie forrestGump = new Movie(id, "Forrest Gump", 1994).persist();
NodeFinder<Movie> movieFinder = finderFactory.createNodeEntityFinder(Movie.class);
// REMINDER, the "null" stands for an optional index name
Movie retrievedMovie = movieFinder.findByPropertyValue(null, "id",id);
assertEqual("retrieved movie matches persisted one",forrestGump,retrievedMovie);
assertEqual("retrieved movie title matches","Forrest Gump",retrievedMovie.getTitle());
}
]]></programlisting>

View File

@@ -1,37 +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">
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.docbook.org/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.
Then came the unknown - how to put these domain objects into the graph. First I read up about graph databases, especially <ulink url="http://neo4j.org">Neo4j</ulink>.
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 <ulink url="http://wiki.neo4j.org/content/Index_Framework">index nodes and relationships</ulink> by field, value pairs to quickly get hold of them.
Those could then be the starting points for further processing. Other useful operations were manual traversal of relationships
and a powerful traversal based on a query-like <ulink url="http://wiki.neo4j.org/content/Traversal_Framework">Traversal Description</ulink>. 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"));
}
So I tried to use the raw Neo4j-API so get a feeling for that. And also see, how (probably) the domain would end up in the graph store. After adding the maven
dependency, it was ready to go.
</para><para>
<programlisting language="xnk" ><![CDATA[
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j</artifactId>
<version>1.3.M04</version>
</dependency>
]]></programlisting>
</para><para>
<programlisting language="java" ><![CDATA[
enum RelationshipTypes implements RelationshipType { ACTS_IN };
GraphDatabaseService gds = new EmbeddedGraphDatabase("/path/to/store");
Node forrest=gds.createNode();
forrest.setProperty("title","Forrest Gump");
forrest.setProperty("year",1994);
gds.index().forNodes("movies").add(forrest,"id",1);
Node tom=gds.createNode();
tom.setProperty("Tom Hanks");
Relationship role=tom.createRelationshipTo(forrest,ACTS_IN);
role.setProperty("role","Forrest 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,5 +1,5 @@
<?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">
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.docbook.org/xml/4.4/docbookx.dtd">
<chapter id="tutorial_recommendations">
<title>Movies! Friends! Bargains! - Recommendations</title>
<para>

View File

@@ -1,22 +1,20 @@
<?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">
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.docbook.org/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:
Next were relationships. Direct relationships didn't require any annotation. Unfortunately I had none of those, because mine had more semantics.
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;
@StartNode Actor actor;
@EndNode Movie movie;
String role;
}
]]></programlisting>
@@ -27,11 +25,13 @@ quickly checking it turned out to be exactly what I needed. I added the method f
</para>
<para>
<programlisting language="java" ><![CDATA[
class Actor {
...
public Role playedIn(Movie movie, String roleName) {
Role role = relateTo(movie, Role.class, "ACTS_IN");
role.setRole(roleName);
return role;
}
}}
]]></programlisting>
</para>
</section>
@@ -39,7 +39,7 @@ public Role playedIn(Movie movie, String roleName) {
<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
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 (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>
@@ -71,33 +71,35 @@ class Actor {
}
]]></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>
</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>
</para>
</section>
<section>
<title>May I introduce ? - Accessing Relationships themselves</title>
<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;
@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;
}
@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

View File

@@ -1,25 +1,25 @@
<?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">
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.docbook.org/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.
That was the first method to add to the brand new repository. So I created a repository for my application, annotated
it with @Repository and @Transactional. I did the same for the Actor.
</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);
}
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.findByPropertyValue(null,"id", id);
}
}
]]></programlisting>
</para>

View File

@@ -1,5 +1,5 @@
<?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">
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.docbook.org/xml/4.4/docbookx.dtd">
<chapter id="tutorial_running">
<title>Curtains Up! - Get it running</title>
<section>
@@ -11,8 +11,67 @@
</para>
<para>
<programlisting language="java"><![CDATA[
TODO code
]]></programlisting>
@Service
public class DatabasePopulator {
@Autowired GraphDatabaseContext ctx;
@Autowired MoviesRepository repository;
@Transactional
public List<Movie> populateDatabase() {
Actor tomHanks = new Actor("1", "Tom Hanks").persist();
Movie forestGump = new Movie("1", "Forrest Gump").persist();
forestGump.setYear(1994);
tomHanks.playedIn(forestGump,"Forrest");
return asList(forestGump);
}}
@Controller
public class MovieController {
private DatabasePopulator populator;
@Autowired
public MovieController(DatabasePopulator populator) {
this.populator = populator;
}
@RequestMapping(value = "/populate", method = RequestMethod.GET)
public String populateDatabase(Model model) {
Collection<Movie> movies=populator.populateDatabase();
model.addAttribute("movies",movies);
return "/movies/list";
}
}
]]></programlisting>
<programlisting language="jsp"><![CDATA[
<%@ page session="false" %>
<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:choose>
<c:when test="${not empty movie}">
<h2>${movie.title}</h2>
<c:if test="${not empty movie.roles}">
<ul>
<c:forEach items="${movie.roles}" var="role">
<li>
<a href="/actors/${role.actor.id}"><c:out value="${role.actor.name}" /> as <c:out value="${role.name}" /></a><br/>
</li>
</c:forEach>
</ul>
</c:if>
</c:when>
<c:otherwise>
No Movie with id ${id} found!
</c:otherwise>
</c:choose>
]]></programlisting>
</para><para>
See the misused GET parameter for that (don't do this at home). This is only for running it from the browser
address line. Better use POST and curl for the call. So I called the URI and it showed the single added movie
on screen.
</para>
</section>
<section>
@@ -25,7 +84,12 @@
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
<mediaobject>
<imageobject>
<imagedata fileref="neoclipse.png" scalefit="1" contentwidth="15cm" align="center"/>
</imageobject>
</mediaobject>
</para>
</section>
<section>
@@ -40,14 +104,50 @@
</para><para>
<programlisting language="shell" ><![CDATA[
neo4j-shell -readonly -path /path/to/my/graphdb
neo4j-shell -readonly -path data/graph.db
]]></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
neo4j-sh[readonly] (0)$ ls
(me) --[SUBREF_java.lang.Object]-> (3)
(me) --[SUBREF_org.neo4j.cineasts.domain.Movie]-> (6)
(me) --[SUBREF_org.neo4j.cineasts.domain.Person]-> (8)
(me) --[SUBREF_org.neo4j.cineasts.domain.User]-> (2)
neo4j-sh[readonly] (0)$ cd 6
neo4j-sh[readonly] (6)$ ls
*class =[org.neo4j.cineasts.domain.Movie]
*count =[39]
(me) <-[INSTANCE_OF]-- (The Matrix Revolutions,123)
(me) <-[INSTANCE_OF]-- (The Matrix Reloaded,110)
(me) <-[INSTANCE_OF]-- (The Matrix,93)
...
neo4j-sh[readonly] (6)$ cd 93
neo4j-sh[readonly] (The Matrix,93)$ ls
*description =[Neo is a young software engineer and part-time hacker who is singled out by some mysterious...]
*genre =[Action]
*homepage =[http://whatisthematrix.warnerbros.com/]
*id =[603]
*imageUrl =[http://cf1.imgobject.com/posters/606/4bc909d0017a3c57fe003606/the-matrix-mid.jpg]
*imdbId =[tt0133093]
*language =[en]
*lastModified =[1299968642000]
*releaseDate =[922831200000]
*runtime =[136]
*studio =[Warner Bros. Pictures]
*tagline =[Welcome to the Real World.]
*title =[The Matrix]
*trailer =[http://www.youtube.com/watch?v=UM5yepZ21pI]
*version =[324]
(me) <-[ACTS_IN]-- (Marc Aden,109)
...
(me) <-[ACTS_IN]-- (Keanu Reeves,96)
(me) <-[DIRECTED]-- (Andy Wachowski,95)
(me) <-[DIRECTED]-- (Lana Wachowski,94)
(me) --[INSTANCE_OF]-> (6)
(me) <-[RATED]-- (Micha,1)
]]></programlisting>
</para>
</section>

View File

@@ -1,40 +1,91 @@
<?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">
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.docbook.org/xml/4.4/docbookx.dtd">
<chapter id="tutorial_security">
<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
To have a 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.
I used Spring Security to 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.
</para>
<para>
<example>
<title>pom.xml for spring-security</title>
<programlisting language="xml" ><![CDATA[
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${spring.version}</version>
</dependency>
]]></programlisting>
</example>
</para>
<para>
<example>
<title>web.xml</title>
<programlisting language="xml" ><![CDATA[
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/applicatioContext-security.xml
/WEB-INF/applicationContext.xml
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
]]></programlisting>
</example>
</para>
<para>
<example>
<title> applicationContext-security.xml</title>
<programlisting language="xml" ><![CDATA[
<security:global-method-security secured-annotations="enabled">
</security:global-method-security>
</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: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>
<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>
<bean id="userDetailsService" class="org.neo4j.movies.service.CineastsUserDetailsService"/>
]]></programlisting>
</example>
<example>
<title>UserDetailsService and UserDetails implementation</title>
<programlisting language="java" ><![CDATA[
@Service
public class CineastsUserDetailsService implements UserDetailsService, InitializingBean {
@@ -57,6 +108,16 @@ public class CineastsUserDetailsService implements UserDetailsService, Initializ
public User findUser(String login) {
return userFinder.findByPropertyValue("users","login",login);
}
public User getUserFromSession() {
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
Object principal = authentication.getPrincipal();
if (principal instanceof CineastsUserDetails) {
CineastsUserDetails userDetails = (CineastsUserDetails) principal;
return userDetails.getUser();
}
return null;
}
}
public class CineastsUserDetails implements UserDetails {
@@ -90,9 +151,16 @@ public class CineastsUserDetails implements UserDetails {
}
]]></programlisting>
</example>
</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.
and JSPs for the views. I used the helper method <code>getUserFromSession()</code> in the controllers to access the logged in user and put it in the model for rendering.
As a teaser I'd like to show off the user profile page, as it will be rendered after heavy UX lifting.
</para>
<mediaobject>
<imageobject>
<imagedata fileref="cineasts_user.png" scalefit="1" contentwidth="15cm"/>
</imageobject>
</mediaobject>
</chapter>

View File

@@ -1,5 +1,5 @@
<?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">
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.docbook.org/xml/4.4/docbookx.dtd">
<chapter id="tutorial_setup">
<title>Scope: Spring</title>
<para>
@@ -23,16 +23,64 @@
in the webapp directory.
</para><para>
<informalexample>
<mediaobject>
<textobject>
<example>
<title>pom.xml</title>
<programlisting language="xml"><![CDATA[
TODO setup code?
<properties>
<spring.version>3.0.5.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<!-- abbreviated for all the dependencies -->
<artifactId>spring-(core,context,aop,aspects,tx,webmvc)</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build><plugins>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>7.1.2.v20100523</version>
<configuration>
<webAppConfig>
<contextPath>/</contextPath>
</webAppConfig>
<!--scanIntervalSeconds>1</scanIntervalSeconds-->
</configuration>
</plugin>
</plugins></build>
]]></programlisting>
</textobject>
<caption>pom.xml</caption>
</mediaobject>
</informalexample>
</example>
<example>
<title>web.xml</title>
<programlisting language="xml"><![CDATA[
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
]]></programlisting>
</example>
</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.
@@ -44,8 +92,44 @@
<example>
<title>applicationContext.xml</title>
<programlisting language="xml"><![CDATA[
TODO setup code?
]]><filename path="pom.xml"/></programlisting>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:task="http://www.springframework.org/schema/task"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:annotation-config/>
<context:spring-configured/>
<context:component-scan base-package="org.neo4j.cineasts">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<tx:annotation-driven mode="aspectj"/>
</beans>
]]></programlisting>
</example>
<example>
<title>dispatcherServlet-servlet.xml</title>
<programlisting language="xml"><![CDATA[
<mvc:annotation-driven/>
<mvc:resources mapping="/images/**" location="/images/"/>
<mvc:resources mapping="/resources/**" location="/resources/"/>
<context:component-scan base-package="org.neo4j.cineasts.controller"/>
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/WEB-INF/views/" p:suffix=".jsp"/>
<tx:annotation-driven mode="aspectj"/>
]]></programlisting>
</example>
</para><para>

View File

@@ -1,5 +1,5 @@
<?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">
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.docbook.org/xml/4.4/docbookx.dtd">
<chapter id="tutorial_social">
<title>Movies 2.0 - Adding social</title>
<para>
@@ -9,6 +9,8 @@
<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.
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.
</para>
<para>
<programlisting language="java" ><![CDATA[
@@ -31,6 +33,7 @@ class User {
this.friends.add(user);
}
}
@RelationshipEntity
class Rating {
@StartNode User user;
@EndNode Movie movie;
@@ -44,6 +47,27 @@ class Rating {
]]></programlisting>
</para>
<para>
I extended my DatabasePopulator to add some users and ratings to the initial setup.
</para>
<para>
<programlisting language="java" ><![CDATA[
@Transactional
public List<Movie> populateDatabase() {
Actor tomHanks = new Actor("1", "Tom Hanks").persist();
Movie forestGump = new Movie("1", "Forrest Gump").persist();
tomHanks.playedIn(forestGump,"Forrest");
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();
ollie.rate(forestGump, 2, "ok");
me.addFriend(ollie);
return asList(forestGump);
}
]]></programlisting>
</para>
</section>
<section>
<title>Beware, Critics - Rating</title>
@@ -67,13 +91,9 @@ class Movie {
]]></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>
Fortunately my tests showed my 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.
</para>
</section>
</chapter>

View File

@@ -1,10 +1,86 @@
<?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">
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.docbook.org/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
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>
</para>
<para>
First step was lots of maven configuration.
<programlisting language="xml"><![CDATA[
<properties>
<aspectj.version>1.6.11.M2</aspectj.version>
</properties>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j</artifactId>
<version>1.0.0.M4</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj.version}</version>
</dependency>
<build> <plugins> <plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.2</version>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspectj.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
<configuration>
<outxml>true</outxml>
<aspectLibraries>
<aspectLibrary>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</aspectLibrary>
<aspectLibrary>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j</artifactId>
</aspectLibrary>
</aspectLibraries>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin> </plugins> </build>
]]></programlisting>
</para><para>
The spring configuration was much easier, thanks to a provided namespace.
</para><para>
<programlisting language="xml"><![CDATA[
<beans xmlns="http://www.springframework.org/schema/beans" ...
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">
<datagraph:config storeDirectory="data/graph.db"/>
</beans>
]]></programlisting>
</para>
</chapter>

View File

@@ -1,9 +1,39 @@
<?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">
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.docbook.org/xml/4.4/docbookx.dtd">
<chapter id="tutorial_user-experience">
<title>Oh the Glamour - More UI</title>
<para>
TODO screenshots
To create a nice user experience, I wanted to have a nice looking app, not something that looked like a developer
made it. So I got some UX people involved and the results were impressive. This sections presents some of the
remaining screenshots of cineasts.net.
</para>
<para>
Some of the noteworthy things. As Spring Data Graph does a read-through to the datastore for property and relationship
access I tried to minimize that by using <code>&lt;c:var/&gt;</code> several times.
The app contains very little javascript / ajax code right now, that will change when it moves ahead.
</para>
<para>
<mediaobject>
<imageobject>
<imagedata fileref="cineasts_main.png" scalefit="1" contentwidth="15cm"/>
</imageobject>
</mediaobject>
<mediaobject>
<imageobject>
<imagedata fileref="cineasts_results.png" scalefit="1" contentwidth="15cm"/>
</imageobject>
</mediaobject>
<mediaobject>
<imageobject>
<imagedata fileref="cineasts_movie.png" scalefit="1" contentwidth="15cm"/>
</imageobject>
</mediaobject>
<mediaobject>
<imageobject>
<imagedata fileref="cineasts_actor.png" scalefit="1" contentwidth="15cm"/>
</imageobject>
</mediaobject>
</para>
</chapter>

View File

@@ -1,14 +1,32 @@
<?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">
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.docbook.org/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>
<programlisting language="java"><![CDATA[
@RequestMapping(value = "/movies/{movieId}", method = RequestMethod.GET, headers = "Accept=text/html")
public String singleMovieView(final Model model, @PathVariable String movieId) {
Movie movie = moviesRepository.getMovie(movieId);
model.addAttribute("id", movieId);
if (movie != null) {
model.addAttribute("movie", movie);
model.addAttribute("stars", movie.getStars());
}
return "/movies/show";
}
]]></programlisting>
</para>
<para>
Later the nice UI would look like that:
<mediaobject>
<imageobject>
<imagedata fileref="cineasts_movie.png" contentwidth="15cm" scalefit="1" align="center"/>
</imageobject>
</mediaobject>
</para>
<section>
<title>What was his name? - Searching</title>
<para>
@@ -39,11 +57,48 @@ public void List<Movie> searchForMovie(String query, int count) {
<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="java"><![CDATA[
@RequestMapping(value = "/movies", method = RequestMethod.GET, headers = "Accept=text/html")
public String findMovies(Model model, @RequestParam("q") String query) {
List<Movie> movies = moviesRepository.findMovies(query, 20);
model.addAttribute("movies", movies);
model.addAttribute("query", query);
return "/movies/list";
}
]]></programlisting>
</para>
<para>
<programlisting language="jsp" ><![CDATA[
TODO jsp fragment
<h2>Movies</h2>
<c:choose>
<c:when test="${not empty movies}">
<dl class="listings">
<c:forEach items="${movies}" var="movie">
<dt>
<a href="/movies/${movie.id}"><c:out value="${movie.title}" /></a><br/>
</dt>
<dd>
<c:out value="${movie.description}" escapeXml="true" />
</dd>
</c:forEach>
</dl>
</c:when>
<c:otherwise>
No movies found for query &quot;${query}&quot;.
</c:otherwise>
</c:choose>
]]></programlisting>
</para>
<para>
Here is another teaser, what the final UX would look like for that:
<mediaobject>
<imageobject>
<imagedata fileref="cineasts_results.png" contentwidth="15cm" scalefit="1" align="center"/>
</imageobject>
</mediaobject>
</para>
</section>
</chapter>