diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e66dbc4
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+# Intellij
+.idea/
+*.iml
+*.iws
+
+# Mac
+.DS_Store
+
+# Maven
+log/
+target/
diff --git a/README.md b/README.md
index 7effc43..d351cac 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
-spring-data-neo4j-showcase
-==========================
+Our Spring Data Neo4j showcase application for comSysto Blog Article Spring Data Neo4j (>>Link here<<).
-Showcase for our blog entry about Spring Data Neo4j.
+It uses Spring, Spring Data Neo4j and Maven to demonstrate how to recommend products based on other users' views.
+
+Please mail comments to Roger.Kowalewski@comsysto.com or Elisabeth.Engel@comsysto.com!
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..2f7d142
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,114 @@
+
+
+ 4.0.0
+
+ spring-data-neo4j-showcase
+ spring-data-neo4j-showcase
+ 1.0-SNAPSHOT
+
+
+ 2.2.0.RELEASE
+ 1.9
+ 3.2.1.RELEASE
+ 1.9.M04
+
+
+
+
+ org.springframework
+ spring-core
+ ${spring.core.version}
+
+
+
+ org.springframework.data
+ spring-data-neo4j
+ ${spring.data.neo4j.version}
+
+
+
+ org.neo4j
+ neo4j
+ ${neo4j.kernel.version}
+
+
+
+ org.neo4j
+ neo4j-cypher
+ ${neo4j.kernel.version}
+
+
+
+ org.neo4j
+ neo4j-cypher-dsl
+ ${neo4j.cypher.dsl.version}
+
+
+
+ org.neo4j
+ neo4j-kernel
+ ${neo4j.kernel.version}
+ test-jar
+
+
+
+ junit
+ junit
+ 4.11
+
+
+ org.springframework
+ spring-test
+ ${spring.core.version}
+
+
+
+ cglib
+ cglib
+ 2.2.2
+
+
+
+ javax.validation
+ validation-api
+ 1.1.0.Final
+
+
+
+
+
+ org.springframework.maven.release
+ Spring Maven Release Repository
+ http://maven.springsource.org/release
+
+ true
+
+ false
+
+
+ neo4j-public-release-repository
+ http://m2.neo4j.org/releases
+
+ false
+
+
+ true
+
+
+
+
+
+
+
+ maven-compiler-plugin
+
+ 1.6
+ 1.6
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/com/comsysto/springDataNeo4j/showcase/ClickedRelationship.java b/src/main/java/com/comsysto/springDataNeo4j/showcase/ClickedRelationship.java
new file mode 100644
index 0000000..ff42a00
--- /dev/null
+++ b/src/main/java/com/comsysto/springDataNeo4j/showcase/ClickedRelationship.java
@@ -0,0 +1,59 @@
+package com.comsysto.springDataNeo4j.showcase;
+
+import org.springframework.data.neo4j.annotation.*;
+
+
+@RelationshipEntity(type = RelationshipTypes.CLICKED)
+public class ClickedRelationship
+{
+ @GraphId
+ private Long graphId;
+
+ @StartNode
+ private User user;
+
+ @EndNode
+ @Fetch
+ private Product product;
+
+ private Integer count;
+
+ public ClickedRelationship() {/* NOOP */}
+
+ public ClickedRelationship(User user, Product product)
+ {
+ this.user = user;
+ this.product = product;
+ }
+
+
+ public Integer getCount() {
+ return this.count;
+ }
+
+ public void setCount(Integer count) {
+ this.count = count;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ ClickedRelationship that = (ClickedRelationship) o;
+
+ if (product != null ? !product.equals(that.product) : that.product != null) return false;
+ if (count != null ? !count.equals(that.count) : that.count != null) return false;
+ if (user != null ? !user.equals(that.user) : that.user != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = user != null ? user.hashCode() : 0;
+ result = 31 * result + (product != null ? product.hashCode() : 0);
+ result = 31 * result + (count != null ? count.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/src/main/java/com/comsysto/springDataNeo4j/showcase/IdentifiableEntity.java b/src/main/java/com/comsysto/springDataNeo4j/showcase/IdentifiableEntity.java
new file mode 100644
index 0000000..2f9f422
--- /dev/null
+++ b/src/main/java/com/comsysto/springDataNeo4j/showcase/IdentifiableEntity.java
@@ -0,0 +1,25 @@
+package com.comsysto.springDataNeo4j.showcase;
+
+import org.springframework.data.neo4j.annotation.GraphId;
+
+/**
+ * @author: rkowalewski
+ */
+public abstract class IdentifiableEntity {
+ @GraphId
+ private Long graphId;
+
+ public Long getGraphId() {
+ return graphId;
+ }
+
+ @Override
+ public boolean equals( Object obj ) {
+ return obj instanceof IdentifiableEntity && graphId.equals( ((IdentifiableEntity) obj).getGraphId() );
+ }
+
+ @Override
+ public int hashCode() {
+ return 0;
+ }
+}
diff --git a/src/main/java/com/comsysto/springDataNeo4j/showcase/Product.java b/src/main/java/com/comsysto/springDataNeo4j/showcase/Product.java
new file mode 100644
index 0000000..52dd19e
--- /dev/null
+++ b/src/main/java/com/comsysto/springDataNeo4j/showcase/Product.java
@@ -0,0 +1,96 @@
+package com.comsysto.springDataNeo4j.showcase;
+
+import org.springframework.data.neo4j.annotation.Indexed;
+import org.springframework.data.neo4j.annotation.NodeEntity;
+import org.springframework.data.neo4j.annotation.RelatedToVia;
+import org.springframework.data.neo4j.support.index.IndexType;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@NodeEntity
+public class Product extends IdentifiableEntity {
+
+ @Indexed(indexName = "productId")
+ private String productId;
+
+ @Indexed(indexType = IndexType.FULLTEXT, indexName = "productName")
+ private String productName;
+
+ @RelatedToVia(type = RelationshipTypes.VIEWED)
+ private Set productsViewed = new HashSet();
+
+
+ public Product() {/* NOOP */}
+
+ public Product(String productId, String productName) {
+ super();
+
+ this.productId = productId;
+ this.productName = productName;
+
+ }
+
+ public String getProductId() {
+ return productId;
+ }
+
+ public void setProductId(String productId) {
+ this.productId = productId;
+ }
+
+ public String getProductName() {
+ return productName;
+ }
+
+ public void setProductName(String productName) {
+ this.productName = productName;
+ }
+
+ public Set getProductsViewed() {
+ return productsViewed;
+ }
+
+ public void setProductsViewed(Set productsViewed) {
+ this.productsViewed = productsViewed;
+ }
+
+ public void addProductViewed(Product productViewed)
+ {
+ productsViewed.add(productViewed);
+ }
+
+
+ @Override
+ public String toString() {
+ return "Product{" +
+ "graphId=" + this.getGraphId() +
+ ", productId=" + productId +
+ ", productName=" + productName +
+ //", #productsViewed=" + productsViewed.size() +
+ //", #userClicked=" + usersClicked.size() +
+ '}';
+ }
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
+
+ Product product = (Product) o;
+
+ if (productId != null ? !productId.equals(product.productId) : product.productId != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + (productId != null ? productId.hashCode() : 0);
+ return result;
+ }
+
+}
diff --git a/src/main/java/com/comsysto/springDataNeo4j/showcase/ProductRepository.java b/src/main/java/com/comsysto/springDataNeo4j/showcase/ProductRepository.java
new file mode 100644
index 0000000..1139c57
--- /dev/null
+++ b/src/main/java/com/comsysto/springDataNeo4j/showcase/ProductRepository.java
@@ -0,0 +1,36 @@
+package com.comsysto.springDataNeo4j.showcase;
+
+import org.springframework.data.neo4j.annotation.Query;
+import org.springframework.data.neo4j.repository.GraphRepository;
+import org.springframework.data.repository.query.Param;
+
+import java.util.List;
+
+
+public interface ProductRepository extends GraphRepository {
+
+ Product findByProductId(String productId);
+
+ List findByProductNameLike(String productName);
+
+ @Query("START product=node:Product(productId='{productId}') " +
+ "MATCH product-[viewed:VIEWED]->otherProduct " +
+ "RETURN distinct otherProduct " +
+ "ORDER BY viewed.count desc " +
+ "LIMIT 5")
+ List findOtherUsersAlsoViewedProducts(@Param("productId") String productId);
+
+ @Query("START product=node:Product(productId='*') " +
+ "RETURN distinct product " +
+ "ORDER BY product.productName")
+ List findAllProductsSortedByName();
+
+ @Query("START product=node:Product(productId='{productId}'), user=node:User(userId='{userId}') " +
+ "MATCH user-[clicked:CLICKED]->product-[viewed:VIEWED]->otherProduct " +
+ "WHERE not(user-[:CLICKED]->otherProduct) " +
+ "RETURN distinct otherProduct " +
+ "ORDER BY viewed.count DESC " +
+ "LIMIT 5")
+ List findOtherUsersAlsoViewedProductsWithoutAlreadyViewed(@Param("productId") String productId, @Param("userId") String userId);
+
+}
diff --git a/src/main/java/com/comsysto/springDataNeo4j/showcase/RelationshipTypes.java b/src/main/java/com/comsysto/springDataNeo4j/showcase/RelationshipTypes.java
new file mode 100644
index 0000000..8e1f515
--- /dev/null
+++ b/src/main/java/com/comsysto/springDataNeo4j/showcase/RelationshipTypes.java
@@ -0,0 +1,12 @@
+package com.comsysto.springDataNeo4j.showcase;
+
+/**
+ * @author: rkowalewski
+ */
+public final class RelationshipTypes {
+ public static final String MEMBER_OF = "MEMBEROF";
+
+ public static final String CLICKED = "CLICKED";
+ public static final String VIEWED = "VIEWED";
+
+}
diff --git a/src/main/java/com/comsysto/springDataNeo4j/showcase/User.java b/src/main/java/com/comsysto/springDataNeo4j/showcase/User.java
new file mode 100644
index 0000000..f501828
--- /dev/null
+++ b/src/main/java/com/comsysto/springDataNeo4j/showcase/User.java
@@ -0,0 +1,97 @@
+package com.comsysto.springDataNeo4j.showcase;
+
+import org.springframework.data.neo4j.annotation.*;
+import org.springframework.data.neo4j.support.index.IndexType;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@NodeEntity
+public class User extends IdentifiableEntity {
+
+ @Indexed(indexName = "userId")
+ private String userId;
+
+ @Indexed(indexType = IndexType.FULLTEXT, indexName = "userName")
+ private String userName;
+
+ private Product clickedBofore = null;
+
+ @RelatedToVia(type = RelationshipTypes.CLICKED)
+ private Set clickedProducts = new HashSet();
+
+
+ public User() {/* NOOP */}
+
+ public User(String userId, String name) {
+ super();
+
+ this.userId = userId;
+ this.userName = name;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public void setUserId(String userId) {
+ this.userId = userId;
+ }
+
+ public String getUserName() {
+ return userName;
+ }
+
+ public void setUserName(String userName) {
+ this.userName = userName;
+ }
+
+ public Set getClickedProducts() {
+ return clickedProducts;
+ }
+
+ public void addClickedProduct(Product clickedProduct)
+ {
+
+ if (this.clickedBofore != null) {
+ this.clickedBofore.addProductViewed(clickedProduct);
+ }
+
+ this.clickedProducts.add(clickedProduct);
+
+ //clickedProduct.addUserClicked(this);
+
+ this.clickedBofore = clickedProduct;
+
+ }
+
+ @Override
+ public String toString() {
+ return "User{" +
+ "graphId=" + this.getGraphId() +
+ ", userId=" + this.userId +
+ ", userName=" + this.userName +
+ //", #clickedProducts=" + this.clickedProducts.size() +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
+
+ User user = (User) o;
+
+ if (userId != null ? !userId.equals(user.userId) : user.userId != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + (userId != null ? userId.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/src/main/java/com/comsysto/springDataNeo4j/showcase/UserRepository.java b/src/main/java/com/comsysto/springDataNeo4j/showcase/UserRepository.java
new file mode 100644
index 0000000..c3150e7
--- /dev/null
+++ b/src/main/java/com/comsysto/springDataNeo4j/showcase/UserRepository.java
@@ -0,0 +1,11 @@
+package com.comsysto.springDataNeo4j.showcase;
+
+import org.springframework.data.neo4j.repository.GraphRepository;
+
+
+public interface UserRepository extends GraphRepository{
+
+ User findByUserId(String userId);
+
+ Iterable findByUserNameLike(String userName);
+}
diff --git a/src/main/java/com/comsysto/springDataNeo4j/showcase/ViewedRelationship.java b/src/main/java/com/comsysto/springDataNeo4j/showcase/ViewedRelationship.java
new file mode 100644
index 0000000..7a0a500
--- /dev/null
+++ b/src/main/java/com/comsysto/springDataNeo4j/showcase/ViewedRelationship.java
@@ -0,0 +1,60 @@
+package com.comsysto.springDataNeo4j.showcase;
+
+import org.springframework.data.neo4j.annotation.*;
+
+
+@RelationshipEntity(type = RelationshipTypes.VIEWED)
+public class ViewedRelationship
+{
+ @GraphId
+ private Long graphId;
+
+ @StartNode
+ private Product productStart;
+
+ @EndNode
+ @Fetch
+ private Product productEnd;
+
+ private Integer count;
+
+ public ViewedRelationship() {/* NOOP */}
+
+ public ViewedRelationship(Product productStart, Product productEnd)
+ {
+ this.productStart = productStart;
+ this.productEnd = productEnd;
+ }
+
+
+ public Integer getCount() {
+ return this.count;
+ }
+
+ public void setCount(Integer count) {
+ this.count = count;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ ViewedRelationship that = (ViewedRelationship) o;
+
+ if (productStart != null ? !productStart.equals(that.productStart) : that.productStart != null) return false;
+ if (productEnd != null ? !productEnd.equals(that.productEnd) : that.productEnd != null) return false;
+ if (count != null ? !count.equals(that.count) : that.count != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = productStart != null ? productStart.hashCode() : 0;
+ result = 31 * result + (productEnd != null ? productEnd.hashCode() : 0);
+ result = 31 * result + (count != null ? count.hashCode() : 0);
+ return result;
+ }
+
+}
diff --git a/src/test/java/com/comsysto/springDataNeo4j/showcase/SpringDataNeo4jProductUserTest.java b/src/test/java/com/comsysto/springDataNeo4j/showcase/SpringDataNeo4jProductUserTest.java
new file mode 100644
index 0000000..3ec1b51
--- /dev/null
+++ b/src/test/java/com/comsysto/springDataNeo4j/showcase/SpringDataNeo4jProductUserTest.java
@@ -0,0 +1,111 @@
+package com.comsysto.springDataNeo4j.showcase;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.neo4j.graphdb.GraphDatabaseService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.neo4j.support.node.Neo4jHelper;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+import java.util.Set;
+
+import static org.junit.Assert.*;
+
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(locations = {"classpath:com/comsysto/springDataNeo4j/showcase/related-to-via-test-context.xml"})
+@Transactional
+public class SpringDataNeo4jProductUserTest {
+ @Autowired
+ private UserRepository userRepository;
+
+ @Autowired
+ private ProductRepository productRepository;
+
+ @Autowired
+ GraphDatabaseService graphDatabaseService;
+
+ User jordan, pippen, miller;
+ Product pizzaMargarita, pizzaFungi, pizzaSalami, pizzaVegitarian, pizzaRustica;
+
+ public void createSzenario () {
+ jordan = createUser("MJ", "Monika Jordan");
+ pippen = createUser("SP", "Sandra Pippen");
+ miller = createUser("JM", "John Miller");
+
+ pizzaMargarita = createProduct("Pizza_1", "Pizza Margarita");
+ pizzaFungi = createProduct("Pizza_1", "Pizza Fungi");
+ pizzaSalami = createProduct("Pizza_1", "Pizza Salami");
+ pizzaVegitarian = createProduct("Pizza_1", "Pizza Vegitarian");
+ pizzaRustica = createProduct("Pizza_1", "Pizza Rustica");
+
+ jordan.addClickedProduct(pizzaMargarita);
+ jordan.addClickedProduct(pizzaFungi);
+ jordan.addClickedProduct(pizzaSalami);
+
+ pippen.addClickedProduct(pizzaMargarita);
+ pippen.addClickedProduct(pizzaVegitarian);
+ pippen.addClickedProduct(pizzaRustica);
+
+ miller.addClickedProduct(pizzaFungi);
+
+ }
+
+ @Test
+ public void testClickedRelationships() {
+
+ createSzenario();
+
+ //Load and check relations
+
+ List allProducts = productRepository.findAll().as(List.class);
+ assertEquals("there should be three products in the products repository", 5, allProducts.size());
+
+ assertTrue("saved and loaded products should be equal",
+ allProducts.contains(pizzaMargarita) && allProducts.contains(pizzaFungi) &&
+ allProducts.contains(pizzaSalami) && allProducts.contains(pizzaVegitarian) &&
+ allProducts.contains(pizzaRustica));
+
+
+ List allUsers = userRepository.findAll().as(List.class);
+ assertEquals("there should be three users in the user repository", 1, allUsers.size());
+
+ Set clickedProducts = allUsers.get(0).getClickedProducts();
+ assertEquals("Monika Jordan should have three clicked products", 3, clickedProducts.size());
+ assertTrue("The two products Monika Jordan clicked on should be pizza margarita and pizza fungi",
+ clickedProducts.contains(pizzaMargarita) && clickedProducts.contains(pizzaFungi) && clickedProducts.contains(pizzaSalami));
+ }
+
+ @Test
+ public void testNamedCypherQuerys() {
+
+ createSzenario();
+
+ List alsoViewedProducts = productRepository.findOtherUsersAlsoViewedProducts(pizzaMargarita.getProductId());
+ assertTrue("using this cypher query should return a list with also viewed products",
+ alsoViewedProducts.contains(pizzaFungi) && alsoViewedProducts.contains(pizzaVegitarian));
+
+ List alsoViewedProductsWithoutAlreadyViewed = productRepository.findOtherUsersAlsoViewedProductsWithoutAlreadyViewed(pizzaMargarita.getProductId(), miller.getUserId());
+ assertTrue("using this cypher query should return a list with also viewed products without the ones Miller already viewed",
+ alsoViewedProducts.contains(pizzaVegitarian));
+
+
+ }
+
+ private Product createProduct(String id, String name) {
+ return productRepository.save(new Product(id, name));
+ }
+
+ private User createUser(String id, String name) {
+ return userRepository.save(new User(id, name));
+ }
+
+ @After
+ public void cleanDB() {
+ Neo4jHelper.cleanDb(graphDatabaseService);
+ }
+}
diff --git a/src/test/resources/com/comsysto/springDataNeo4j/showcase/related-to-via-test-context.xml b/src/test/resources/com/comsysto/springDataNeo4j/showcase/related-to-via-test-context.xml
new file mode 100644
index 0000000..12cddd0
--- /dev/null
+++ b/src/test/resources/com/comsysto/springDataNeo4j/showcase/related-to-via-test-context.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file