updated documentation and tutorial
This commit is contained in:
186
pom.xml
186
pom.xml
@@ -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>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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<Role>. 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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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><c:var/></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>
|
||||
|
||||
@@ -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 "${query}".
|
||||
</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>
|
||||
|
||||
Reference in New Issue
Block a user