diff --git a/pom.xml b/pom.xml index b1a220d91..fc75050bd 100644 --- a/pom.xml +++ b/pom.xml @@ -157,6 +157,7 @@ spring-data-neo4j-examples/hello-worlds spring-data-neo4j-examples/imdb spring-data-neo4j-examples/cineasts + spring-data-neo4j-examples/cineasts-aspects spring-data-neo4j-examples/cineasts-rest spring-data-neo4j-examples/myrestaurants-social diff --git a/spring-data-neo4j-examples/cineasts/Readme.md b/spring-data-neo4j-examples/cineasts/Readme.md new file mode 100644 index 000000000..4913adb66 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/Readme.md @@ -0,0 +1,16 @@ +![Cineasts.net Logo](https://github.com/jexp/cineasts/raw/master/cineasts.png) +Cineasts.net +============ + +This project is the demo/tutorial application for the [Spring Data Graph](http://github.com/springsource/spring-data-graph) library which provides convenient access to the [Neo4j](http://neo4j.org) graph database. + +This tutorial creates a complete web application built on top of Spring Data Graph. + +It uses a domain that should be familiar to most - movies. So for cineasts.net we decided to add a social +touch to the whole movie rating business, allowing friends to share their ratings and get recommendations +for new friends and movies. + +The tutorial is presented as a colloquial description of the steps necessary to create the application. +It provides the configuration and code examples that are needed to understand what's happening. + +The complete tutorial is contained in this projects [github wiki](https://github.com/jexp/cineasts/wiki). \ No newline at end of file diff --git a/spring-data-neo4j-examples/cineasts/pom.xml b/spring-data-neo4j-examples/cineasts/pom.xml new file mode 100644 index 000000000..c3b54477b --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/pom.xml @@ -0,0 +1,364 @@ + + 4.0.0 + + org.neo4j.examples + cineasts + 2.0-SNAPSHOT + war + + Movies + + + UTF-8 + 3.0.6.RELEASE + 1.6.1 + UTF-8 + 1.5.M02 + 2.0.0.BUILD-SNAPSHOT + 1.6.12.M1 + 1.6.1 + + + + + spring-maven-release + Spring Maven Release Repository + http://maven.springframework.org/release + + + spring-maven-snapshot + Spring Maven Snapshot Repository + + true + + http://maven.springframework.org/snapshot + + + spring-maven-milestone + Spring Maven Milestone Repository + http://maven.springframework.org/milestone + + + neo4j-release-repository + Neo4j Maven 2 release repository + http://m2.neo4j.org/releases + + true + + + false + + + + neo4j-snapshot-repository + Neo4j Maven 2 snapshot repository + http://m2.neo4j.org/snapshots + + true + + + false + + + + jboss-public-repo + http://repository.jboss.org/maven2/ + JBoss public available repo + + + + + + spring-maven-release + Spring Maven Release Repository + http://maven.springframework.org/release + + + spring-maven-milestone + Spring Maven Milestone Repository + http://maven.springframework.org/milestone + + + + + + + org.springframework + spring-core + ${spring.version} + + + commons-logging + commons-logging + + + + + org.springframework + spring-test + ${spring.version} + test + + + commons-logging + commons-logging + + + + + + org.springframework + spring-context + ${spring.version} + + + org.springframework + spring-aop + ${spring.version} + + + org.springframework + spring-aspects + ${spring.version} + + + org.springframework + spring-tx + ${spring.version} + + + + org.springframework + spring-webmvc + ${spring.version} + + + commons-logging + commons-logging + + + + + + org.springframework.security + spring-security-web + ${spring.version} + + + org.springframework.security + spring-security-config + ${spring.version} + + + + org.slf4j + slf4j-api + ${org.slf4j-version} + + + org.slf4j + jcl-over-slf4j + ${org.slf4j-version} + + + org.slf4j + slf4j-log4j12 + ${org.slf4j-version} + runtime + + + log4j + log4j + 1.2.16 + runtime + + + + org.springframework.data + spring-data-neo4j-aspects + ${spring-data-graph.version} + + + org.hibernate.javax.persistence + hibernate-jpa-2.0-api + + + + + + opensymphony + sitemesh + 2.4.2 + + + + org.hibernate + hibernate-validator + 4.0.2.GA + + + + javax.servlet + servlet-api + 2.5 + provided + + + javax.servlet.jsp + jsp-api + 2.1 + provided + + + javax.servlet + jstl + 1.2 + + + + org.codehaus.jackson + jackson-mapper-asl + 1.7.2 + + + + org.aspectj + aspectjrt + ${aspectj.version} + + + + cglib + cglib-nodep + 2.2 + + + + org.hibernate.javax.persistence + hibernate-jpa-2.0-api + 1.0.0.Final + + + + + + org.neo4j + neo4j-kernel + ${neo4j.version} + test-jar + test + + + org.neo4j + neo4j-cypher + ${neo4j.version} + + + + junit + junit + 4.8.1 + test + + + + + + org.mortbay.jetty + jetty-maven-plugin + 7.1.2.v20100523 + + + / + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.1 + + 1.6 + 1.6 + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + **/*Tests.java + **/*Test.java + + + **/Abstract*.java + + junit:junit + + + + + org.apache.maven.plugins + maven-eclipse-plugin + 2.8 + + + 1.5 + + org.eclipse.ajdt.ui.ajnature + org.eclipse.jdt.core.javanature + org.springframework.ide.eclipse.core.springnature + + + + + + 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-aspects + + + 1.6 + 1.6 + + + + + + diff --git a/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/controller/AuthController.java b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/controller/AuthController.java new file mode 100644 index 000000000..48d7d7c4d --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/controller/AuthController.java @@ -0,0 +1,63 @@ +package org.neo4j.cineasts.controller; + + +import org.neo4j.cineasts.service.CineastsUserDetailsService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +/** + * Handles and retrieves the login or denied page depending on the URI template + */ +@Controller +@RequestMapping("/auth") +public class AuthController { + + private final static Logger logger = LoggerFactory.getLogger(AuthController.class); + + @Autowired + CineastsUserDetailsService userDetailsService; + @RequestMapping(value = "/login", method = RequestMethod.GET) + public String login(@RequestParam(value = "login_error", required = false) boolean error, Model model) { + logger.debug("Received request to show login page, error "+error); + if (error) { + model.addAttribute("error", "You have entered an invalid username or password!"); + } + return "/auth/loginpage"; + } + + @RequestMapping(value = "/register", method = RequestMethod.POST) + public String register( + @RequestParam(value = "j_username") String login, + @RequestParam(value = "j_displayname") String name, + @RequestParam(value = "j_password") String password, + Model model) { + + try { + userDetailsService.register(login,name,password); + return "forward:/user/"+login; + } catch(Exception e) { + model.addAttribute("j_username",login); + model.addAttribute("j_displayname",name); + model.addAttribute("error",e.getMessage()); + return "/auth/registerpage"; + } + } + + @RequestMapping(value = "/denied", method = RequestMethod.GET) + public String denied() { + logger.debug("Received request to show denied page"); + return "/auth/deniedpage"; + } + + @RequestMapping(value = "/registerpage", method = RequestMethod.GET) + public String registerPage() { + logger.debug("Received request to show register page"); + return "/auth/registerpage"; + } +} \ No newline at end of file diff --git a/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/controller/ImportController.java b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/controller/ImportController.java new file mode 100644 index 000000000..61aa74861 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/controller/ImportController.java @@ -0,0 +1,64 @@ +package org.neo4j.cineasts.controller; + +import org.neo4j.cineasts.movieimport.MovieDbImportService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * @author mh + * @since 04.03.11 + */ +@Controller +public class ImportController { + + private MovieDbImportService importService; + private static final Logger log = LoggerFactory.getLogger(ImportController.class); + + @Autowired + public ImportController(MovieDbImportService importService) { + this.importService = importService; + } + + @RequestMapping(value = "/import/{ids}", method = RequestMethod.GET) + public String importMovie(@PathVariable String ids, Model model) { + long start=System.currentTimeMillis(); + final Map movies = importService.importMovies(extractRanges(ids)); + long duration = (System.currentTimeMillis() - start) / 1000; + model.addAttribute("duration", duration); + model.addAttribute("ids", ids); + model.addAttribute("movies", movies.entrySet()); + return "import/result"; + } + + + private Map extractRanges(String ids) { + Map ranges = new LinkedHashMap(); + StringBuilder errors = new StringBuilder(); + for (String token : ids.split(",")) { + try { + if (token.contains("-")) { + String[] range = token.split("-"); + ranges.put(Integer.parseInt(range[0]), Integer.parseInt(range[1])); + } else { + int id = Integer.parseInt(token); + ranges.put(id, id); + } + } catch (Exception e) { + errors.append(token).append(": ").append(e.getMessage()).append("\n"); + } + } + if (errors.length() > 0) { + throw new RuntimeException("Error parsing ids\n" + errors); + } + return ranges; + } +} diff --git a/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/controller/MovieController.java b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/controller/MovieController.java new file mode 100644 index 000000000..ddfa15478 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/controller/MovieController.java @@ -0,0 +1,132 @@ +package org.neo4j.cineasts.controller; + +import org.neo4j.cineasts.domain.Movie; +import org.neo4j.cineasts.domain.Person; +import org.neo4j.cineasts.domain.Rating; +import org.neo4j.cineasts.domain.User; +import org.neo4j.cineasts.repository.MovieRepository; +import org.neo4j.cineasts.repository.PersonRepository; +import org.neo4j.cineasts.service.CineastsRepository; +import org.neo4j.cineasts.service.CineastsUserDetailsService; +import org.neo4j.cineasts.service.DatabasePopulator; +import org.neo4j.helpers.collection.IteratorUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.*; + +import java.util.Collection; +import java.util.Collections; + +/** + * @author mh + * @since 04.03.11 + */ +@Controller +public class MovieController { + + @Autowired + private MovieRepository movieRepository; + @Autowired + private PersonRepository personRepository; + @Autowired + private CineastsUserDetailsService userDetailsService; + @Autowired + private DatabasePopulator populator; + private static final Logger log = LoggerFactory.getLogger(MovieController.class); + + /** + * Only matches 'GET /moviies/{id}}' requests for JSON content; a 404 is sent otherwise. + * TODO send a 406 if an unsupported representation, such as XML, is requested. See SPR-7353. + */ + @RequestMapping(value = "/movies/{id}", method = RequestMethod.GET, headers = "Accept=application/json") + public + @ResponseBody + Movie getMovie(@PathVariable String id) { + return movieRepository.findById(id); + } + + + @RequestMapping(value = "/movies/{movieId}", method = RequestMethod.GET, headers = "Accept=text/html") + public String singleMovieView(final Model model, @PathVariable String movieId) { + User user = addUser(model); + Movie movie = movieRepository.findById(movieId); + model.addAttribute("id", movieId); + if (movie != null) { + model.addAttribute("movie", movie); + final int stars = movie.getStars(); + model.addAttribute("stars", stars); + Rating rating = null; + if (user!=null) rating = movie.getRelationshipTo(user, Rating.class, "RATED"); + if (rating == null) rating = new Rating().rate(stars,null); + model.addAttribute("userRating",rating); + } + return "/movies/show"; + } + + @RequestMapping(value = "/movies/{movieId}", method = RequestMethod.POST, headers = "Accept=text/html") + public String updateMovie(Model model, @PathVariable String movieId, @RequestParam(value = "rated",required = false) Integer stars, @RequestParam(value = "comment",required = false) String comment) { + Movie movie = movieRepository.findById(movieId); + User user = userDetailsService.getUserFromSession(); + if (user != null && movie != null) { + int stars1 = stars==null ? -1 : stars; + String comment1 = comment!=null ? comment.trim() : null; + user.rate(movie, stars1, comment1); + } + return singleMovieView(model,movieId); + } + + private User addUser(Model model) { + User user = userDetailsService.getUserFromSession(); + model.addAttribute("user", user); + return user; + } + + @RequestMapping(value = "/movies", method = RequestMethod.GET, headers = "Accept=text/html") + public String findMovies(Model model, @RequestParam("q") String query) { + if (query!=null && !query.isEmpty()) { + Page movies = movieRepository.findByTitleLike(query, new PageRequest(0, 20)); + model.addAttribute("movies", movies.getContent()); + } else { + model.addAttribute("movies", Collections.emptyList()); + } + model.addAttribute("query", query); + addUser(model); + return "/movies/list"; + } + + @RequestMapping(value = "/actors/{id}", method = RequestMethod.GET, headers = "Accept=text/html") + public String singleActorView(Model model, @PathVariable String id) { + Person person = personRepository.findById(id); + model.addAttribute("actor", person); + model.addAttribute("id", id); + model.addAttribute("roles", IteratorUtil.asCollection(person.getRoles())); + addUser(model); + return "/actors/show"; + } + + //@RequestMapping(value = "/admin/populate", method = RequestMethod.GET) + @RequestMapping(value = "/populate", method = RequestMethod.GET) + public String populateDatabase(Model model) { + Collection movies=populator.populateDatabase(); + model.addAttribute("movies",movies); + addUser(model); + return "/movies/list"; + } + + @RequestMapping(value = "/admin/clean", method = RequestMethod.GET) + public String clean(Model model) { + populator.cleanDb(); + return "movies/list"; + } + + @RequestMapping(value = "/", method = RequestMethod.GET) + public String index(Model model) { + addUser(model); + return "index"; + } +} diff --git a/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/controller/UserController.java b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/controller/UserController.java new file mode 100644 index 000000000..05bb133c6 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/controller/UserController.java @@ -0,0 +1,64 @@ +package org.neo4j.cineasts.controller; + + +import org.neo4j.cineasts.domain.User; +import org.neo4j.cineasts.repository.MovieRepository; +import org.neo4j.cineasts.service.CineastsUserDetailsService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +/** + * Handles and retrieves the login or denied page depending on the URI template + */ +@Controller +public class UserController { + + @Autowired CineastsUserDetailsService userDetailsService; + @Autowired + MovieRepository movieRepository; + + private final static Logger logger = LoggerFactory.getLogger(UserController.class); + + @RequestMapping(value = "/user", method = RequestMethod.GET) + public String profile(Model model) { + final User user = userDetailsService.getUserFromSession(); + model.addAttribute("user", user); + if (user!=null) { + model.addAttribute("recommendations", movieRepository.getRecommendations(user)); + } + return "/user/index"; + } + + @RequestMapping(value = "/user/{login}/friends", method = RequestMethod.POST) + public String addFriend(Model model, @PathVariable("login") String login) { + userDetailsService.addFriend(login); + return "forward:/user/"+login; + } + + @RequestMapping(value = "/user/{login}") + public String publicProfile(Model model, @PathVariable("login") String login) { + User profiled = userDetailsService.findUser(login); + User user = userDetailsService.getUserFromSession(); + + return publicProfile(model, profiled, user); + } + + private String publicProfile(Model model, User profiled, User user) { + if (profiled.equals(user)) return profile(model); + + model.addAttribute("profiled", profiled); + model.addAttribute("user", user); + model.addAttribute("isFriend", areFriends(profiled, user)); + return "/user/public"; + } + + private boolean areFriends(User user, User loggedIn) { + return user!=null && user.isFriend(loggedIn); + } +} \ No newline at end of file diff --git a/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/domain/Movie.java b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/domain/Movie.java new file mode 100644 index 000000000..fdad46ad6 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/domain/Movie.java @@ -0,0 +1,228 @@ +package org.neo4j.cineasts.domain; + +import org.neo4j.graphdb.Direction; +import org.neo4j.helpers.collection.IteratorUtil; +import org.springframework.data.neo4j.annotation.NodeEntity; +import org.springframework.data.neo4j.annotation.RelatedTo; +import org.springframework.data.neo4j.annotation.RelatedToVia; +import org.springframework.data.neo4j.annotation.Indexed; +import org.springframework.data.neo4j.support.index.IndexType; + +import java.util.*; + +import static org.neo4j.graphdb.Direction.INCOMING; + + +/** + * @author mh + * @since 04.03.11 + */ +@NodeEntity +public class Movie { + + @Indexed + String id; + + @Indexed(indexType=IndexType.FULLTEXT, indexName = "search") + String title; + + String description; + + @RelatedTo(type="DIRECTED", direction = INCOMING) + Person director; + + @RelatedTo(type = "ACTS_IN", direction = INCOMING) + Set actors; + + @RelatedToVia(elementClass = Role.class, type = "ACTS_IN", direction = INCOMING) + Iterable roles; + + @RelatedToVia(elementClass = Rating.class, type = "RATED", direction = INCOMING) + Iterable ratings; + private String language; + private String imdbId; + private String tagline; + private Date releaseDate; + private Integer runtime; + private String homepage; + private String trailer; + private String genre; + private String studio; + private Integer version; + private Date lastModified; + private String imageUrl; + + public Movie() { + } + + public Movie(String id, String title) { + this.id = id; + this.title = title; + } + + public Collection getActors() { + return actors; + } + + public Collection getRoles() { + return IteratorUtil.asCollection(roles); + } + + public int getYear() { + if (releaseDate==null) return 0; + Calendar cal = Calendar.getInstance(); + cal.setTime(releaseDate); + return cal.get(Calendar.YEAR); + } + + public String getId() { + return id; + } + + public String getTitle() { + return title; + } + + @Override + public String toString() { + return String.format("%s (%s) [%s]", title, releaseDate, id); + } + + public String getDescription() { + return description; + } + + public int getStars() { + Iterable allRatings = ratings; + + if (allRatings == null) return 0; + int stars=0, count=0; + for (Rating rating : allRatings) { + stars += rating.getStars(); + count++; + } + return count==0 ? 0 : stars / count; + } + + public Collection getRatings() { + Iterable allRatings = ratings; + return allRatings == null ? Collections.emptyList() : IteratorUtil.asCollection(allRatings); + } + + public Person getDirector() { + return director; + } + + public void setTitle(String title) { + this.title=title; + } + + public void setLanguage(String language) { + this.language = language; + } + + public void setImdbId(String imdbId) { + this.imdbId = imdbId; + } + + public void setTagline(String tagline) { + this.tagline = tagline; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setReleaseDate(Date releaseDate) { + this.releaseDate = releaseDate; + } + + public void setRuntime(Integer runtime) { + this.runtime = runtime; + } + + public void setHomepage(String homepage) { + this.homepage = homepage; + } + + public void setTrailer(String trailer) { + this.trailer = trailer; + } + + public void setGenre(String genre) { + this.genre = genre; + } + + public void setStudio(String studio) { + this.studio = studio; + } + + public void setVersion(Integer version) { + this.version = version; + } + + public void setLastModified(Date lastModified) { + this.lastModified = lastModified; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + + public String getLanguage() { + return language; + } + + public String getImdbId() { + return imdbId; + } + + public String getTagline() { + return tagline; + } + + public Date getReleaseDate() { + return releaseDate; + } + + public Integer getRuntime() { + return runtime; + } + + public String getHomepage() { + return homepage; + } + + public String getTrailer() { + return trailer; + } + + public String getGenre() { + return genre; + } + + public String getStudio() { + return studio; + } + + public Integer getVersion() { + return version; + } + + public Date getLastModified() { + return lastModified; + } + + public String getImageUrl() { + return imageUrl; + } + + public String getYoutubeId() { + String trailerUrl = trailer; + if (trailerUrl==null || !trailerUrl.contains("youtu")) return null; + String[] parts = trailerUrl.split("[=/]"); + int numberOfParts = parts.length; + return numberOfParts > 0 ? parts[numberOfParts-1] : null; + } +} + diff --git a/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/domain/MovieRecommendation.java b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/domain/MovieRecommendation.java new file mode 100644 index 000000000..5568c7239 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/domain/MovieRecommendation.java @@ -0,0 +1,17 @@ +package org.neo4j.cineasts.domain; + +import org.springframework.data.neo4j.annotation.MapResult; +import org.springframework.data.neo4j.annotation.ResultColumn; + +/** +* @author mh +* @since 04.11.11 +*/ +@MapResult +public interface MovieRecommendation { + @ResultColumn("otherMovie") + Movie getMovie(); + + @ResultColumn("avg(r3.stars)") + int getRating(); +} diff --git a/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/domain/Person.java b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/domain/Person.java new file mode 100644 index 000000000..9cb5e9698 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/domain/Person.java @@ -0,0 +1,126 @@ +package org.neo4j.cineasts.domain; + +import org.springframework.data.neo4j.annotation.Indexed; +import org.springframework.data.neo4j.annotation.NodeEntity; +import org.springframework.data.neo4j.annotation.RelatedTo; +import org.springframework.data.neo4j.annotation.RelatedToVia; +import org.springframework.data.neo4j.support.index.IndexType; + +import java.util.Date; +import java.util.Set; + +/** + * @author mh + * @since 12.03.11 + */ +@NodeEntity +public class Person { + @Indexed + String id; + @Indexed(indexType=IndexType.FULLTEXT, indexName = "people") + String name; + private Date birthday; + private String birthplace; + private String biography; + private Integer version; + private Date lastModified; + private String profileImageUrl; + + public Person(String id, String name) { + this.id = id; + this.name = name; + } + + public Person() { + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return String.format("%s [%s]", name, id); + } + + public void setBirthday(Date birthday) { + this.birthday = birthday; + } + + public void setBirthplace(String birthplace) { + this.birthplace = birthplace; + } + + public void setBiography(String biography) { + this.biography = biography; + } + + public void setVersion(Integer version) { + this.version = version; + } + + public void setLastModified(Date lastModified) { + this.lastModified = lastModified; + } + + public Date getBirthday() { + return birthday; + } + + public String getBirthplace() { + return birthplace; + } + + public String getBiography() { + return biography; + } + + public Integer getVersion() { + return version; + } + + public Date getLastModified() { + return lastModified; + } + + public void setProfileImageUrl(String profileImageUrl) { + this.profileImageUrl = profileImageUrl; + } + + public String getProfileImageUrl() { + return profileImageUrl; + } + + @RelatedTo(elementClass = Movie.class, type = "DIRECTED") + private Set directedMovies; + + public Set getDirectedMovies() { + return directedMovies; + } + + public void directed(Movie movie) { + this.directedMovies.add(movie); + } + + @RelatedToVia(elementClass = Role.class, type = "ACTS_IN") + Iterable roles; + + public Iterable getRoles() { + return roles; + } + + public Role playedIn(Movie movie, String roleName) { + Role role = relateTo(movie, Role.class, "ACTS_IN"); + role.setName(roleName); + return role; + } + +} diff --git a/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/domain/Rating.java b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/domain/Rating.java new file mode 100644 index 000000000..ea14ea5f9 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/domain/Rating.java @@ -0,0 +1,52 @@ +package org.neo4j.cineasts.domain; + +import org.springframework.data.neo4j.annotation.EndNode; +import org.springframework.data.neo4j.annotation.RelationshipEntity; +import org.springframework.data.neo4j.annotation.StartNode; + +/** + * @author mh + * @since 04.03.11 + */ +@RelationshipEntity +public class Rating { + private static final int MAX_STARS = 5; + private static final int MIN_STARS = 0; + + @StartNode + User user; + @EndNode Movie movie; + + int stars; + String comment; + + public User getUser() { + return user; + } + + public Movie getMovie() { + return movie; + } + + public int getStars() { + return stars; + } + + public void setStars(int stars) { + this.stars = stars; + } + + public String getComment() { + return comment; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public Rating rate(int stars, String comment) { + if (stars>= MIN_STARS && stars <= MAX_STARS) this.stars=stars; + if (comment!=null && !comment.isEmpty()) this.comment = comment; + return this; + } +} diff --git a/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/domain/Role.java b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/domain/Role.java new file mode 100644 index 000000000..e5a4dac27 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/domain/Role.java @@ -0,0 +1,38 @@ +package org.neo4j.cineasts.domain; + +import org.springframework.data.neo4j.annotation.EndNode; +import org.springframework.data.neo4j.annotation.RelationshipEntity; +import org.springframework.data.neo4j.annotation.StartNode; + +/** + * @author mh + * @since 04.03.11 + */ +@RelationshipEntity +public class Role { + @EndNode Movie movie; + @StartNode Person actor; + + String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Movie getMovie() { + return movie; + } + + public Person getActor() { + return actor; + } + + @Override + public String toString() { + return String.format("%s acts as %s in %s", actor, name, movie); + } +} diff --git a/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/domain/Roles.java b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/domain/Roles.java new file mode 100644 index 000000000..3319ab32f --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/domain/Roles.java @@ -0,0 +1,9 @@ +package org.neo4j.cineasts.domain; + +/** +* @author mh +* @since 12.03.11 +*/ +public enum Roles { + ACTS_IN, DIRECTED +} diff --git a/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/domain/User.java b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/domain/User.java new file mode 100644 index 000000000..8d362207a --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/domain/User.java @@ -0,0 +1,117 @@ +package org.neo4j.cineasts.domain; + +import org.neo4j.graphdb.Direction; +import org.neo4j.helpers.collection.IteratorUtil; +import org.springframework.data.neo4j.annotation.Indexed; +import org.springframework.data.neo4j.annotation.NodeEntity; +import org.springframework.data.neo4j.annotation.RelatedTo; +import org.springframework.data.neo4j.annotation.RelatedToVia; +import org.springframework.security.authentication.encoding.Md5PasswordEncoder; +import org.springframework.security.core.GrantedAuthority; + +import java.util.Collection; +import java.util.Set; + +@NodeEntity +public class User { + private static final String SALT = "cewuiqwzie"; + public static final String FRIEND = "FRIEND"; + public static final String RATED = "RATED"; + @Indexed + String login; + String name; + String password; + String info; + private Roles[] roles; + + public User() { + } + + public User(String login, String name, String password, Roles... roles) { + this.login = login; + this.name = name; + this.password = encode(password); + this.roles = roles; + } + + private String encode(String password) { + return new Md5PasswordEncoder().encodePassword(password, SALT); + } + + @RelatedToVia(elementClass = Rating.class, type = RATED) + Iterable ratings; + + @RelatedTo(elementClass = Movie.class, type = RATED) + Set favorites; + + + @RelatedTo(elementClass = User.class, type = FRIEND, direction = Direction.BOTH) + Set friends; + + public void addFriend(User friend) { + this.friends.add(friend); + } + + public Rating rate(Movie movie, int stars, String comment) { + return relateTo(movie, Rating.class, RATED).rate(stars, comment); + } + + public Collection getRatings() { + return IteratorUtil.asCollection(ratings); + } + + @Override + public String toString() { + return String.format("%s (%s)", name, login); + } + + public String getName() { + return name; + } + + public Set getFriends() { + return friends; + } + + public Roles[] getRole() { + return roles; + } + + + public String getLogin() { + return login; + } + + public String getPassword() { + return password; + } + + public String getInfo() { + return info; + } + public void setInfo(String info) { + this.info = info; + } + public void updatePassword(String old, String newPass1, String newPass2) { + if (!password.equals(encode(old))) throw new IllegalArgumentException("Existing Password invalid"); + if (!newPass1.equals(newPass2)) throw new IllegalArgumentException("New Passwords don't match"); + this.password = encode(newPass1); + } + + public void setName(String name) { + this.name = name; + } + + public boolean isFriend(User other) { + return other!=null && getFriends().contains(other); + } + + public enum Roles implements GrantedAuthority { + ROLE_USER, ROLE_ADMIN; + + @Override + public String getAuthority() { + return name(); + } + } +} diff --git a/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/movieimport/MovieDbApiClient.java b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/movieimport/MovieDbApiClient.java new file mode 100644 index 000000000..f07fc9753 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/movieimport/MovieDbApiClient.java @@ -0,0 +1,46 @@ +package org.neo4j.cineasts.movieimport; + +import org.codehaus.jackson.map.ObjectMapper; + +import java.net.URL; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class MovieDbApiClient { + + private final String baseUrl = "http://api.themoviedb.org/"; + private final String apiKey; + protected final ObjectMapper mapper; + + public MovieDbApiClient(String apiKey) { + this.apiKey = apiKey; + mapper = new ObjectMapper(); + } + + public Map getMovie(String id) { + return loadJsonData(id, buildMovieUrl(id)); + } + + private Map loadJsonData(String id, String url) { + try { + List value = mapper.readValue(new URL(url), List.class); + if (value.isEmpty() || value.get(0).equals("Nothing found.")) return Collections.singletonMap("not_found",System.currentTimeMillis()); + return (Map) value.get(0); + } catch (Exception e) { + throw new RuntimeException("Failed to get data from " + url, e); + } + } + + private String buildMovieUrl(String movieId) { + return String.format("%s2.1/Movie.getInfo/en/json/%s/%s", baseUrl, apiKey, movieId); + } + + public Map getPerson(String id) { + return loadJsonData(id, buildPersonUrl(id)); + } + + private String buildPersonUrl(String personId) { + return String.format("%s2.1/Person.getInfo/en/json/%s/%s", baseUrl, apiKey, personId); + } +} diff --git a/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/movieimport/MovieDbException.java b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/movieimport/MovieDbException.java new file mode 100644 index 000000000..a1da5992b --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/movieimport/MovieDbException.java @@ -0,0 +1,14 @@ +package org.neo4j.cineasts.movieimport; + +public class MovieDbException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public MovieDbException(String message) { + super(message); + } + + public MovieDbException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/movieimport/MovieDbImportService.java b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/movieimport/MovieDbImportService.java new file mode 100644 index 000000000..6f8250c96 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/movieimport/MovieDbImportService.java @@ -0,0 +1,132 @@ +package org.neo4j.cineasts.movieimport; + +import org.neo4j.cineasts.domain.*; +import org.neo4j.cineasts.repository.MovieRepository; +import org.neo4j.cineasts.repository.PersonRepository; +import org.neo4j.cineasts.service.CineastsRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +@Service +public class MovieDbImportService { + + private static final Logger logger = LoggerFactory.getLogger(MovieDbImportService.class); + MovieDbJsonMapper movieDbJsonMapper = new MovieDbJsonMapper(); + + @Autowired + MovieRepository movieRepository; + @Autowired + PersonRepository personRepository; + + @Autowired + MovieDbApiClient client; + + @Autowired + MovieDbLocalStorage localStorage; + + @Transactional + public Map importMovies(Map ranges) { + final Map movies=new LinkedHashMap(); + for (Map.Entry entry : ranges.entrySet()) { + for (int id = entry.getKey(); id <= entry.getValue(); id++) { + String result = importMovieFailsafe(id); + movies.put(id, result); + } + } + return movies; + } + + private String importMovieFailsafe(Integer id) { + try { + Movie movie = doImportMovie(String.valueOf(id)); + return movie.getTitle(); + } catch (Exception e) { + return e.getMessage(); + } + } + + @Transactional + public Movie importMovie(String movieId) { + return doImportMovie(movieId); + } + + private Movie doImportMovie(String movieId) { + logger.debug("Importing movie " + movieId); + + Movie movie = movieRepository.findById(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 Map loadMovieData(String movieId) { + if (localStorage.hasMovie(movieId)) { + return localStorage.loadMovie(movieId); + } + + Map data = client.getMovie(movieId); + localStorage.storeMovie(movieId, data); + return data; + } + + private void relatePersonsToMovie(Movie movie, Map data) { + Collection cast = (Collection) data.get("cast"); + for (Map entry : cast) { + String id = "" + entry.get("id"); + String jobName = (String) entry.get("job"); + Roles job = movieDbJsonMapper.mapToRole(jobName); + if (job==null) { + if (logger.isInfoEnabled()) logger.info("Could not add person with job "+jobName+" "+entry); + continue; + } + Person person = doImportPerson(id); + switch (job) { + case DIRECTED: + person.directed(movie); + break; + case ACTS_IN: + person.playedIn(movie, (String) entry.get("character")); + break; + } + } + } + + @Transactional + public Person importPerson(String personId) { + return doImportPerson(personId); + } + + private Person doImportPerson(String personId) { + logger.debug("Importing person " + personId); + Person person = personRepository.findById(personId); + if (person!=null) return person; + Map data = loadPersonData(personId); + if (data.containsKey("not_found")) throw new RuntimeException("Data for Person "+personId+" not found."); + Person newPerson=new Person(personId,null); + movieDbJsonMapper.mapToPerson(data, newPerson); + return newPerson.persist(); + } + + private Map loadPersonData(String personId) { + if (localStorage.hasPerson(personId)) { + return localStorage.loadPerson(personId); + } + Map data = client.getPerson(personId); + localStorage.storePerson(personId, data); + return localStorage.loadPerson(personId); + } +} diff --git a/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/movieimport/MovieDbJsonMapper.java b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/movieimport/MovieDbJsonMapper.java new file mode 100644 index 000000000..c1675830c --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/movieimport/MovieDbJsonMapper.java @@ -0,0 +1,704 @@ +package org.neo4j.cineasts.movieimport; + +import org.neo4j.cineasts.domain.Movie; +import org.neo4j.cineasts.domain.Person; +import org.neo4j.cineasts.domain.Roles; +import org.springframework.stereotype.Component; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.Map; + +@Component +public class MovieDbJsonMapper { + + public void mapToMovie(Map data, Movie movie) { + try { + movie.setTitle((String) data.get("name")); + movie.setLanguage((String) data.get("language")); + movie.setImdbId((String) data.get("imdb_id")); + movie.setTagline((String) data.get("tagline")); + movie.setDescription(limit((String) data.get("overview"), 500)); + movie.setReleaseDate(toDate(data, "released", "yyyy-MM-dd")); + movie.setRuntime((Integer) data.get("runtime")); + movie.setHomepage((String) data.get("homepage")); + movie.setTrailer((String) data.get("trailer")); + movie.setGenre(extractFirst(data, "genres", "name")); + movie.setStudio(extractFirst(data,"studios", "name")); + movie.setVersion((Integer)data.get("version")); + movie.setLastModified(toDate(data,"last_modified_at","yyyy-MM-dd HH:mm:ss")); + movie.setImageUrl(selectImageUrl((List) data.get("posters"), "poster", "mid")); + } catch (Exception e) { + throw new MovieDbException("Failed to map json for movie", e); + } + } + + private String selectImageUrl(List data, final String type, final String size) { + if (data==null) return null; + for (Map entry : data) { + Map image = (Map) entry.get("image"); + if (image.get("type").equals(type) && image.get("size").equals(size)) return (String) image.get("url"); + } + return null; + } + + + private String extractFirst(Map data, String field, String property) { + List inner = (List) data.get(field); + if (inner == null || inner.isEmpty()) return null; + return (String) inner.get(0).get(property); + } + + private Date toDate(Map data, String field, final String pattern) throws ParseException { + String dateString = (String) data.get(field); + if (dateString == null || dateString.isEmpty()) return null; + return new SimpleDateFormat(pattern).parse(dateString); + } + + /* + [{"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. Receiving a call from an old lady trapped in her house, +they reach her building to hear horrifying screams -- which begin a long nightmare and a uniquely dramatic TV report.", +"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"}}, +{"image":{"type":"poster", +"size":"mid", +"height":708, +"width":500, +"url":"http://cf1.imgobject.com/posters/3a0/4cc8df415e73d650240003a0/rec-mid.jpg", +"id":"4cc8df415e73d650240003a0"}}, +{"image":{"type":"poster", +"size":"cover", +"height":262, +"width":185, +"url":"http://cf1.imgobject.com/posters/3a0/4cc8df415e73d650240003a0/rec-cover.jpg", +"id":"4cc8df415e73d650240003a0"}}, +{"image":{"type":"poster", +"size":"thumb", +"height":130, +"width":92, +"url":"http://cf1.imgobject.com/posters/3a0/4cc8df415e73d650240003a0/rec-thumb.jpg", +"id":"4cc8df415e73d650240003a0"}}, +{"image":{"type":"poster", +"size":"original", +"height":1000, +"width":711, +"url":"http://cf1.imgobject.com/posters/1e7/4bc92117017a3c57fe00d1e7/rec-original.jpg", +"id":"4bc92117017a3c57fe00d1e7"}}, +{"image":{"type":"poster", +"size":"mid", +"height":703, +"width":500, +"url":"http://cf1.imgobject.com/posters/1e7/4bc92117017a3c57fe00d1e7/rec-mid.jpg", +"id":"4bc92117017a3c57fe00d1e7"}}, +{"image":{"type":"poster", +"size":"cover", +"height":260, +"width":185, +"url":"http://cf1.imgobject.com/posters/1e7/4bc92117017a3c57fe00d1e7/rec-cover.jpg", +"id":"4bc92117017a3c57fe00d1e7"}}, +{"image":{"type":"poster", +"size":"thumb", +"height":129, +"width":92, +"url":"http://cf1.imgobject.com/posters/1e7/4bc92117017a3c57fe00d1e7/rec-thumb.jpg", +"id":"4bc92117017a3c57fe00d1e7"}}, +{"image":{"type":"poster", +"size":"original", +"height":1500, +"width":1000, +"url":"http://cf1.imgobject.com/posters/1ec/4bc92118017a3c57fe00d1ec/rec-original.jpg", +"id":"4bc92118017a3c57fe00d1ec"}}, +{"image":{"type":"poster", +"size":"mid", +"height":750, +"width":500, +"url":"http://cf1.imgobject.com/posters/1ec/4bc92118017a3c57fe00d1ec/rec-mid.jpg", +"id":"4bc92118017a3c57fe00d1ec"}}, +{"image":{"type":"poster", +"size":"cover", +"height":278, +"width":185, +"url":"http://cf1.imgobject.com/posters/1ec/4bc92118017a3c57fe00d1ec/rec-cover.jpg", +"id":"4bc92118017a3c57fe00d1ec"}}, +{"image":{"type":"poster", +"size":"thumb", +"height":138, +"width":92, +"url":"http://cf1.imgobject.com/posters/1ec/4bc92118017a3c57fe00d1ec/rec-thumb.jpg", +"id":"4bc92118017a3c57fe00d1ec"}}, +{"image":{"type":"poster", +"size":"original", +"height":1429, +"width":1000, +"url":"http://cf1.imgobject.com/posters/1f1/4bc92118017a3c57fe00d1f1/rec-original.jpg", +"id":"4bc92118017a3c57fe00d1f1"}}, +{"image":{"type":"poster", +"size":"mid", +"height":715, +"width":500, +"url":"http://cf1.imgobject.com/posters/1f1/4bc92118017a3c57fe00d1f1/rec-mid.jpg", +"id":"4bc92118017a3c57fe00d1f1"}}, +{"image":{"type":"poster", +"size":"cover", +"height":265, +"width":185, +"url":"http://cf1.imgobject.com/posters/1f1/4bc92118017a3c57fe00d1f1/rec-cover.jpg", +"id":"4bc92118017a3c57fe00d1f1"}}, +{"image":{"type":"poster", +"size":"thumb", +"height":132, +"width":92, +"url":"http://cf1.imgobject.com/posters/1f1/4bc92118017a3c57fe00d1f1/rec-thumb.jpg", +"id":"4bc92118017a3c57fe00d1f1"}}, +{"image":{"type":"poster", +"size":"original", +"height":1125, +"width":800, +"url":"http://cf1.imgobject.com/posters/1f6/4bc92118017a3c57fe00d1f6/rec-original.jpg", +"id":"4bc92118017a3c57fe00d1f6"}}, +{"image":{"type":"poster", +"size":"mid", +"height":703, +"width":500, +"url":"http://cf1.imgobject.com/posters/1f6/4bc92118017a3c57fe00d1f6/rec-mid.jpg", +"id":"4bc92118017a3c57fe00d1f6"}}, +{"image":{"type":"poster", +"size":"cover", +"height":260, +"width":185, +"url":"http://cf1.imgobject.com/posters/1f6/4bc92118017a3c57fe00d1f6/rec-cover.jpg", +"id":"4bc92118017a3c57fe00d1f6"}}, +{"image":{"type":"poster", +"size":"thumb", +"height":129, +"width":92, +"url":"http://cf1.imgobject.com/posters/1f6/4bc92118017a3c57fe00d1f6/rec-thumb.jpg", +"id":"4bc92118017a3c57fe00d1f6"}}], +"backdrops":[{"image":{"type":"backdrop", +"size":"original", +"height":1080, +"width":1920, +"url":"http://cf1.imgobject.com/backdrops/1df/4bc92117017a3c57fe00d1df/rec-original.jpg", +"id":"4bc92117017a3c57fe00d1df"}}, +{"image":{"type":"backdrop", +"size":"poster", +"height":439, +"width":780, +"url":"http://cf1.imgobject.com/backdrops/1df/4bc92117017a3c57fe00d1df/rec-poster.jpg", +"id":"4bc92117017a3c57fe00d1df"}}, +{"image":{"type":"backdrop", +"size":"thumb", +"height":169, +"width":300, +"url":"http://cf1.imgobject.com/backdrops/1df/4bc92117017a3c57fe00d1df/rec-thumb.jpg", +"id":"4bc92117017a3c57fe00d1df"}}, +{"image":{"type":"backdrop", +"size":"original", +"height":1080, +"width":1920, +"url":"http://cf1.imgobject.com/backdrops/1d7/4bc92113017a3c57fe00d1d7/rec-original.jpg", +"id":"4bc92113017a3c57fe00d1d7"}}, +{"image":{"type":"backdrop", +"size":"poster", +"height":439, +"width":780, +"url":"http://cf1.imgobject.com/backdrops/1d7/4bc92113017a3c57fe00d1d7/rec-poster.jpg", +"id":"4bc92113017a3c57fe00d1d7"}}, +{"image":{"type":"backdrop", +"size":"thumb", +"height":169, +"width":300, +"url":"http://cf1.imgobject.com/backdrops/1d7/4bc92113017a3c57fe00d1d7/rec-thumb.jpg", +"id":"4bc92113017a3c57fe00d1d7"}}, +{"image":{"type":"backdrop", +"size":"original", +"height":1080, +"width":1920, +"url":"http://cf1.imgobject.com/backdrops/1e3/4bc92117017a3c57fe00d1e3/rec-original.jpg", +"id":"4bc92117017a3c57fe00d1e3"}}, +{"image":{"type":"backdrop", +"size":"poster", +"height":439, +"width":780, +"url":"http://cf1.imgobject.com/backdrops/1e3/4bc92117017a3c57fe00d1e3/rec-poster.jpg", +"id":"4bc92117017a3c57fe00d1e3"}}, +{"image":{"type":"backdrop", +"size":"thumb", +"height":169, +"width":300, +"url":"http://cf1.imgobject.com/backdrops/1e3/4bc92117017a3c57fe00d1e3/rec-thumb.jpg", +"id":"4bc92117017a3c57fe00d1e3"}}, +{"image":{"type":"backdrop", +"size":"original", +"height":1080, +"width":1920, +"url":"http://cf1.imgobject.com/backdrops/1d3/4bc92113017a3c57fe00d1d3/rec-original.jpg", +"id":"4bc92113017a3c57fe00d1d3"}}, +{"image":{"type":"backdrop", +"size":"poster", +"height":439, +"width":780, +"url":"http://cf1.imgobject.com/backdrops/1d3/4bc92113017a3c57fe00d1d3/rec-poster.jpg", +"id":"4bc92113017a3c57fe00d1d3"}}, +{"image":{"type":"backdrop", +"size":"thumb", +"height":169, +"width":300, +"url":"http://cf1.imgobject.com/backdrops/1d3/4bc92113017a3c57fe00d1d3/rec-thumb.jpg", +"id":"4bc92113017a3c57fe00d1d3"}}, +{"image":{"type":"backdrop", +"size":"original", +"height":1080, +"width":1920, +"url":"http://cf1.imgobject.com/backdrops/1db/4bc92114017a3c57fe00d1db/rec-original.jpg", +"id":"4bc92114017a3c57fe00d1db"}}, +{"image":{"type":"backdrop", +"size":"poster", +"height":439, +"width":780, +"url":"http://cf1.imgobject.com/backdrops/1db/4bc92114017a3c57fe00d1db/rec-poster.jpg", +"id":"4bc92114017a3c57fe00d1db"}}, +{"image":{"type":"backdrop", +"size":"thumb", +"height":169, +"width":300, +"url":"http://cf1.imgobject.com/backdrops/1db/4bc92114017a3c57fe00d1db/rec-thumb.jpg", +"id":"4bc92114017a3c57fe00d1db"}}, +{"image":{"type":"backdrop", +"size":"original", +"height":1080, +"width":1920, +"url":"http://cf1.imgobject.com/backdrops/6f4/4d0e39c25e73d637100016f4/rec-original.jpg", +"id":"4d0e39c25e73d637100016f4"}}, +{"image":{"type":"backdrop", +"size":"poster", +"height":439, +"width":780, +"url":"http://cf1.imgobject.com/backdrops/6f4/4d0e39c25e73d637100016f4/rec-poster.jpg", +"id":"4d0e39c25e73d637100016f4"}}, +{"image":{"type":"backdrop", +"size":"thumb", +"height":169, +"width":300, +"url":"http://cf1.imgobject.com/backdrops/6f4/4d0e39c25e73d637100016f4/rec-thumb.jpg", +"id":"4d0e39c25e73d637100016f4"}}, +{"image":{"type":"backdrop", +"size":"original", +"height":1080, +"width":1920, +"url":"http://cf1.imgobject.com/backdrops/567/4d0e39dd5e73d6370a001567/rec-original.jpg", +"id":"4d0e39dd5e73d6370a001567"}}, +{"image":{"type":"backdrop", +"size":"poster", +"height":439, +"width":780, +"url":"http://cf1.imgobject.com/backdrops/567/4d0e39dd5e73d6370a001567/rec-poster.jpg", +"id":"4d0e39dd5e73d6370a001567"}}, +{"image":{"type":"backdrop", +"size":"thumb", +"height":169, +"width":300, +"url":"http://cf1.imgobject.com/backdrops/567/4d0e39dd5e73d6370a001567/rec-thumb.jpg", +"id":"4d0e39dd5e73d6370a001567"}}, +{"image":{"type":"backdrop", +"size":"original", +"height":1080, +"width":1920, +"url":"http://cf1.imgobject.com/backdrops/58a/4d0e39fb5e73d6371c00158a/rec-original.jpg", +"id":"4d0e39fb5e73d6371c00158a"}}, +{"image":{"type":"backdrop", +"size":"poster", +"height":439, +"width":780, +"url":"http://cf1.imgobject.com/backdrops/58a/4d0e39fb5e73d6371c00158a/rec-poster.jpg", +"id":"4d0e39fb5e73d6371c00158a"}}, +{"image":{"type":"backdrop", +"size":"thumb", +"height":169, +"width":300, +"url":"http://cf1.imgobject.com/backdrops/58a/4d0e39fb5e73d6371c00158a/rec-thumb.jpg", +"id":"4d0e39fb5e73d6371c00158a"}}, +{"image":{"type":"backdrop", +"size":"original", +"height":1080, +"width":1920, +"url":"http://cf1.imgobject.com/backdrops/5f4/4d0e3a185e73d6370e0015f4/rec-original.jpg", +"id":"4d0e3a185e73d6370e0015f4"}}, +{"image":{"type":"backdrop", +"size":"poster", +"height":439, +"width":780, +"url":"http://cf1.imgobject.com/backdrops/5f4/4d0e3a185e73d6370e0015f4/rec-poster.jpg", +"id":"4d0e3a185e73d6370e0015f4"}}, +{"image":{"type":"backdrop", +"size":"thumb", +"height":169, +"width":300, +"url":"http://cf1.imgobject.com/backdrops/5f4/4d0e3a185e73d6370e0015f4/rec-thumb.jpg", +"id":"4d0e3a185e73d6370e0015f4"}}, +{"image":{"type":"backdrop", +"size":"original", +"height":1080, +"width":1920, +"url":"http://cf1.imgobject.com/backdrops/67f/4d0e3a395e73d6371600167f/rec-original.jpg", +"id":"4d0e3a395e73d6371600167f"}}, +{"image":{"type":"backdrop", +"size":"poster", +"height":439, +"width":780, +"url":"http://cf1.imgobject.com/backdrops/67f/4d0e3a395e73d6371600167f/rec-poster.jpg", +"id":"4d0e3a395e73d6371600167f"}}, +{"image":{"type":"backdrop", +"size":"thumb", +"height":169, +"width":300, +"url":"http://cf1.imgobject.com/backdrops/67f/4d0e3a395e73d6371600167f/rec-thumb.jpg", +"id":"4d0e3a395e73d6371600167f"}}], +"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":"Carlos Lasarte", +"job":"Actor", +"department":"Actors", +"character":"C\u00e9sar", +"id":54519, +"order":2, +"cast_id":3, +"url":"http://www.themoviedb.org/person/54519", +"profile":"http://cf1.imgobject.com/profiles/ba6/4d27aa297b9aa134d3001ba6/carlos-lasarte-thumb.jpg"}, +{"name":"Pablo Rosso", +"job":"Actor", +"department":"Actors", +"character":"Marcos", +"id":54520, +"order":3, +"cast_id":4, +"url":"http://www.themoviedb.org/person/54520", +"profile":""}, +{"name":"David Vert", +"job":"Actor", +"department":"Actors", +"character":"Alex", +"id":54521, +"order":4, +"cast_id":5, +"url":"http://www.themoviedb.org/person/54521", +"profile":"http://cf1.imgobject.com/profiles/c7e/4d27aad57b9aa134cb001c7e/david-vert-thumb.jpg"}, +{"name":"Vicente Gil", +"job":"Actor", +"department":"Actors", +"character":"Polizist", +"id":54522, +"order":5, +"cast_id":6, +"url":"http://www.themoviedb.org/person/54522", +"profile":"http://cf1.imgobject.com/profiles/c86/4d27ab237b9aa134cb001c86/vicente-gil-thumb.jpg"}, +{"name":"Martha Carbonell", +"job":"Actor", +"department":"Actors", +"character":"Frau Izquierdo", +"id":54523, +"order":6, +"cast_id":7, +"url":"http://www.themoviedb.org/person/54523", +"profile":"http://cf1.imgobject.com/profiles/c90/4d27ab777b9aa134cb001c90/martha-carbonell-thumb.jpg"}, +{"name":"Carlos Vicente", +"job":"Actor", +"department":"Actors", +"character":"Guillem", +"id":54524, +"order":7, +"cast_id":8, +"url":"http://www.themoviedb.org/person/54524", +"profile":"http://cf1.imgobject.com/profiles/b0f/4d27abcc7b9aa134cf001b0f/carlos-vicente-thumb.jpg"}, +{"name":"Ferran Terraza", +"job":"Actor", +"department":"Actors", +"character":"Manu", +"id":54532, +"order":8, +"cast_id":22, +"url":"http://www.themoviedb.org/person/54532", +"profile":"http://cf1.imgobject.com/profiles/129/4c4bfbab7b9aa1237f000129/ferran-terraza-thumb.jpg"}, +{"name":"Jorge Yamam", +"job":"Actor", +"department":"Actors", +"character":"Sergio", +"id":147878, +"order":9, +"cast_id":23, +"url":"http://www.themoviedb.org/person/147878", +"profile":"http://cf1.imgobject.com/profiles/cbb/4d27ac677b9aa134dc001cbb/jorge-yamam-thumb.jpg"}, +{"name":"Jaume Balaguer\u00f3", +"job":"Director", +"department":"Directing", +"character":"", +"id":54525, +"order":0, +"cast_id":9, +"url":"http://www.themoviedb.org/person/54525", +"profile":""}, +{"name":"Paco Plaza", +"job":"Director", +"department":"Directing", +"character":"", +"id":54526, +"order":0, +"cast_id":10, +"url":"http://www.themoviedb.org/person/54526", +"profile":""}, +{"name":"Jaume Balaguer\u00f3", +"job":"Screenplay", +"department":"Writing", +"character":"", +"id":54525, +"order":0, +"cast_id":11, +"url":"http://www.themoviedb.org/person/54525", +"profile":""}, +{"name":"Paco Plaza", +"job":"Screenplay", +"department":"Writing", +"character":"", +"id":54526, +"order":0, +"cast_id":12, +"url":"http://www.themoviedb.org/person/54526", +"profile":""}, +{"name":"Luis Berdejo", +"job":"Screenplay", +"department":"Writing", +"character":"", +"id":54527, +"order":0, +"cast_id":13, +"url":"http://www.themoviedb.org/person/54527", +"profile":""}, +{"name":"Julio Fern\u00e1ndez", +"job":"Producer", +"department":"Production", +"character":"", +"id":17083, +"order":0, +"cast_id":14, +"url":"http://www.themoviedb.org/person/17083", +"profile":""}, +{"name":"Carlos Fern\u00e1ndez", +"job":"Executive Producer", +"department":"Production", +"character":"", +"id":37950, +"order":0, +"cast_id":15, +"url":"http://www.themoviedb.org/person/37950", +"profile":""}, +{"name":"Alberto Marini", +"job":"Producer", +"department":"Production", +"character":"", +"id":54528, +"order":0, +"cast_id":16, +"url":"http://www.themoviedb.org/person/54528", +"profile":""}, +{"name":"Pablo Rosso", +"job":"Director of Photography", +"department":"Camera", +"character":"", +"id":54520, +"order":0, +"cast_id":17, +"url":"http://www.themoviedb.org/person/54520", +"profile":""}, +{"name":"Gemma Fauria", +"job":"Set Designer", +"department":"Art", +"character":"", +"id":54529, +"order":0, +"cast_id":18, +"url":"http://www.themoviedb.org/person/54529", +"profile":""}, +{"name":"David Gallart", +"job":"Editor", +"department":"Editing", +"character":"", +"id":28500, +"order":0, +"cast_id":19, +"url":"http://www.themoviedb.org/person/28500", +"profile":""}, +{"name":"Xavier Mas", +"job":"Sound Designer", +"department":"Sound", +"character":"", +"id":54530, +"order":0, +"cast_id":20, +"url":"http://www.themoviedb.org/person/54530", +"profile":""}, +{"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"}] + */ + + public void mapToPerson(Map data, Person person) { + try { + person.setName((String) data.get("name")); + person.setBirthday(toDate(data, "birthday", "yyyy-MM-dd")); + person.setBirthplace((String) data.get("birthplace")); + String biography = (String) data.get("biography"); + person.setBiography(limit(biography,500)); + person.setVersion((Integer) data.get("version")); + person.setProfileImageUrl(selectImageUrl((List) data.get("profile"), "profile", "profile")); + person.setLastModified(toDate(data,"last_modified_at","yyyy-MM-dd HH:mm:ss")); + } catch (Exception e) { + throw new MovieDbException("Failed to map json for person", e); + } + } + + private String limit(String text, int limit) { + if (text==null || text.length() < limit) return text; + return text.substring(0,limit); + } + +/* + [{"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"}, +{"name":"Arizona Days (1937)", +"id":22367, +"job":"Actor", +"department":"Actors", +"character":"Henchman Pete ", +"cast_id":12, +"url":"http://www.themoviedb.org/movie/22367", +"poster":"", +"adult":false, +"release":"1937-01-30"}, +{"name":"House of Dracula", +"id":30793, +"job":"Actor", +"department":"Actors", +"character":"The Frankenstein Monster", +"cast_id":3, +"url":"http://www.themoviedb.org/movie/30793", +"poster":"http://cf1.imgobject.com/posters/7cf/4bc97660017a3c57fe0367cf/house-of-dracula-cover.jpg", +"adult":false, +"release":"1945-01-01"}, +{"name":"The Fargo Kid", +"id":38704, +"job":"Actor", +"department":"Actors", +"character":"Sheriff Dave", +"cast_id":24, +"url":"http://www.themoviedb.org/movie/38704", +"poster":"http://cf1.imgobject.com/posters/04e/4d74018a5e73d6021d00004e/the-fargo-kid-cover.jpg", +"adult":false, +"release":"1940-12-06"}], +"profile":[], +"version":19, +"last_modified_at":"2011-03-07 13:02:35"}] +*/ + + + public Roles mapToRole(String roleString) { + if (roleString.equals("Actor")) { + return Roles.ACTS_IN; + } + if (roleString.equals("Director")) { + return Roles.DIRECTED; + } + return null; + } +} diff --git a/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/movieimport/MovieDbLocalStorage.java b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/movieimport/MovieDbLocalStorage.java new file mode 100644 index 000000000..199c47986 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/movieimport/MovieDbLocalStorage.java @@ -0,0 +1,95 @@ +package org.neo4j.cineasts.movieimport; + +import org.codehaus.jackson.map.ObjectMapper; + +import java.io.*; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class MovieDbLocalStorage { + + private String storagePath; + protected ObjectMapper mapper; + + public MovieDbLocalStorage(String storagePath) { + this.storagePath = storagePath; + ensureStorageDirectoryExists(); + mapper = new ObjectMapper(); + } + + private void ensureStorageDirectoryExists() { + File storageDirectory = new File(storagePath); + if (!storageDirectory.isDirectory()) { + if (!storageDirectory.mkdirs()) { + throw new RuntimeException("Failed to create storage dir"); + } + } + } + + public boolean hasMovie(String movieId) { + return fileForMovie(movieId).exists(); + } + + private File fileForMovie(String movieId) { + return new File(storagePath, String.format("movie_%s.json", movieId)); + } + + public Map loadMovie(String movieId) { + File storageFile = fileForMovie(movieId); + return loadJsonValue(storageFile); + } + + private Map loadJsonValue(File storageFile) { + try { + final Object value = mapper.readValue(storageFile, Object.class); + if (value instanceof List) { + List list = (List) value; + if (list.isEmpty() || list.get(0).equals("Nothing found.")) return Collections.singletonMap("not_found", System.currentTimeMillis()); + return asMap(list.get(0)); + } + return asMap(value); + } catch (Exception e) { + throw new MovieDbException("Failed to load JSON from storage for file " + storageFile.getPath(), e); + } + } + + private Map asMap(Object value) { + if (value instanceof Map) { + return (Map) value; + } + final String typeInformation = value == null ? "null" : value.getClass().getSimpleName(); + throw new MovieDbException("Wrong movie data format, expected Map/JSON-Object but was "+ typeInformation); + } + + public void storeMovie(String movieId, Object movieData) { + File storageFile = fileForMovie(movieId); + storeJsonValue(movieData, storageFile); + } + + private void storeJsonValue(Object jsonData, File storageFile) { + try { + mapper.writeValue(storageFile,jsonData); + } catch (Exception e) { + throw new MovieDbException("Failed to store JSON to storage for file " + storageFile.getPath(), e); + } + } + + public boolean hasPerson(String personId) { + return fileForPerson(personId).exists(); + } + + private File fileForPerson(String personId) { + return new File(storagePath, String.format("person_%s.json", personId)); + } + + public Map loadPerson(String personId) { + File storageFile = fileForPerson(personId); + return loadJsonValue(storageFile); + } + + public void storePerson(String personId, Object personJson) { + File storageFile = fileForPerson(personId); + storeJsonValue(personJson, storageFile); + } +} diff --git a/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/movieimport/MovieImporter.java b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/movieimport/MovieImporter.java new file mode 100644 index 000000000..0fa2c0fe5 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/movieimport/MovieImporter.java @@ -0,0 +1,50 @@ +package org.neo4j.cineasts.movieimport; + +import org.springframework.context.support.FileSystemXmlApplicationContext; + +import java.util.Collections; +import java.util.Map; + +/** + * @author mh + * @since 04.10.11 + */ +public class MovieImporter { + + private final MovieDbImportService importer; + + public static void main(String[] args) { + final FileSystemXmlApplicationContext ctx = new FileSystemXmlApplicationContext("src/main/webapp/WEB-INF/applicationContext.xml"); + try { + final MovieDbImportService importer = ctx.getBean(MovieDbImportService.class); + final MovieImporter movieImporter = new MovieImporter(importer); + movieImporter.runImport(getMovieIdsToImport(args)); + } finally { + ctx.close(); + } + } + + public MovieImporter(MovieDbImportService importer) { + this.importer = importer; + } + + private void runImport(Map movieIdsToImport) { + final long start = System.currentTimeMillis(); + final Map result = importer.importMovies(movieIdsToImport); + final long time = System.currentTimeMillis() - start; + for (Map.Entry movie : result.entrySet()) { + System.out.println(movie.getKey() + "\t" + movie.getValue()); + } + System.out.println("Imported movies took "+ time+" ms."); + } + + private static Map getMovieIdsToImport(String[] args) { + if (args.length == 0) { + throw new IllegalArgumentException("Usage: MovieImporter 1 10000\nWorking Directory should be the cineasts directory with the json files in data/json."); + } + if (args.length == 1) { + return Collections.singletonMap(Integer.valueOf(args[0]), Integer.valueOf(args[0])); + } + return Collections.singletonMap(Integer.valueOf(args[0]), Integer.valueOf(args[1])); + } +} diff --git a/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/repository/MovieRepository.java b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/repository/MovieRepository.java new file mode 100644 index 000000000..824479306 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/repository/MovieRepository.java @@ -0,0 +1,31 @@ +package org.neo4j.cineasts.repository; + +import org.neo4j.cineasts.domain.Movie; +import org.neo4j.cineasts.domain.MovieRecommendation; +import org.neo4j.cineasts.domain.User; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.neo4j.annotation.Query; +import org.springframework.data.neo4j.repository.GraphRepository; +import org.springframework.data.neo4j.repository.NamedIndexRepository; + +import java.util.List; + +/** + * @author mh + * @since 02.04.11 + */ +public interface MovieRepository extends GraphRepository, NamedIndexRepository { + Movie findById(String id); + + Page findByTitleLike(String title, Pageable page); + + @Query("start user=node({_0}) " + + " match user-[r:RATED]->movie<-[r2:RATED]-other-[r3:RATED]->otherMovie " + + " where r.stars >= 3 and r2.stars >= 3 and r3.stars >= 3 " + + " return otherMovie, avg(r3.stars) " + + " order by avg(r3.stars) desc, count(*) desc" + + " limit 10") + List getRecommendations(User user); + +} diff --git a/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/repository/PersonRepository.java b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/repository/PersonRepository.java new file mode 100644 index 000000000..65fbc3b2c --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/repository/PersonRepository.java @@ -0,0 +1,12 @@ +package org.neo4j.cineasts.repository; + +import org.neo4j.cineasts.domain.Person; +import org.springframework.data.neo4j.repository.GraphRepository; + +/** + * @author mh + * @since 02.04.11 + */ +public interface PersonRepository extends GraphRepository { + Person findById(String id); +} diff --git a/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/repository/UserRepository.java b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/repository/UserRepository.java new file mode 100644 index 000000000..d879532be --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/repository/UserRepository.java @@ -0,0 +1,11 @@ +package org.neo4j.cineasts.repository; + +import org.neo4j.cineasts.domain.User; +import org.springframework.data.neo4j.repository.GraphRepository; + +/** + * @author mh + * @since 02.04.11 + */ +public interface UserRepository extends GraphRepository { +} diff --git a/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/service/CineastsRepository.java b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/service/CineastsRepository.java new file mode 100644 index 000000000..da2933071 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/service/CineastsRepository.java @@ -0,0 +1,19 @@ +package org.neo4j.cineasts.service; + +import org.neo4j.cineasts.repository.MovieRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +/** + * @author mh + * @since 04.03.11 + */ +@Repository +@Transactional +public class CineastsRepository { + + @Autowired private MovieRepository movieRepository; + + +} diff --git a/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/service/CineastsUserDetails.java b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/service/CineastsUserDetails.java new file mode 100644 index 000000000..0bad6f7c3 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/service/CineastsUserDetails.java @@ -0,0 +1,62 @@ +package org.neo4j.cineasts.service; + +import org.neo4j.cineasts.domain.User; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +/** +* @author mh +* @since 07.03.11 +*/ +public class CineastsUserDetails implements UserDetails { + private final User user; + + public CineastsUserDetails(User user) { + this.user = user; + } + + @Override + public Collection getAuthorities() { + User.Roles[] roles = user.getRole(); + if (roles ==null) return Collections.emptyList(); + return Arrays.asList(roles); + } + + @Override + public String getPassword() { + return user.getPassword(); + } + + @Override + public String getUsername() { + return user.getLogin(); + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + + public User getUser() { + return user; + } +} diff --git a/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/service/CineastsUserDetailsService.java b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/service/CineastsUserDetailsService.java new file mode 100644 index 000000000..3f4e81a67 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/service/CineastsUserDetailsService.java @@ -0,0 +1,77 @@ +package org.neo4j.cineasts.service; + +import org.neo4j.cineasts.domain.User; +import org.neo4j.cineasts.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * @author mh + * @since 06.03.11 + */ +@Service +public class CineastsUserDetailsService implements UserDetailsService { + + @Autowired + private UserRepository userRepository; + + @Override + public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException, DataAccessException { + final User user = findUser(login); + if (user==null) throw new UsernameNotFoundException("Username not found",login); + return new CineastsUserDetails(user); + } + + public User findUser(String login) { + return userRepository.findByPropertyValue("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; + } + + @Transactional + public User register(String login, String name, String password) { + User found = findUser(login); + if (found!=null) throw new RuntimeException("Login already taken: "+login); + if (name==null || name.isEmpty()) throw new RuntimeException("No name provided."); + if (password==null || password.isEmpty()) throw new RuntimeException("No password provided."); + User user=userRepository.save(new User(login,name,password,User.Roles.ROLE_USER)); + setUserInSession(user); + return user; + } + + private void setUserInSession(User user) { + SecurityContext context = SecurityContextHolder.getContext(); + CineastsUserDetails userDetails = new CineastsUserDetails(user); + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, user.getPassword(),userDetails.getAuthorities()); + context.setAuthentication(authentication); + + } + + @Transactional + public void addFriend(String login) { + User friend = findUser(login); + User user = getUserFromSession(); + if (!user.equals(friend)) { + user.addFriend(friend); + } + } +} diff --git a/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/service/DatabasePopulator.java b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/service/DatabasePopulator.java new file mode 100644 index 000000000..00740c8b1 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/service/DatabasePopulator.java @@ -0,0 +1,55 @@ +package org.neo4j.cineasts.service; + +import org.neo4j.cineasts.repository.MovieRepository; +import org.neo4j.kernel.AbstractGraphDatabase; +import org.neo4j.cineasts.domain.*; +import org.neo4j.cineasts.movieimport.MovieDbImportService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.neo4j.support.Neo4jTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; + +import static java.util.Arrays.asList; + +/** + * @author mh + * @since 04.03.11 + */ +@Service +public class DatabasePopulator { + + @Autowired + Neo4jTemplate ctx; + @Autowired + MovieRepository movieRepository; + + @Autowired + MovieDbImportService importService; + private final static Logger log = LoggerFactory.getLogger(DatabasePopulator.class); + + @Transactional + public List populateDatabase() { + User me = new User("micha", "Micha", "password", User.Roles.ROLE_ADMIN,User.Roles.ROLE_USER).persist(); + User ollie = new User("ollie", "Olliver", "password",User.Roles.ROLE_USER).persist(); + me.addFriend(ollie); + List ids = asList(19995 , 194, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 13, 20526, 11, 1893, 1892, 1894, 168, 193, 200, 157, 152, 201, 154, 12155, 58, 285, 118, 22, 392, 5255, 568, 9800, 497, 101, 120, 121, 122); + List result=new ArrayList(ids.size()); + for (Integer id : ids) { + result.add(importService.importMovie(String.valueOf(id))); + } + + //me.rate(repository.getMovie("13"),5,"Inspiring"); + me.rate(movieRepository.findById("603"),5,"Best of the series"); + return result; + } + + @Transactional + public void cleanDb() { + new Neo4jDatabaseCleaner((AbstractGraphDatabase) ctx.getGraphDatabaseService()).cleanDb(); + } +} diff --git a/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/service/FriendsMovieRecommendations.java b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/service/FriendsMovieRecommendations.java new file mode 100644 index 000000000..9f63b5b01 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/service/FriendsMovieRecommendations.java @@ -0,0 +1,74 @@ +package org.neo4j.cineasts.service; + +import org.neo4j.cineasts.domain.Movie; +import org.neo4j.cineasts.domain.Rating; +import org.neo4j.cineasts.domain.User; +import org.neo4j.graphdb.traversal.Evaluators; +import org.neo4j.graphdb.traversal.TraversalDescription; +import org.neo4j.kernel.Traversal; +import org.neo4j.kernel.Uniqueness; +import org.springframework.data.neo4j.core.EntityPath; + +import java.util.HashMap; +import java.util.Map; + +import static org.neo4j.graphdb.DynamicRelationshipType.withName; + +/** + * @author mh + * @since 04.04.11 + */ +public class FriendsMovieRecommendations { + private final Map ratings = new HashMap(); + private final int ratingDistance; + + public static class MovieRating { + int stars; + int count; + public void update(Rating rating, int weight) { + this.stars += rating.getStars()*weight; + this.count += weight; + } + public int average() { + return count == 0 ? 0 : stars / count; + } + public String toString() { + return String.valueOf(average()); + } + + } + public FriendsMovieRecommendations(int ratingDistance) { + this.ratingDistance = ratingDistance; + } + + public Map getRecommendationsFor(User user) { + TraversalDescription traversal = Traversal.description().breadthFirst() + .uniqueness(Uniqueness.NODE_GLOBAL).relationships(withName(User.FRIEND)) + .evaluator(Evaluators.toDepth(ratingDistance)).evaluator(Evaluators.excludeStartPosition()); + + Iterable result = user.findAllPathsByTraversal(traversal); + Iterable> friends = (Iterable>)result; + for (EntityPath path : friends) { + int weight = ratingDistance - path.length(); + User friend = path.endEntity(); + aggregateRatings(friend,weight); + } + for (Rating rating : user.getRatings()) { + ratings.remove(rating.getMovie()); + } + return ratings; + } + + private void aggregateRatings(User friend, int weight) { + for (Rating rating : friend.getRatings()) { + obtainRating(rating.getMovie()).update(rating, weight); + } + } + + private MovieRating obtainRating(Movie movie) { + if (!ratings.containsKey(movie)) { + ratings.put(movie, new MovieRating()); + } + return ratings.get(movie); + } +} diff --git a/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/service/MovieRecommendations.java b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/service/MovieRecommendations.java new file mode 100644 index 000000000..ac262f5de --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/service/MovieRecommendations.java @@ -0,0 +1,82 @@ +package org.neo4j.cineasts.service; + +import org.neo4j.cineasts.domain.Movie; +import org.neo4j.cineasts.domain.User; +import org.neo4j.cineasts.repository.MovieRepository; +import org.neo4j.graphdb.Path; +import org.neo4j.graphdb.Relationship; +import org.neo4j.graphdb.traversal.Evaluation; +import org.neo4j.graphdb.traversal.Evaluator; +import org.neo4j.graphdb.traversal.TraversalDescription; +import org.neo4j.kernel.Traversal; +import org.neo4j.kernel.Uniqueness; + +import java.util.HashMap; +import java.util.Map; + +import static org.neo4j.graphdb.Direction.OUTGOING; +import static org.neo4j.graphdb.DynamicRelationshipType.withName; + +/** +* @author mh +* @since 04.04.11 +*/ +class MovieRecommendations implements Evaluator { + private final Map ratings=new HashMap(); + private final int ratingDistance; + private MovieRepository movieRepository; + + public MovieRecommendations(MovieRepository movieRepository, int ratingDistance) { + this.movieRepository = movieRepository; + this.ratingDistance = ratingDistance; + } + + public Map getRecommendationsFor(User user) { + TraversalDescription traversal= Traversal.description().breadthFirst() + .uniqueness(Uniqueness.NODE_PATH) + .relationships(withName(User.FRIEND)) + .relationships(withName(User.RATED), OUTGOING) + .evaluator(this); + return averageRecommendations(user, traversal); + } + + private Map averageRecommendations(User user, TraversalDescription traversal) { // todo sort, limit + Map result=new HashMap(); + for (Movie movie : movieRepository.findAllByTraversal(user, traversal)) { + final int[] rating = ratings.get(movie.getNodeId()); + result.put(movie, rating[1]==0 ? 0 : rating[0]/rating[1]); + } + return result; + } + + @Override + public Evaluation evaluate(Path path) { + final int distance = path.length() - 1; + if (distance > ratingDistance) return Evaluation.EXCLUDE_AND_PRUNE; + Relationship rated = path.lastRelationship(); + if (rated != null && rated.getType().name().equals(User.RATED)) { + if (distance == 0) return Evaluation.EXCLUDE_AND_PRUNE; // my rated movies + updateRating(rated, distance); + return Evaluation.INCLUDE_AND_PRUNE; + } + System.out.println(); + return Evaluation.EXCLUDE_AND_CONTINUE; + } + + private void updateRating(Relationship rated, int distance) { + final long movieId = rated.getEndNode().getId(); + int[] rating = obtainRating(movieId); + + int weight = ratingDistance - distance; + final Integer stars = (Integer) rated.getProperty("stars", 0); + rating[0] += weight * stars; + rating[1] += weight; + } + + private int[] obtainRating(long movieId) { + if (!ratings.containsKey(movieId)) { + ratings.put(movieId,new int[2]); + } + return ratings.get(movieId); + } +} diff --git a/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/service/Neo4jDatabaseCleaner.java b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/service/Neo4jDatabaseCleaner.java new file mode 100644 index 000000000..15d995459 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/java/org/neo4j/cineasts/service/Neo4jDatabaseCleaner.java @@ -0,0 +1,67 @@ +package org.neo4j.cineasts.service; + +import org.neo4j.graphdb.Direction; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Relationship; +import org.neo4j.graphdb.Transaction; +import org.neo4j.graphdb.index.IndexManager; +import org.neo4j.kernel.AbstractGraphDatabase; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * @author mh + * @since 02.03.11 + */ +public class Neo4jDatabaseCleaner { + private AbstractGraphDatabase graph; + + public Neo4jDatabaseCleaner(AbstractGraphDatabase graph) { + this.graph = graph; + } + + public Map cleanDb() { + Map result = new HashMap(); + Transaction tx = graph.beginTx(); + try { + removeNodes(result); + clearIndex(result); + tx.success(); + } finally { + tx.finish(); + } + return result; + } + + private void removeNodes(Map result) { + Node refNode = graph.getReferenceNode(); + int nodes = 0, relationships = 0; + for (Node node : graph.getAllNodes()) { + for (Relationship rel : node.getRelationships(Direction.OUTGOING)) { + rel.delete(); + relationships++; + } + if (!refNode.equals(node)) { + node.delete(); + nodes++; + } + } + result.put("nodes", nodes); + result.put("relationships", relationships); + + } + + private void clearIndex(Map result) { + IndexManager indexManager = graph.index(); + result.put("node-indexes", Arrays.asList(indexManager.nodeIndexNames())); + result.put("relationship-indexes", Arrays.asList(indexManager.relationshipIndexNames())); + for (String ix : indexManager.nodeIndexNames()) { + indexManager.forNodes(ix).delete(); + } + for (String ix : indexManager.relationshipIndexNames()) { + indexManager.forRelationships(ix).delete(); + } + } +} diff --git a/spring-data-neo4j-examples/cineasts/src/main/resources/log4j.properties b/spring-data-neo4j-examples/cineasts/src/main/resources/log4j.properties new file mode 100644 index 000000000..37867140a --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/resources/log4j.properties @@ -0,0 +1,7 @@ +log4j.rootLogger=warn, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout + +#log4j.category.org.springframework.web=DEBUG + diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/applicatioContext-security.xml b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/applicatioContext-security.xml new file mode 100644 index 000000000..9f1321d68 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/applicatioContext-security.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/applicationContext.xml b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/applicationContext.xml new file mode 100644 index 000000000..bff684ee6 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/applicationContext.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/decorators.xml b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/decorators.xml new file mode 100644 index 000000000..1dab375ce --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/decorators.xml @@ -0,0 +1,8 @@ + + + / + + + /* + + \ No newline at end of file diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/dispatcherServlet-servlet.xml b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/dispatcherServlet-servlet.xml new file mode 100644 index 000000000..2f428fc98 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/dispatcherServlet-servlet.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/actors/show.jsp b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/actors/show.jsp new file mode 100644 index 000000000..fffb8c52e --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/actors/show.jsp @@ -0,0 +1,36 @@ +<%@ page session="false" %> +<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> +<%--@elvariable id="actor" type="org.neo4j.cineasts.domain.Actor"--%> +<%--@elvariable id="role" type="org.neo4j.cineasts.domain.Role"--%> + + +
+
+
+
+

${actor.name}

+

Born in ${actor.birthplace}.

+
+
+
+
+

Biography

+

${actor.biography}

+

Roles

+ + + +
+
+ +

Actor cannot be found.

+
+
diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/auth/deniedpage.jsp b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/auth/deniedpage.jsp new file mode 100644 index 000000000..7057a169d --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/auth/deniedpage.jsp @@ -0,0 +1,7 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + + Access denied + + Unfortunately you are not authorized to see this page. + diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/auth/loginpage.jsp b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/auth/loginpage.jsp new file mode 100644 index 000000000..4ac9ab507 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/auth/loginpage.jsp @@ -0,0 +1,36 @@ +<%@ page import="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %> +<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %> + +<%@ page language="java" contentType="text/html; charset=UTF-8" +pageEncoding="UTF-8"%> + + + + + Login + + + +

Login

+
${error}
+ +
+

+ + +

+

+ + +

+

+ Remember me +

+ +

+ Register + + diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/auth/registerpage.jsp b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/auth/registerpage.jsp new file mode 100644 index 000000000..4a9c2d6e0 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/auth/registerpage.jsp @@ -0,0 +1,37 @@ +<%@ page import="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter" %> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %> +<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %> + +<%@ page language="java" contentType="text/html; charset=UTF-8" +pageEncoding="UTF-8"%> + + + + + Login + + + +

Login

+
${error}
+ +
+

+ + +

+

+ + +

+

+ + +

+ +
+
+ Login + + diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/decorators/frontpage.jsp b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/decorators/frontpage.jsp new file mode 100644 index 000000000..4d82b031f --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/decorators/frontpage.jsp @@ -0,0 +1,30 @@ +<%@ taglib uri="http://www.opensymphony.com/sitemesh/decorator" + prefix="decorator" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> + + + + + Cineasts.net - <decorator:title default="Welcome" /> + + + <%@ include file="/WEB-INF/views/includes/style.jsp" %> + <%@ include file="/WEB-INF/views/includes/js.jsp" %> + + + +
+ +
+ <%@ include file="/WEB-INF/views/includes/footer.jsp" %> + + diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/decorators/main.jsp b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/decorators/main.jsp new file mode 100644 index 000000000..96d6d65c6 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/decorators/main.jsp @@ -0,0 +1,31 @@ +<%@ taglib uri="http://www.opensymphony.com/sitemesh/decorator" + prefix="decorator" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> + + + + + Cineasts.net - <decorator:title default="Welcome" /> + + + <%@ include file="/WEB-INF/views/includes/style.jsp" %> + <%@ include file="/WEB-INF/views/includes/js.jsp" %> + + + +
+ +
+ <%@ include file="/WEB-INF/views/includes/footer.jsp" %> + + \ No newline at end of file diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/import/result.jsp b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/import/result.jsp new file mode 100644 index 000000000..5c9f63b43 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/import/result.jsp @@ -0,0 +1,15 @@ +<%@ page session="false" %> +<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> + +

Results for importing movies: "${ids}"

+ +

Imported of ${fn:length(movies)} movies took ${duration} seconds.

+ +
+ +
${entry.key}
+
${entry.value}
+
+
diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/includes/footer.jsp b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/includes/footer.jsp new file mode 100644 index 000000000..57861b0d0 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/includes/footer.jsp @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/includes/header-frontpage.jsp b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/includes/header-frontpage.jsp new file mode 100644 index 000000000..c851575ab --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/includes/header-frontpage.jsp @@ -0,0 +1,3 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" session="false" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/includes/header.jsp b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/includes/header.jsp new file mode 100644 index 000000000..c851575ab --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/includes/header.jsp @@ -0,0 +1,3 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" session="false" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/includes/header_login.jsp b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/includes/header_login.jsp new file mode 100644 index 000000000..39d5c4d3e --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/includes/header_login.jsp @@ -0,0 +1,24 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" session="false" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/includes/js.jsp b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/includes/js.jsp new file mode 100644 index 000000000..e69de29bb diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/includes/links.jsp b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/includes/links.jsp new file mode 100644 index 000000000..e69de29bb diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/includes/navigation.jsp b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/includes/navigation.jsp new file mode 100644 index 000000000..484da2378 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/includes/navigation.jsp @@ -0,0 +1,15 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" session="false" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/includes/search.jsp b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/includes/search.jsp new file mode 100644 index 000000000..0aebd4917 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/includes/search.jsp @@ -0,0 +1,5 @@ + diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/includes/style.jsp b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/includes/style.jsp new file mode 100644 index 000000000..1d77d9f54 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/includes/style.jsp @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/index.jsp b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/index.jsp new file mode 100644 index 000000000..049b8fe23 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/index.jsp @@ -0,0 +1,11 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" session="false" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +
+

Welcome to Cineasts.net

+
+
+ + +
+
+
\ No newline at end of file diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/movies/list.jsp b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/movies/list.jsp new file mode 100644 index 000000000..0aabbaaf0 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/movies/list.jsp @@ -0,0 +1,26 @@ +<%@ page session="false" %> +<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + +

Results for "${query}"

+ + + + + + +

No movies found for query "${query}".

+
+
diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/movies/show.jsp b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/movies/show.jsp new file mode 100644 index 000000000..9e47500bc --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/movies/show.jsp @@ -0,0 +1,170 @@ +<%@ page session="false" %> +<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> + + +${movie.title} + + + +
+
+ <%--@elvariable id="movie" type="org.neo4j.cineasts.domain.Movie"--%> + + +
"/>
+
+

${movie.title} (${movie.year}) ${stars} stars

+
+

<${movie.tagline}

+
+
+ +
+

Movie facts

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Language${movie.language}
Runtime${movie.runtime} Minutes
Genre${movie.genre}
Other sitesTheMovieDb.org | + IMDb +
Buy DVDs & Books">Amazon | + ">CineButler +
In Cinemas">Google Movies +
+
+ +
+ +

Trailers

+ + + + + + + + + +
+
+ +
+

Release info

+ +
    +
  • + + + + + + + + + + + + + + + +
    Released
    Homepage
    Studio${movie.studio}
    +
  • +
+
+ +
+
+
+
+

Overview

+

${movie.description}

+ +

Cast

+ + +
+
+ +

Reviews

+ + +
"> +

Give + + + + to "${movie.title}" saying: + + + +

+
+ +
+ + + +
+
+
+
+ +

No movie found

+

The movie you were looking for could not be found.

+
+
+ diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/user/index.jsp b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/user/index.jsp new file mode 100644 index 000000000..9a852cc1e --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/user/index.jsp @@ -0,0 +1,104 @@ +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%--@elvariable id="user" type="org.neo4j.cineasts.domain.User"--%> +<%--@elvariable id="recommendations" type="java.util.List"--%> +<%--@elvariable id="recommendation" type="org.neo4j.cineasts.domain.MovieRecommendation"--%> + + + Profile + + + +
+
+
" />
+
+

Hi ${user.name}!

+

${user.info}

+
+
+
+ + + +

Your friends

+
    + +
  • + ">" /> +
    +

    ">

    +

    ${friend.info}

    +
    +
    +
  • +
    +
+
+ +

You don't have any friends yet

+
+
+
+
+
+
+

Your rated movies

+
+ +
+

${fn:length(ratings)}

+
+ +
+ +
+

Your recommendations

+
+
+

${fn:length(recommendations)}

+
+ +
+
+
+ + + diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/user/public.jsp b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/user/public.jsp new file mode 100644 index 000000000..9b46bae21 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/views/user/public.jsp @@ -0,0 +1,86 @@ +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> + +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%--@elvariable id="user" type="org.neo4j.cineasts.domain.User"--%> + + + Profile for ${name} + + +
+
+
" />
+
+

${name}

+

${profiled.info}

+
+
+
+ +

${name}'s friends

+ + + + + +
    + +
  • + ">" /> +
    +

    ">

    +

    ${friend.info}

    +
    +
    +
  • +
    +
+
+ + ${name} has no friends. + +
+
+ +
+ Add ${name} as a friend. +
+
+
+
+ +
+
+
+

${name}'s rated movies

+
+
+

${fn:length(ratings)}

+
+ + + +
+
+
+ + diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/web.xml b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..f2420a9a0 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,58 @@ + + + + + contextConfigLocation + + /WEB-INF/applicatioContext-security.xml + /WEB-INF/applicationContext.xml + + + + + org.springframework.web.context.ContextLoaderListener + + + + sitemesh + + com.opensymphony.module.sitemesh.filter.PageFilter + + + + + springSecurityFilterChain + org.springframework.web.filter.DelegatingFilterProxy + + + + springSecurityFilterChain + /* + + + + sitemesh + /* + + + + dispatcherServlet + org.springframework.web.servlet.DispatcherServlet + 1 + + + + dispatcherServlet + / + + + + index.jsp + + + + 60 + + diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/images/actor-picture-frame.png b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/actor-picture-frame.png new file mode 100644 index 000000000..cb58bc5a9 Binary files /dev/null and b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/actor-picture-frame.png differ diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/images/cineasts.png b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/cineasts.png new file mode 100644 index 000000000..e7778456b Binary files /dev/null and b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/cineasts.png differ diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/images/filmroll-horizontal.png b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/filmroll-horizontal.png new file mode 100644 index 000000000..e4d71d2c2 Binary files /dev/null and b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/filmroll-horizontal.png differ diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/images/filmroll-vertical-full.png b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/filmroll-vertical-full.png new file mode 100644 index 000000000..3270bbd07 Binary files /dev/null and b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/filmroll-vertical-full.png differ diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/images/header-curtain.png b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/header-curtain.png new file mode 100644 index 000000000..5b23f8ec7 Binary files /dev/null and b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/header-curtain.png differ diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/images/header-splash-bg.png b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/header-splash-bg.png new file mode 100644 index 000000000..e4f78a20a Binary files /dev/null and b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/header-splash-bg.png differ diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/images/logo-splash.png b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/logo-splash.png new file mode 100644 index 000000000..2d1815a1e Binary files /dev/null and b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/logo-splash.png differ diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/images/logo.png b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/logo.png new file mode 100644 index 000000000..98fbfde7c Binary files /dev/null and b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/logo.png differ diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/images/logo/neo4j.png b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/logo/neo4j.png new file mode 100644 index 000000000..c96c298ed Binary files /dev/null and b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/logo/neo4j.png differ diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/images/logo/springdata.jpg b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/logo/springdata.jpg new file mode 100644 index 000000000..a45ae83da Binary files /dev/null and b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/logo/springdata.jpg differ diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/images/logo/springdatagraph.png b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/logo/springdatagraph.png new file mode 100644 index 000000000..df46c007f Binary files /dev/null and b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/logo/springdatagraph.png differ diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/images/logo/springsource.png b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/logo/springsource.png new file mode 100644 index 000000000..e573cde33 Binary files /dev/null and b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/logo/springsource.png differ diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/images/movie-placeholder.png b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/movie-placeholder.png new file mode 100644 index 000000000..cc73fce08 Binary files /dev/null and b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/movie-placeholder.png differ diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/images/neo4j.png b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/neo4j.png new file mode 100644 index 000000000..ee5cac71c Binary files /dev/null and b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/neo4j.png differ diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/images/profile-placeholder-small.png b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/profile-placeholder-small.png new file mode 100644 index 000000000..44dc7e19f Binary files /dev/null and b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/profile-placeholder-small.png differ diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/images/profile-placeholder.png b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/profile-placeholder.png new file mode 100644 index 000000000..66ff4f73d Binary files /dev/null and b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/profile-placeholder.png differ diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/images/rated_0.png b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/rated_0.png new file mode 100644 index 000000000..bf5766366 Binary files /dev/null and b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/rated_0.png differ diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/images/rated_1.png b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/rated_1.png new file mode 100644 index 000000000..48730c132 Binary files /dev/null and b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/rated_1.png differ diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/images/rated_2.png b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/rated_2.png new file mode 100644 index 000000000..b91aa3345 Binary files /dev/null and b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/rated_2.png differ diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/images/rated_3.png b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/rated_3.png new file mode 100644 index 000000000..fe3eb1757 Binary files /dev/null and b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/rated_3.png differ diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/images/rated_4.png b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/rated_4.png new file mode 100644 index 000000000..2e55bb019 Binary files /dev/null and b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/rated_4.png differ diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/images/rated_5.png b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/rated_5.png new file mode 100644 index 000000000..faddfa4a1 Binary files /dev/null and b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/rated_5.png differ diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/images/rating-active.png b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/rating-active.png new file mode 100644 index 000000000..50b708b33 Binary files /dev/null and b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/rating-active.png differ diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/images/rating-disabled.png b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/rating-disabled.png new file mode 100644 index 000000000..32cfa2c85 Binary files /dev/null and b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/rating-disabled.png differ diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/images/searchbar-big-searchicon.png b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/searchbar-big-searchicon.png new file mode 100644 index 000000000..eca23c8e7 Binary files /dev/null and b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/searchbar-big-searchicon.png differ diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/images/searchbar-big.png b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/searchbar-big.png new file mode 100644 index 000000000..faf80be44 Binary files /dev/null and b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/searchbar-big.png differ diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/images/springdata.jpg b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/springdata.jpg new file mode 100644 index 000000000..90f4de574 Binary files /dev/null and b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/springdata.jpg differ diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/images/springdatagraph.png b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/springdatagraph.png new file mode 100644 index 000000000..1718b3475 Binary files /dev/null and b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/springdatagraph.png differ diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/images/springsource.png b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/springsource.png new file mode 100644 index 000000000..36b42b001 Binary files /dev/null and b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/springsource.png differ diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/images/star.png b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/star.png new file mode 100644 index 000000000..983454b95 Binary files /dev/null and b/spring-data-neo4j-examples/cineasts/src/main/webapp/images/star.png differ diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/resources/forms.css b/spring-data-neo4j-examples/cineasts/src/main/webapp/resources/forms.css new file mode 100644 index 000000000..2f3103131 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/webapp/resources/forms.css @@ -0,0 +1,13 @@ +label { + display: block; + float: left; + width: 120px; +} + +/** + * Label for radio buttons, normal buttons and checkboxes. + */ +label.button-label { + display:inline; + float:none; +} diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/resources/grid.css b/spring-data-neo4j-examples/cineasts/src/main/webapp/resources/grid.css new file mode 100644 index 000000000..881cf609a --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/webapp/resources/grid.css @@ -0,0 +1,18 @@ +.span-1 { width: 6.958%; float:left; margin-right:1.5%; } +.span-2 { width: 15.416%; float:left; margin-right:1.5%; } +.span-3 { width: 23.875%; float:left; margin-right:1.5%; } +.span-4 { width: 32.333%; float:left; margin-right:1.5%; } +.span-5 { width: 40.791%; float:left; margin-right:1.5%; } +.span-6 { width: 49.250%; float:left; margin-right:1.5%; } +.span-7 { width: 57.708%; float:left; margin-right:1.5%; } +.span-8 { width: 66.166%; float:left; margin-right:1.5%; } +.span-9 { width: 74.625%; float:left; margin-right:1.5%; } +.span-10 { width: 83.083%; float:left; margin-right:1.5%; } +.span-11 { width: 91.541%; float:left; margin-right:1.5%; } +.span-12 { width: 100%; float:left; margin-right:0%; } + +.span-third { width: 32%; float:left; margin-right:2%; } +.span-half { width: 49%; float:left; margin-right:2%; } +.span-all { width: 100%; float:left; clear:right; } + +.last { clear:right; margin-right:0; } diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/resources/style.css b/spring-data-neo4j-examples/cineasts/src/main/webapp/resources/style.css new file mode 100644 index 000000000..316a5a2a5 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/webapp/resources/style.css @@ -0,0 +1,328 @@ + +@import url(typography.css); +@import url(forms.css); +@import url(grid.css); + +* { + outline-style: none; +} + +body { + margin:0; + padding:0; + background : #010100; +} + +a img { border:0; } + +/** + * MAIN COMPONENTS + */ + +#header { + background: url(../images/header-curtain.png) repeat-y bottom center #010100; + border-bottom: 1px solid #5C5C45; +} + +#logo img { + padding: 10px 10px 0; +} + +#header-topbar { + height: 49px; + padding: 0 0 0 10px; + background: url(../images/filmroll-horizontal.png) repeat-x bottom left transparent; +} + +#header-splash { + text-align:center; + background: url(../images/header-splash-bg.png) repeat-x bottom center; +} + +#logo-splash { + position:relative; + top:4px; +} + + +#header-menu { + float: right; + color: #DDDDDD; + height:28px; + background:#0b0f07; + padding: 10px 10px; +} + +.search { + float:left; + padding:0 10px; +} + +.navigation { + float:left; +} + +.navigation a { + color: #d8d8d8; + text-decoration:none; + padding: 0 0 0 5px; +} + +#content { + padding: 10px; +} + +#footer { + text-align:center; + color: #999999; + font-size: 12px; + padding: 10px; + clear:both; +} + +#footer img { + padding: 10px; +} +/** + * SEARCH RESULTS + */ + +.search-results > li { + border-top: 1px solid #666666; + padding: 10px; +} + +.search-result-details { +/* float:left; + padding: 0px 10px; + */ +} + +/** + * PROFILE, ACTOR AND MOVIE PAGES + */ + +.profile-header { + clear:both; +} + +.profile-image { + float:left; +} + +.profile-image img { + width: 100px; +} +.actor-image img, .profile-image img, .friend-image img, .thumbnail img { + border: 1px solid #4C5C2A !important; +} + +.thumbnail { + display:block; + /* float:left;*/ +} +.thumbnail img { + height: 100px; +} + +.actor-framed-image { + background: url(../images/actor-picture-frame.png) no-repeat; + width:166px; + height:214px; + padding: 6px 60px 16px; +} + +.actor-sidebar { + padding: 0 56px; +} + +.profile-header-details { + float: left; + padding: 0 10px; +} + +.profile-feed { + background: none repeat scroll 0 0 #252525; + border: 5px solid #CACF81; + padding: 10px; +} + +.facts { + width:50%; + float:left; +} + +.movie-content-outer, .actor-content-outer { + background: url(../images/filmroll-vertical-full.png) no-repeat top right; + padding-right: 38px; +} + +.movie-content, .actor-content { + background: url(../images/filmroll-vertical-full.png) no-repeat top left; + padding: 0 10px 0 48px; + min-height:690px; +} + +.actors-list li { + width:100px; + float:left; +} + +.actors-list img { + height:92px; + display:block; +} + +.friends-list li { + border-top:1px solid #333; + padding: 5px 0; +} + +.friends-list .friend-image { + float:left; +} + +.friend-image img { + height: 64px; +} +.friends-list .friend-info { + float:left; + padding: 0 5px; +} + +.friends-list h3, .search-results h3 { + border:none; + margin: 5px 0; +} + +.friends-list h3 a, .search-results h3 a { + text-decoration:none; + color: #99CC33; +} + +.rated-movies-list h3 { + margin: 0; + padding: 5px 0; + float: left; +} + +.rated-movies-list h3 a { + color: #99CC33; + text-decoration:none; +} + +.rated-movies-list li { + position:relative; + height:20px; + padding:8px 0; +} + +.rated-movies-list .rating { +/* position:absolute; + right:0; + top:5px; +*/ + float: right; +} + +/** + * BIG SEARCH + */ + +#big-search-wrap { + padding: 20px 0; + text-align: center; +} + +.big-search { + background: url("../images/searchbar-big.png") no-repeat scroll 0 0 transparent; + border: 0 none; + height: 50px; + width: 480px; + font-size: 22px; + padding: 0 10px; +} + +.big-search-submit { + background: url("../images/searchbar-big-searchicon.png") no-repeat scroll 0 0 transparent; + border: 0 none; + color: transparent; + cursor: pointer; + height: 40px; + left: -75px; + position: relative; + width: 41px; +} + +/** + * RATINGS + */ + +.rating-input li, .rating-input lh { + float:left; +} + +.rating { + display: block; + height: 20px; + width: 110px; +} + +.rating li { + height: 28px; + width: 22px; + display:block; + float:left; + padding:0 2px; +} + +.rating li.active { + background: url("/images/rating-active.png") no-repeat scroll center 0 transparent; +} + +.rating li.disabled { +/* background: url("/images/rating-disabled.png") no-repeat scroll center 0 transparent; */ +} + +/** + * TABLES + */ + +table th { + text-align: left; +} + +/** + * LISTS + */ + +ul { + list-style:none; + padding:0; +} + +lh { + font-weight:bold; +} + +/** + * UTILS + */ + +.break { + clear:both; +} + + +/** + * ERRORS + */ + +.error { + color:red; +} + +a { + text-decoration: none; + color: #99CC33; +} diff --git a/spring-data-neo4j-examples/cineasts/src/main/webapp/resources/typography.css b/spring-data-neo4j-examples/cineasts/src/main/webapp/resources/typography.css new file mode 100644 index 000000000..7410d6f29 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/main/webapp/resources/typography.css @@ -0,0 +1,32 @@ + +body { + font-family:tahoma,verdana,sans-serif,serif; + color: #666666; + font-size:13px; +} + +h2 { + color: #314718; /*#99CC33;*/ + font-size: 16px; + text-transform: uppercase; +} + +h3 { + color: #314718; /*#ff6600;*/ + font-size: 14px; + text-transform: uppercase; + border-bottom: 1px solid #333333; +} + +h4 { + margin: 5px 0; +} + +.tiny-link { + color: #222; + font-size:12px; + text-decoration:none; +} +.tiny-link:hover { + text-decoration:underline; +} diff --git a/spring-data-neo4j-examples/cineasts/src/test/java/org/neo4j/cineasts/domain/DomainTest.java b/spring-data-neo4j-examples/cineasts/src/test/java/org/neo4j/cineasts/domain/DomainTest.java new file mode 100644 index 000000000..114ec458c --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/test/java/org/neo4j/cineasts/domain/DomainTest.java @@ -0,0 +1,82 @@ +package org.neo4j.cineasts.domain; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.neo4j.cineasts.repository.MovieRepository; +import org.neo4j.cineasts.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Iterator; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author mh + * @since 04.03.11 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration({"/movies-test-context.xml"}) +@Transactional +public class DomainTest { + + @Autowired + protected MovieRepository movieRepository; + @Autowired + protected UserRepository userRepository; + + @Before + public void setUp() throws Exception { + } + + @Test + public void actorCanPlayARoleInAMovie() { + Person tomHanks = new Person("1","Tom Hanks").persist(); + Movie forestGump = new Movie("1", "Forrest Gump").persist(); + + Role role = tomHanks.playedIn(forestGump, "Forrest"); + + Movie foundForestGump = this.movieRepository.findByPropertyValue(null, "id", "1"); + + assertEquals("created and looked up movie equal", forestGump, foundForestGump); + Role firstRole = foundForestGump.getRoles().iterator().next(); + assertEquals("role forrest",role, firstRole); + assertEquals("role forrest","Forrest", firstRole.getName()); + } + + @Test + public void canFindMovieByTitleQuery() { + Movie forestGump = new Movie("1", "Forrest Gump").persist(); + Iterator queryResults = movieRepository.findAllByQuery("search", "title", "Forre*").iterator(); + assertTrue("found movie by query",queryResults.hasNext()); + Movie foundMovie = queryResults.next(); + assertEquals("created and looked up movie equal", forestGump, foundMovie); + assertFalse("found only one movie by query", queryResults.hasNext()); + } + + @Test + public void userCanRateMovie() { + Movie movie= new Movie("1","Forrest Gump").persist(); + User user = new User("ich","Micha","password").persist(); + Rating awesome = user.rate(movie, 5, "Awesome"); + + + User foundUser = userRepository.findByPropertyValue("login", "ich"); + Rating rating = user.getRatings().iterator().next(); + assertEquals(awesome,rating); + assertEquals("Awesome",rating.getComment()); + assertEquals(5,rating.getStars()); + assertEquals(5,movie.getStars(),0); + } + @Test + public void canFindUserByLogin() { + User user = new User("ich","Micha","password").persist(); + User foundUser = userRepository.findByPropertyValue("login", "ich"); + assertEquals(user, foundUser); + } +} diff --git a/spring-data-neo4j-examples/cineasts/src/test/java/org/neo4j/cineasts/movieimport/MovieDbApiClientTest.java b/spring-data-neo4j-examples/cineasts/src/test/java/org/neo4j/cineasts/movieimport/MovieDbApiClientTest.java new file mode 100644 index 000000000..935c56c29 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/test/java/org/neo4j/cineasts/movieimport/MovieDbApiClientTest.java @@ -0,0 +1,28 @@ +package org.neo4j.cineasts.movieimport; + +import org.junit.Test; + +import java.util.Map; + +import static org.junit.Assert.assertEquals; + +/** + * @author mh + * @since 13.03.11 + */ +public class MovieDbApiClientTest { + private static final String API_KEY = "926d2a79e82920b62f03b1cb57e532e6"; + + @Test + public void testGetMovie() throws Exception { + Map movie = new MovieDbApiClient(API_KEY).getMovie("2"); + assertEquals(2,movie.get("id")); + } + + @Test + public void testGetPerson() throws Exception { + Map person = new MovieDbApiClient(API_KEY).getPerson("30112"); + assertEquals(30112,person.get("id")); + + } +} diff --git a/spring-data-neo4j-examples/cineasts/src/test/java/org/neo4j/cineasts/movieimport/MovieDbImportServiceTest.java b/spring-data-neo4j-examples/cineasts/src/test/java/org/neo4j/cineasts/movieimport/MovieDbImportServiceTest.java new file mode 100644 index 000000000..7cc261452 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/test/java/org/neo4j/cineasts/movieimport/MovieDbImportServiceTest.java @@ -0,0 +1,51 @@ +package org.neo4j.cineasts.movieimport; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.neo4j.cineasts.domain.Movie; +import org.neo4j.cineasts.domain.Person; +import org.neo4j.cineasts.repository.MovieRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.transaction.annotation.Transactional; + +import static org.junit.Assert.assertEquals; + +/** + * @author mh + * @since 13.03.11 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration({"/movies-test-context.xml"}) +@Transactional +public class MovieDbImportServiceTest { + + @Autowired + MovieDbImportService importService; + + @Autowired + MovieRepository movieRepository; + + @Test + public void testImportMovie() throws Exception { + Movie movie = importService.importMovie("2"); + assertEquals("movie-id","2", movie.getId()); + assertEquals("movie-title","Ariel", movie.getTitle()); + } + + @Test + public void testImportMovieTwice() throws Exception { + Movie movie = importService.importMovie("2"); + Movie movie2 = importService.importMovie("2"); + final Movie foundMovie = movieRepository.findById("2"); + assertEquals("movie-id", movie, foundMovie); + } + + @Test + public void testImportPerson() throws Exception { + Person actor = importService.importPerson("105955"); + assertEquals("movie-id","105955", actor.getId()); + assertEquals("movie-title","George M. Williamson", actor.getName()); + } +} diff --git a/spring-data-neo4j-examples/cineasts/src/test/java/org/neo4j/cineasts/movieimport/MovieDbLocalStorageTest.java b/spring-data-neo4j-examples/cineasts/src/test/java/org/neo4j/cineasts/movieimport/MovieDbLocalStorageTest.java new file mode 100644 index 000000000..54bc6279b --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/test/java/org/neo4j/cineasts/movieimport/MovieDbLocalStorageTest.java @@ -0,0 +1,76 @@ +package org.neo4j.cineasts.movieimport; + +import org.junit.Before; +import org.junit.Test; + +import java.io.File; +import java.util.Collections; +import java.util.Map; + +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * @author mh + * @since 13.03.11 + */ +public class MovieDbLocalStorageTest { + + + private static final String ID = "111"; + private static final Map DATA = Collections.singletonMap("id", ID); + protected MovieDbLocalStorage storage; + + @Before + public void setUp() throws Exception { + storage = new MovieDbLocalStorage("target/data"); + } + + @Test + public void testHasMovie() throws Exception { + storage.storeMovie(ID,DATA); + assertEquals(true, storage.hasMovie(ID)); + } + + @Test + public void testLoadMovie() throws Exception { + storage.storeMovie(ID,DATA); + assertEquals(DATA,storage.loadMovie(ID)); + } + + @Test + public void testStoreMovie() throws Exception { + storage.storeMovie(ID,DATA); + assertEquals(true, new File("target/data/movie_"+ID+".json").exists()); + } + + @Test + public void testHasPerson() throws Exception { + storage.storePerson(ID,DATA); + assertEquals(true, storage.hasPerson(ID)); + } + + @Test + public void testLoadPerson() throws Exception { + storage.storePerson(ID,DATA); + assertEquals(DATA,storage.loadPerson(ID)); + } + @Test + public void testLoadPersonFromList() throws Exception { + storage.storePerson(ID,asList(DATA)); + assertEquals(DATA,storage.loadPerson(ID)); + } + @Test + public void testLoadPersonFromInvalidList() throws Exception { + storage.storePerson(ID, asList("Nothing found.")); + final Map personData = storage.loadPerson(ID); + assertTrue("person unexpectedly found", personData.containsKey("not_found")); + } + + @Test + public void testStorePerson() throws Exception { + storage.storePerson(ID,DATA); + assertEquals(true, new File("target/data/person_"+ID+".json").exists()); + } +} diff --git a/spring-data-neo4j-examples/cineasts/src/test/java/org/neo4j/cineasts/service/MoviesRepositoryTest.java b/spring-data-neo4j-examples/cineasts/src/test/java/org/neo4j/cineasts/service/MoviesRepositoryTest.java new file mode 100644 index 000000000..9f7b27851 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/test/java/org/neo4j/cineasts/service/MoviesRepositoryTest.java @@ -0,0 +1,88 @@ +package org.neo4j.cineasts.service; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.neo4j.cineasts.domain.Movie; +import org.neo4j.cineasts.domain.MovieRecommendation; +import org.neo4j.cineasts.domain.Rating; +import org.neo4j.cineasts.domain.User; +import org.neo4j.cineasts.repository.MovieRepository; +import org.neo4j.helpers.collection.IteratorUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.transaction.annotation.Transactional; + +import java.util.HashSet; +import java.util.List; + +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * @author mh + * @since 04.03.11 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration({"/movies-test-context.xml"}) +@Transactional +public class MoviesRepositoryTest { + @Autowired + MovieRepository movieRepository; + + @Test + public void testGetMovie() throws Exception { + Movie movie = new Movie("1", "Test-Movie").persist(); + Movie found = movieRepository.findById("1"); + assertEquals("movie found by id", movie, found); + + } + @Test + public void testGetMovieRecommendations() throws Exception { + Movie movie = new Movie("1", "Test-Movie").persist(); + Movie movie2 = new Movie("2", "Test-Movie2").persist(); + User user=new User("me","me","me").persist(); + user.rate(movie,3,"me"); + User friend=new User("friend","friend","friend").persist(); + friend.rate(movie,5,"friend"); + friend.rate(movie2,3,"friend2"); + assertEquals(2,movieRepository.count()); + final List recommendations = movieRepository.getRecommendations(user); + assertEquals("one recommendation", 1, recommendations.size()); + assertEquals("one recommendation", movie2, recommendations.get(0).getMovie()); + assertEquals("one recommendation", 3, recommendations.get(0).getRating()); + } + @Test + public void testRateMovie() throws Exception { + Movie movie = new Movie("1", "Test-Movie").persist(); + User user=new User("me","me","me").persist(); + user.rate(movie, 5, "comment"); + final Rating rating = IteratorUtil.first(movie.getRatings()); + assertEquals("rating stars", 5, rating.getStars()); + assertEquals("rating comment", "comment", rating.getComment()); + assertEquals("rating user", user, rating.getUser()); + assertEquals("rating movie", movie, rating.getMovie()); + } + + @Test + public void testFindTwoMovies() throws Exception { + Movie movie1 = new Movie("1", "Test-Movie1").persist(); + Movie movie2 = new Movie("2", "Test-Movie2").persist(); + Movie movie3 = new Movie("3", "Another-Movie3").persist(); + List found = movieRepository.findByTitleLike("Test*", new PageRequest(0, 2)).getContent(); + assertEquals("2 movies found", 2, found.size()); + assertEquals("2 correct movies found by query", new HashSet(asList(movie1, movie2)), new HashSet(found)); + } + + @Test + public void testFindTwoMoviesButRestrictToOne() throws Exception { + Movie movie1 = new Movie("1", "Test-Movie1").persist(); + Movie movie2 = new Movie("2", "Test-Movie2").persist(); + Movie movie3 = new Movie("3", "Another-Movie3").persist(); + List found = movieRepository.findByTitleLike("Test*", new PageRequest(0, 1)).getContent(); + assertEquals("1 movie found",1,found.size()); + assertTrue("1 correct movie found by query", found.get(0).getTitle().startsWith("Test")); + } +} diff --git a/spring-data-neo4j-examples/cineasts/src/test/resources/movies-test-context.xml b/spring-data-neo4j-examples/cineasts/src/test/resources/movies-test-context.xml new file mode 100644 index 000000000..51064ec85 --- /dev/null +++ b/spring-data-neo4j-examples/cineasts/src/test/resources/movies-test-context.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file