diff --git a/pom.xml b/pom.xml index da0be7d2f..2070efb21 100644 --- a/pom.xml +++ b/pom.xml @@ -150,80 +150,126 @@ - - com.agilejava.docbkx - docbkx-maven-plugin - 2.0.7 - - - - generate-html - generate-pdf - - pre-site - - - - - org.docbook - docbook-xml - 4.4 - runtime - - - - javax.media - jai-core - 1.1.3 - compile - - - index.xml - true - ${project.basedir}/src/docbkx/resources/xsl/fopdf.xsl - - ${project.basedir}/src/docbkx/resources/css/html.css - ${project.basedir}/src/docbkx/resources/images/ - ${project.basedir}/src/docbkx/resources/images/ - - false - ${project.basedir}/src/docbkx/resources/xsl/html.xsl - - - - version - ${pom.version} - - - - - - - - - - - - - - - - - - - - - + + + index.xml + false + + css/html.css + ${basedir}/src/docbkx/ + ${basedir}/src/docbkx/resources/xsl/fopdf.xsl + true + + + version + ${project.version} + + + + maven-javadoc-plugin 2.5 diff --git a/src/docbkx/index.xml b/src/docbkx/index.xml index 439468353..036e7f31b 100644 --- a/src/docbkx/index.xml +++ b/src/docbkx/index.xml @@ -34,8 +34,8 @@ - + diff --git a/src/docbkx/introduction/bookinfo.xml b/src/docbkx/introduction/bookinfo.xml index 08ad0c4dd..152c92e61 100644 --- a/src/docbkx/introduction/bookinfo.xml +++ b/src/docbkx/introduction/bookinfo.xml @@ -27,8 +27,11 @@ - - + + + + + Copies of this document may be made for your own use and for distribution diff --git a/src/docbkx/introduction/preface.xml b/src/docbkx/introduction/preface.xml index 77b483dd9..a3c1e0df9 100644 --- a/src/docbkx/introduction/preface.xml +++ b/src/docbkx/introduction/preface.xml @@ -2,22 +2,24 @@ - - - + Preface + + + + + + + +
+ Foreword: Rod Johnson, CEO of SpringSource + +
+
+ Foreword: Emil Eifrem, CEO of Neo Technology + +
+
About this Guide Book Welcome to the Spring Data Graph Guide Book. Thank you for taking the time to get an in depth look into 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. +
diff --git a/src/docbkx/reference/cross-store.xml b/src/docbkx/reference/cross-store.xml index fee707a68..eb1bf27cf 100644 --- a/src/docbkx/reference/cross-store.xml +++ b/src/docbkx/reference/cross-store.xml @@ -14,12 +14,12 @@ Partial graph persistence 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. - 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. @@ -66,15 +66,12 @@ private String lastName; @GraphProperty - @Transient String nickname; @RelatedTo(type = "friends", elementClass = UserAccount.class) - @Transient Set friends; @RelatedToVia(type = "recommends", elementClass = Recommendation.class) - @Transient Iterable 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 @@ - + diff --git a/src/docbkx/reference/programming-model/introducedmethods.xml b/src/docbkx/reference/programming-model/introducedmethods.xml index a62d8c290..f0e1a156d 100644 --- a/src/docbkx/reference/programming-model/introducedmethods.xml +++ b/src/docbkx/reference/programming-model/introducedmethods.xml @@ -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. + + persisting the node-entity initially and after changes outside of a transaction, + persist participates in a transaction or creates its own implict transaction. + + + nodeEntity.persist() + + accessing node and relationship ids @@ -26,17 +34,29 @@ - creating relationships to a target node entity + creating relationships to a target node entity and returning the relationship-entity instance nodeEntity.relateTo(targetEntity, relationshipClass, relationshipType) - retrieving a single relationship + retrieving a single relationship-entity nodeEntity.getRelationshipTo(targetEnttiy, relationshipClass, relationshipType) + + creating relationships to a target node entity and returning the relationship + + nodeEntity.relateTo(targetEntity, relationshipType) + + + + retrieving a single relationship + + nodeEntity.getRelationshipTo(targetEnttiy, relationshipType) + + removing a single relationship diff --git a/src/docbkx/resources/xsl/pdf/titlepage.xml b/src/docbkx/resources/xsl/pdf/titlepage.xml index dc18e1e0d..b3cdc44e2 100644 --- a/src/docbkx/resources/xsl/pdf/titlepage.xml +++ b/src/docbkx/resources/xsl/pdf/titlepage.xml @@ -61,7 +61,7 @@ --> - + - + Allow me to introduce - Cineasts.net @@ -13,7 +13,7 @@ existing friends. - + I looked for possible sources for data, IMDB was my first stop, but they charge 15k USD for data usage. Fortunately I found TheMoviedb.org which has @@ -24,7 +24,7 @@ There were many more ideas but I wanted to get something done over the course of - + diff --git a/src/docbkx/tutorial/annotations.xml b/src/docbkx/tutorial/annotations.xml index 94d066b01..49744a102 100644 --- a/src/docbkx/tutorial/annotations.xml +++ b/src/docbkx/tutorial/annotations.xml @@ -1,13 +1,14 @@ - + Decorations - Annotated Domain - 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 Hello-World example 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. 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(). @@ -15,14 +16,14 @@ diff --git a/src/docbkx/tutorial/domain.xml b/src/docbkx/tutorial/domain.xml index 8d22e43c6..bdcc4213d 100644 --- a/src/docbkx/tutorial/domain.xml +++ b/src/docbkx/tutorial/domain.xml @@ -1,5 +1,5 @@ - + Setting the Stage - Movies Domain @@ -21,40 +21,40 @@ - cast; - } + cast; +} - class Actor { - int id; - String name; - Set filmography; - Role playedIn(Movie movie, String role); - } - class Role { - Movie movie; - Actor actor; - String role; - } - class User { - String login; - String name; - String password; - Set ratings; - Set 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 filmography; + Role playedIn(Movie movie, String role); +} +class Role { + Movie movie; + Actor actor; + String role; +} +class User { + String login; + String name; + String password; + Set ratings; + Set friends; + Rating rate(Movie movie, int stars, String comment); + void befriend(User user); +} +class Rating { + User user; + Movie movie; + int stars; + String comment; +} ]]> diff --git a/src/docbkx/tutorial/import.xml b/src/docbkx/tutorial/import.xml index 52b22b4b0..e37b24954 100644 --- a/src/docbkx/tutorial/import.xml +++ b/src/docbkx/tutorial/import.xml @@ -1,23 +1,135 @@ - + The dusty archives - Importing Data - 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 themoviedb.org. Registering there and getting an API key was simple, using the API on the commandline with curl too. Looking at the JSON returned for movies and people I decided to pimp my domain model and add some more fields so that the representation in the UI was worth the effort. - 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. + + + + 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. + + 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. - cast = (Collection) 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) data.get("posters"), "poster", "mid")); +} + ]]> + + + 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. diff --git a/src/docbkx/tutorial/indexing.xml b/src/docbkx/tutorial/indexing.xml index dae910384..4e5268c79 100644 --- a/src/docbkx/tutorial/indexing.xml +++ b/src/docbkx/tutorial/indexing.xml @@ -1,5 +1,5 @@ - + Do I know you? - Indexing @@ -10,24 +10,25 @@ 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 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()); +} ]]> diff --git a/src/docbkx/tutorial/neo4j.xml b/src/docbkx/tutorial/neo4j.xml index 135e9c8eb..7b6af07c3 100644 --- a/src/docbkx/tutorial/neo4j.xml +++ b/src/docbkx/tutorial/neo4j.xml @@ -1,37 +1,49 @@ - + Graphs ahead - Learning Neo4j - 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 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 nodes and relationships 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 Traversal Description. That all seemed pretty easy. 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. - + + org.neo4j + neo4j + 1.3.M04 + ]]> + + diff --git a/src/docbkx/tutorial/recommendations.xml b/src/docbkx/tutorial/recommendations.xml index cbea2e6a0..46c2a7507 100644 --- a/src/docbkx/tutorial/recommendations.xml +++ b/src/docbkx/tutorial/recommendations.xml @@ -1,5 +1,5 @@ - + Movies! Friends! Bargains! - Recommendations diff --git a/src/docbkx/tutorial/relationships.xml b/src/docbkx/tutorial/relationships.xml index 0b33eadc1..f8c2c583c 100644 --- a/src/docbkx/tutorial/relationships.xml +++ b/src/docbkx/tutorial/relationships.xml @@ -1,22 +1,20 @@ - + A convincing act - Relationships
Value in Relationships - Creating them - 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: @@ -27,11 +25,13 @@ quickly checking it turned out to be exactly what I needed. I added the method f
@@ -39,7 +39,7 @@ public Role playedIn(Movie movie, String roleName) { Who's there ? - Accessing related entities 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. @@ -71,33 +71,35 @@ class Actor { } ]]>
- -
- May I introduce ? - Accessing Relationships themselves 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. - + + 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). - + +
+
+ May I introduce ? - Accessing Relationships themselves + 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). cast; +@NodeEntity +class Movie { + @Indexed + int id; + String title; + int year; + @RelatedTo(elementClass = Actor.class, type = "ACTS_IN", direction = Direction.INCOMING) + Set cast; - @RelatedToVia(elementClass = Role.class, type = "ACTS_IN", direction = Direction.INCOMING) - Iterable roles; - } + @RelatedToVia(elementClass = Role.class, type = "ACTS_IN", direction = Direction.INCOMING) + Iterable roles; +} ]]> After the tests proved that those relationship fields really mirrored the underlying relationships in the graph and instantly reflected additions and removals I was diff --git a/src/docbkx/tutorial/repository.xml b/src/docbkx/tutorial/repository.xml index 374782551..f5f6e092b 100644 --- a/src/docbkx/tutorial/repository.xml +++ b/src/docbkx/tutorial/repository.xml @@ -1,25 +1,25 @@ - + Serving a good cause - Repository - 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. 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 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); + } } ]]> diff --git a/src/docbkx/tutorial/running.xml b/src/docbkx/tutorial/running.xml index d06801790..4e68b4270 100644 --- a/src/docbkx/tutorial/running.xml +++ b/src/docbkx/tutorial/running.xml @@ -1,5 +1,5 @@ - + Curtains Up! - Get it running
@@ -11,8 +11,67 @@ +@Service +public class DatabasePopulator { + + @Autowired GraphDatabaseContext ctx; + @Autowired MoviesRepository repository; + + @Transactional + public List 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 movies=populator.populateDatabase(); + model.addAttribute("movies",movies); + return "/movies/list"; + } +} + + ]]> + +<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + + +

${movie.title}

+ + + +
+ + No Movie with id ${id} found! + +
+ ]]>
+
+ 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.
@@ -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. - TODO neoclipse image + + + + + +
@@ -40,14 +104,50 @@ 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. (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) ]]>
diff --git a/src/docbkx/tutorial/security.xml b/src/docbkx/tutorial/security.xml index 28030cf00..0e1201a5a 100644 --- a/src/docbkx/tutorial/security.xml +++ b/src/docbkx/tutorial/security.xml @@ -1,40 +1,91 @@ - + Protecting Assets - Adding Security - 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. -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. + + pom.xml for spring-security + + org.springframework.security + spring-security-web + ${spring.version} + + + org.springframework.security + spring-security-config + ${spring.version} + + ]]> + + + + + web.xml + + contextConfigLocation + + /WEB-INF/applicatioContext-security.xml + /WEB-INF/applicationContext.xml + + + + + org.springframework.web.context.ContextLoaderListener + + + + springSecurityFilterChain + org.springframework.web.filter.DelegatingFilterProxy + + + + springSecurityFilterChain + /* + + ]]> + + + + + applicationContext-security.xml - + - - - - - - - - - - + + + + + + + + + + - - - - - - - + + + + + + + - ]]> + +]]> + + + UserDetailsService and UserDetails implementation + After that a logged in user was available in the session and could so be used for all the social interactions. Most of the work done next was adding controller methods -and JSPs for the views. +and JSPs for the views. I used the helper method getUserFromSession() in the controllers to access the logged in user and put it in the model for rendering. + As a teaser I'd like to show off the user profile page, as it will be rendered after heavy UX lifting. + + + + + diff --git a/src/docbkx/tutorial/setup.xml b/src/docbkx/tutorial/setup.xml index 03ac55680..24adaee8c 100644 --- a/src/docbkx/tutorial/setup.xml +++ b/src/docbkx/tutorial/setup.xml @@ -1,5 +1,5 @@ - + Scope: Spring @@ -23,16 +23,64 @@ in the webapp directory. - - - + + pom.xml + 3.0.5.RELEASE + + + + + org.springframework + + spring-(core,context,aop,aspects,tx,webmvc) + ${spring.version} + + + org.springframework + spring-test + ${spring.version} + test + + + + + + org.mortbay.jetty + jetty-maven-plugin + 7.1.2.v20100523 + + + / + + + + + + ]]> - - pom.xml - - + + + + web.xml + + org.springframework.web.context.ContextLoaderListener + + + + dispatcherServlet + org.springframework.web.servlet.DispatcherServlet + 1 + + + + dispatcherServlet + / + + ]]> + 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 @@ applicationContext.xml + + + + + + + + + + + +]]> + + + dispatcherServlet-servlet.xml + + + + + + + + +]]> diff --git a/src/docbkx/tutorial/social.xml b/src/docbkx/tutorial/social.xml index 349ae4804..266d27e6d 100644 --- a/src/docbkx/tutorial/social.xml +++ b/src/docbkx/tutorial/social.xml @@ -1,5 +1,5 @@ - + Movies 2.0 - Adding social @@ -9,6 +9,8 @@ See, mom a Cineast! - Users 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. + + I extended my DatabasePopulator to add some users and ratings to the initial setup. + + + 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); +} + ]]> +
Beware, Critics - Rating @@ -67,13 +91,9 @@ class Movie { ]]> - 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. - - - + 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.
diff --git a/src/docbkx/tutorial/spring-data-graph.xml b/src/docbkx/tutorial/spring-data-graph.xml index 220a0966f..638006a6f 100644 --- a/src/docbkx/tutorial/spring-data-graph.xml +++ b/src/docbkx/tutorial/spring-data-graph.xml @@ -1,10 +1,86 @@ - + Conjuring Magic - Spring Data Graph - 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. - +
+ + First step was lots of maven configuration. + + 1.6.11.M2 + + + + org.springframework.data + spring-data-neo4j + 1.0.0.M4 + + + + org.aspectj + aspectjrt + ${aspectj.version} + + + + org.codehaus.mojo + aspectj-maven-plugin + 1.2 + + + org.aspectj + aspectjrt + ${aspectj.version} + + + org.aspectj + aspectjtools + ${aspectj.version} + + + + + + compile + test-compile + + + + + true + + + org.springframework + spring-aspects + + + org.springframework.data + spring-data-neo4j + + + 1.6 + 1.6 + + + ]]> + + The spring configuration was much easier, thanks to a provided namespace. + + + + + + ]]> +
diff --git a/src/docbkx/tutorial/user-experience.xml b/src/docbkx/tutorial/user-experience.xml index 49fcd8812..87f0d3144 100644 --- a/src/docbkx/tutorial/user-experience.xml +++ b/src/docbkx/tutorial/user-experience.xml @@ -1,9 +1,39 @@ - + Oh the Glamour - More UI - 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. + + + 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 <c:var/> several times. + The app contains very little javascript / ajax code right now, that will change when it moves ahead. + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/docbkx/tutorial/webapp.xml b/src/docbkx/tutorial/webapp.xml index 6326ae88d..d0c3ceb9c 100644 --- a/src/docbkx/tutorial/webapp.xml +++ b/src/docbkx/tutorial/webapp.xml @@ -1,14 +1,32 @@ - + Showing off - Web views 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á. - TODO screenshot of movie display - - + +
+ + Later the nice UI would look like that: + + + + + +
What was his name? - Searching @@ -39,11 +57,48 @@ public void List searchForMovie(String query, int count) { 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. + + + movies = moviesRepository.findMovies(query, 20); + model.addAttribute("movies", movies); + model.addAttribute("query", query); + return "/movies/list"; +} +]]> Movies + + + +
+ +
+
+
+
+ +
+
+
+
+ + No movies found for query "${query}". + +
]]>
+ + Here is another teaser, what the final UX would look like for that: + + + + + +