From 8143657e419ebb302e83a250f93516f581a2423c Mon Sep 17 00:00:00 2001 From: David Montag Date: Wed, 18 Aug 2010 21:37:02 +0200 Subject: [PATCH] Added support for relationship entities. Support for having them as fields in graph entities is still missing though. --- .../persistence/graph/Graph.java | 22 +- .../persistence/graph/Relationship.java | 20 -- ...uctorBypassingGraphEntityInstantiator.java | 26 +-- ...4jConstructorGraphEntityInstantiator.java} | 2 +- .../graph/neo4j/Neo4jNodeBacking.aj | 41 ++-- .../graph/neo4j/Neo4jRelationshipBacking.aj | 198 ++++++++++++++++++ .../graph/neo4j/RelationshipBacked.java | 20 ++ .../persistence/test/Friendship.java | 53 +++++ .../persistence/test/Group.java | 8 +- .../persistence/test/Person.java | 41 +--- .../test/graph/Neo4jGraphPersistenceTest.java | 107 ++++++++-- .../Neo4jGraphPersistenceTest-context.xml | 11 +- 12 files changed, 424 insertions(+), 125 deletions(-) delete mode 100644 src/main/java/org/springframework/persistence/graph/Relationship.java rename src/main/java/org/springframework/persistence/graph/neo4j/{Neo4jConstructorEntityInstantiator.java => Neo4jConstructorGraphEntityInstantiator.java} (75%) create mode 100644 src/main/java/org/springframework/persistence/graph/neo4j/Neo4jRelationshipBacking.aj create mode 100644 src/main/java/org/springframework/persistence/graph/neo4j/RelationshipBacked.java create mode 100644 src/test/java/org/springframework/persistence/test/Friendship.java diff --git a/src/main/java/org/springframework/persistence/graph/Graph.java b/src/main/java/org/springframework/persistence/graph/Graph.java index f9a501c34..4fdfec41b 100644 --- a/src/main/java/org/springframework/persistence/graph/Graph.java +++ b/src/main/java/org/springframework/persistence/graph/Graph.java @@ -5,6 +5,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.persistence.graph.neo4j.NodeBacked; + public @interface Graph { @Retention(RetentionPolicy.RUNTIME) @@ -13,7 +15,21 @@ public @interface Graph { @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) - public @interface Relationship { + public @interface Relationship { + + String type(); + + Direction direction(); + + Class elementClass() default NodeBacked.class; + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public @interface RelationshipEntity { + String type(); + + Direction direction(); } } @@ -21,7 +37,9 @@ public @interface Graph { @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Relationship { - + + String type() default ""; + @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface StartNode { diff --git a/src/main/java/org/springframework/persistence/graph/Relationship.java b/src/main/java/org/springframework/persistence/graph/Relationship.java deleted file mode 100644 index cf6704ec1..000000000 --- a/src/main/java/org/springframework/persistence/graph/Relationship.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.springframework.persistence.graph; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.persistence.graph.neo4j.NodeBacked; - -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.FIELD) -public @interface Relationship { - - String type(); - - Direction direction(); - - Class elementClass() default NodeBacked.class; - -} diff --git a/src/main/java/org/springframework/persistence/graph/neo4j/ConstructorBypassingGraphEntityInstantiator.java b/src/main/java/org/springframework/persistence/graph/neo4j/ConstructorBypassingGraphEntityInstantiator.java index 913133348..eddaab36f 100644 --- a/src/main/java/org/springframework/persistence/graph/neo4j/ConstructorBypassingGraphEntityInstantiator.java +++ b/src/main/java/org/springframework/persistence/graph/neo4j/ConstructorBypassingGraphEntityInstantiator.java @@ -7,28 +7,18 @@ import org.springframework.persistence.support.EntityInstantiator; import sun.reflect.ReflectionFactory; -/** - * Uses Sun internal libraries used in deserializations to instantiate - * objects bypassing constructor. - * - * Code based on this http://www.javaspecialists.eu/archive/Issue175.html - * TODO check license implications - * - * @author rodjohnson - * - */ -public class ConstructorBypassingGraphEntityInstantiator implements EntityInstantiator { +public class ConstructorBypassingGraphEntityInstantiator implements EntityInstantiator { - private static T createWithoutConstructorInvocation(Class clazz) { - return createWithoutConstructorInvocation(clazz, Object.class); - } + protected static T createWithoutConstructorInvocation(Class clazz) { + return createWithoutConstructorInvocation(clazz, Object.class); + } @SuppressWarnings("unchecked") - private static T createWithoutConstructorInvocation(Class clazz, Class parent) { + protected static T createWithoutConstructorInvocation(Class clazz, Class parent) { try { ReflectionFactory rf = ReflectionFactory.getReflectionFactory(); - Constructor objDef = parent.getDeclaredConstructor(); - Constructor intConstr = rf.newConstructorForSerialization(clazz, + Constructor objDef = parent.getDeclaredConstructor(); + Constructor intConstr = rf.newConstructorForSerialization(clazz, objDef); return clazz.cast(intConstr.newInstance()); } catch (RuntimeException e) { @@ -37,7 +27,7 @@ public class ConstructorBypassingGraphEntityInstantiator implements EntityInstan throw new IllegalStateException("Cannot create object", e); } } - + @Override public T createEntityFromState(Node n, Class c) { T t = createWithoutConstructorInvocation(c); diff --git a/src/main/java/org/springframework/persistence/graph/neo4j/Neo4jConstructorEntityInstantiator.java b/src/main/java/org/springframework/persistence/graph/neo4j/Neo4jConstructorGraphEntityInstantiator.java similarity index 75% rename from src/main/java/org/springframework/persistence/graph/neo4j/Neo4jConstructorEntityInstantiator.java rename to src/main/java/org/springframework/persistence/graph/neo4j/Neo4jConstructorGraphEntityInstantiator.java index 34949c443..23a4e5e4c 100644 --- a/src/main/java/org/springframework/persistence/graph/neo4j/Neo4jConstructorEntityInstantiator.java +++ b/src/main/java/org/springframework/persistence/graph/neo4j/Neo4jConstructorGraphEntityInstantiator.java @@ -9,7 +9,7 @@ import org.springframework.persistence.support.AbstractConstructorEntityInstanti * * @author Rod Johnson */ -public class Neo4jConstructorEntityInstantiator extends AbstractConstructorEntityInstantiator{ +public class Neo4jConstructorGraphEntityInstantiator extends AbstractConstructorEntityInstantiator{ @Override protected void setState(NodeBacked entity, Node s) { diff --git a/src/main/java/org/springframework/persistence/graph/neo4j/Neo4jNodeBacking.aj b/src/main/java/org/springframework/persistence/graph/neo4j/Neo4jNodeBacking.aj index 850367c3d..5384f165f 100644 --- a/src/main/java/org/springframework/persistence/graph/neo4j/Neo4jNodeBacking.aj +++ b/src/main/java/org/springframework/persistence/graph/neo4j/Neo4jNodeBacking.aj @@ -2,27 +2,24 @@ package org.springframework.persistence.graph.neo4j; import java.lang.reflect.Field; import java.util.AbstractSet; -import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; -import java.util.List; import java.util.Set; -import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.reflect.FieldSignature; import org.neo4j.graphdb.Direction; import org.neo4j.graphdb.DynamicRelationshipType; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.NotInTransactionException; +import org.neo4j.graphdb.Relationship; import org.neo4j.graphdb.RelationshipType; import org.neo4j.util.GraphDatabaseUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.dao.InvalidDataAccessResourceUsageException; -import org.springframework.persistence.graph.GraphEntity; -import org.springframework.persistence.graph.Relationship; +import org.springframework.persistence.graph.Graph; import org.springframework.persistence.support.AbstractTypeAnnotatingMixinFields; import org.springframework.persistence.support.EntityInstantiator; @@ -33,7 +30,7 @@ import org.springframework.persistence.support.EntityInstantiator; * * @author Rod Johnson */ -public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields { +public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields { //------------------------------------------------------------------------- // Configure aspect for whole system. @@ -43,7 +40,7 @@ public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields graphEntityInstantiator; + private EntityInstantiator graphEntityInstantiator; private GraphDatabaseUtil graphDatabaseUtil; @@ -63,8 +60,8 @@ public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields relatedType=(Class)field.getType(); + Class relatedType = (Class) field.getType(); if (relAnnotation != null) { return new SingleRelationshipInfo(DynamicRelationshipType.withName(relAnnotation.type()), - relAnnotation.direction().toNeo4jDir(),relatedType, graphEntityInstantiator); + relAnnotation.direction().toNeo4jDir(), relatedType, graphEntityInstantiator); } return new SingleRelationshipInfo(DynamicRelationshipType.withName(getNeo4jPropertyName(field)), Direction.OUTGOING, relatedType, graphEntityInstantiator); @@ -211,7 +208,7 @@ public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields relatedType; private final EntityInstantiator graphEntityInstantiator; - public SingleRelationshipInfo(RelationshipType type, Direction direction, Class clazz, EntityInstantiator graphEntityInstantiator) { + public SingleRelationshipInfo(RelationshipType type, Direction direction, Class clazz, EntityInstantiator graphEntityInstantiator) { this.type = type; this.direction = direction; this.relatedType = clazz; @@ -244,7 +241,7 @@ public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields result = new HashSet(); - for (org.neo4j.graphdb.Relationship rel : entityNode.getRelationships(type, direction)) { - NodeBacked newEntity=graphEntityInstantiator.createEntityFromState(rel.getOtherNode(entityNode), relatedType);; + for (Relationship rel : entityNode.getRelationships(type, direction)) { + NodeBacked newEntity = graphEntityInstantiator.createEntityFromState(rel.getOtherNode(entityNode), relatedType); result.add(newEntity); } return new ManagedSet(entity, result,this); } } - + } diff --git a/src/main/java/org/springframework/persistence/graph/neo4j/Neo4jRelationshipBacking.aj b/src/main/java/org/springframework/persistence/graph/neo4j/Neo4jRelationshipBacking.aj new file mode 100644 index 000000000..45f796fd5 --- /dev/null +++ b/src/main/java/org/springframework/persistence/graph/neo4j/Neo4jRelationshipBacking.aj @@ -0,0 +1,198 @@ +package org.springframework.persistence.graph.neo4j; + +import java.lang.reflect.Field; +import java.util.AbstractSet; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.reflect.FieldSignature; +import org.neo4j.graphdb.Direction; +import org.neo4j.graphdb.DynamicRelationshipType; +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Relationship; +import org.neo4j.graphdb.NotInTransactionException; +import org.neo4j.graphdb.RelationshipType; +import org.neo4j.util.GraphDatabaseUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.dao.InvalidDataAccessResourceUsageException; +import org.springframework.persistence.graph.Graph; +import org.springframework.persistence.support.AbstractTypeAnnotatingMixinFields; +import org.springframework.persistence.support.EntityInstantiator; + +/** + * Aspect to turn an object annotated with GraphEntity into a graph entity using Neo4J. + * Delegates all field access (except for fields assumed to be transient) + * to an underlying Neo4 graph node. + * + * @author Rod Johnson + */ +public aspect Neo4jRelationshipBacking extends AbstractTypeAnnotatingMixinFields { + + //------------------------------------------------------------------------- + // Configure aspect for whole system. + // init() method can be invoked automatically if the aspect is a Spring + // bean, or called in user code. + //------------------------------------------------------------------------- + // Aspect shared Neo4J Graph Database Service + private EntityInstantiator graphEntityInstantiator; + + @Autowired + public void init(EntityInstantiator gei) { + this.graphEntityInstantiator = gei; + } + + // Introduced fields + private Relationship RelationshipBacked.underlyingRelationship; + private Node RelationshipBacked.underlyingStartNode; + private Node RelationshipBacked.underlyingEndNode; + + public void RelationshipBacked.setUnderlyingRelationship(Relationship r) { + this.underlyingRelationship = r; + underlyingStartNode = r.getStartNode(); + underlyingEndNode = r.getEndNode(); + } + + public Relationship RelationshipBacked.getUnderlyingRelationship() { + return underlyingRelationship; + } + + public void RelationshipBacked.setUnderlyingStartNode(Node n) { + underlyingStartNode = n; + } + + public Node RelationshipBacked.getUnderlyingStartNode() { + return underlyingStartNode; + } + + public void RelationshipBacked.setUnderlyingEndNode(Node n) { + underlyingEndNode = n; + } + + public Node RelationshipBacked.getUnderlyingEndNode() { + return underlyingEndNode; + } + + public long RelationshipBacked.getId() { + return underlyingRelationship.getId(); + } + + + //------------------------------------------------------------------------- + // Equals and hashCode for Neo4j entities. + // Final to prevent overriding. + //------------------------------------------------------------------------- + // TODO could use template method for further checks if needed + public final boolean RelationshipBacked.equals(Object obj) { + if (obj instanceof RelationshipBacked) { + return this.getUnderlyingRelationship().equals(((RelationshipBacked) obj).getUnderlyingRelationship()); + } + return false; + } + + public final int RelationshipBacked.hashCode() { + return getUnderlyingRelationship().hashCode(); + } + + + Object around(RelationshipBacked entity) : entityFieldGet(entity) { + FieldSignature fieldSignature=(FieldSignature) thisJoinPoint.getSignature(); + Field f = fieldSignature.getField(); + + if (isStartNodeField(f)) { + Node startNode = entity.getUnderlyingStartNode(); + if (startNode == null) { + return null; + } + return graphEntityInstantiator.createEntityFromState(startNode, (Class) f.getType()); + } + if (isEndNodeField(f)) { + Node endNode = entity.getUnderlyingEndNode(); + if (endNode == null) { + return null; + } + return graphEntityInstantiator.createEntityFromState(endNode, (Class) f.getType()); + } + +// TODO fix arrays, TODO serialize other types as byte[] or string (for indexing, querying) via Annotation + if (isPropertyType(f.getType())) { + Relationship rel = entity.getUnderlyingRelationship(); + if (rel == null) { + throw new InvalidDataAccessApiUsageException("Please set start node and end node before reading from other fields."); + } + String propName = getNeo4jPropertyName(f); + log.info("GET " + f + " <- Neo4J simple relationship property [" + propName + "]"); + return rel.getProperty(propName, null); + } + + return proceed(entity); + } + + Object around(RelationshipBacked entity, Object newVal) : entityFieldSet(entity, newVal) { + try { + FieldSignature fieldSignature = (FieldSignature) thisJoinPoint.getSignature(); + Field f = fieldSignature.getField(); + if (newVal instanceof NodeBacked + && isStartNodeField(f) + && entity.getUnderlyingStartNode() == null) { + NodeBacked newValNb = (NodeBacked) newVal; + entity.setUnderlyingStartNode(newValNb.getUnderlyingNode()); + if (entity.getUnderlyingEndNode() != null) { + entity.setUnderlyingRelationship(entity.getUnderlyingStartNode().createRelationshipTo(entity.getUnderlyingEndNode(), DynamicRelationshipType.withName(f.getDeclaringClass().getSimpleName()))); + } + } + if (newVal instanceof NodeBacked + && isEndNodeField(f) + && entity.getUnderlyingEndNode() == null) { + NodeBacked newValNb = (NodeBacked) newVal; + entity.setUnderlyingEndNode(newValNb.getUnderlyingNode()); + if (entity.getUnderlyingStartNode() != null) { + entity.setUnderlyingRelationship(entity.getUnderlyingStartNode().createRelationshipTo(entity.getUnderlyingEndNode(), DynamicRelationshipType.withName(f.getDeclaringClass().getSimpleName()))); + } + } + if (isPropertyType(f.getType())) { + Relationship rel = entity.getUnderlyingRelationship(); + if (rel == null) { + throw new InvalidDataAccessApiUsageException("Please set start node and end node before assigning to other fields."); + } + String propName = getNeo4jPropertyName(f); + entity.getUnderlyingRelationship().setProperty(propName, newVal); + log.info("SET " + f + " -> Neo4J simple relationship property [" + propName + "] with value=[" + newVal + "]"); + return proceed(entity, newVal); + } + return proceed(entity, newVal); + } catch(NotInTransactionException e) { + throw new InvalidDataAccessResourceUsageException("Not in a Neo4j transaction.", e); + } + } + + private boolean isEndNodeField(Field f) { + return f.isAnnotationPresent(Graph.Relationship.EndNode.class); + } + + private boolean isStartNodeField(Field f) { + return f.isAnnotationPresent(Graph.Relationship.StartNode.class); + } + + private boolean isPropertyType(Class fieldType) { + // todo: add array support + return fieldType.isPrimitive() + || (fieldType.isArray() && !fieldType.getComponentType().isArray() && isPropertyType(fieldType.getComponentType())) + || fieldType.equals(String.class) + || fieldType.equals(Character.class) + || fieldType.equals(Boolean.class) + || (fieldType.getName().startsWith("java.lang") && Number.class.isAssignableFrom(fieldType)); + } + + private static String getNeo4jPropertyName(Field field) { + return String.format("%s.%s",field.getDeclaringClass().getSimpleName(),field.getName()); + } + + +} diff --git a/src/main/java/org/springframework/persistence/graph/neo4j/RelationshipBacked.java b/src/main/java/org/springframework/persistence/graph/neo4j/RelationshipBacked.java new file mode 100644 index 000000000..cfdbae0ab --- /dev/null +++ b/src/main/java/org/springframework/persistence/graph/neo4j/RelationshipBacked.java @@ -0,0 +1,20 @@ +package org.springframework.persistence.graph.neo4j; + +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Relationship; + +public interface RelationshipBacked { + + Relationship getUnderlyingRelationship(); + + void setUnderlyingRelationship(Relationship r); + + void setUnderlyingStartNode(Node n); + + Node getUnderlyingStartNode(); + + void setUnderlyingEndNode(Node n); + + Node getUnderlyingEndNode(); + +} diff --git a/src/test/java/org/springframework/persistence/test/Friendship.java b/src/test/java/org/springframework/persistence/test/Friendship.java new file mode 100644 index 000000000..0efcb1873 --- /dev/null +++ b/src/test/java/org/springframework/persistence/test/Friendship.java @@ -0,0 +1,53 @@ +package org.springframework.persistence.test; + +import org.springframework.persistence.graph.Graph; + +@Graph.Relationship +public class Friendship { + + public Friendship() { + } + + public Friendship(Person p1, Person p2) { + this.p1 = p1; + this.p2 = p2; + } + + public Friendship(Person p1, Person p2, int years) { + this.p1 = p1; + this.p2 = p2; + this.years = years; + } + + @Graph.Relationship.StartNode + private Person p1; + + @Graph.Relationship.EndNode + private Person p2; + + private int years; + + public void setPerson1(Person p) { + p1 = p; + } + + public Person getPerson1() { + return p1; + } + + public void setPerson2(Person p) { + p2 = p; + } + + public Person getPerson2() { + return p2; + } + + public void setYears(int years) { + this.years = years; + } + + public int getYears() { + return years; + } +} diff --git a/src/test/java/org/springframework/persistence/test/Group.java b/src/test/java/org/springframework/persistence/test/Group.java index 45733b47a..7c0694347 100644 --- a/src/test/java/org/springframework/persistence/test/Group.java +++ b/src/test/java/org/springframework/persistence/test/Group.java @@ -1,16 +1,14 @@ package org.springframework.persistence.test; import org.springframework.persistence.graph.Direction; -import org.springframework.persistence.graph.GraphEntity; -import org.springframework.persistence.graph.Relationship; +import org.springframework.persistence.graph.Graph; -import java.util.ArrayList; import java.util.Collection; -@GraphEntity +@Graph.Entity public class Group { - @Relationship(type = "persons", direction = Direction.OUTGOING, elementClass = Person.class) + @Graph.Entity.Relationship(type = "persons", direction = Direction.OUTGOING, elementClass = Person.class) private Collection persons; public void setPersons(Collection persons) { diff --git a/src/test/java/org/springframework/persistence/test/Person.java b/src/test/java/org/springframework/persistence/test/Person.java index f93ae79fb..aa34879fa 100644 --- a/src/test/java/org/springframework/persistence/test/Person.java +++ b/src/test/java/org/springframework/persistence/test/Person.java @@ -1,13 +1,9 @@ package org.springframework.persistence.test; -import java.lang.reflect.Field; -import java.lang.reflect.ParameterizedType; - import org.springframework.persistence.graph.Direction; -import org.springframework.persistence.graph.GraphEntity; -import org.springframework.persistence.graph.Relationship; +import org.springframework.persistence.graph.Graph; -@GraphEntity +@Graph.Entity public class Person { private Long id; @@ -18,30 +14,17 @@ public class Person { private Short height; - Person spouse; + private Person spouse; - @Relationship(type="mother", direction=Direction.OUTGOING) - Person mother; + @Graph.Entity.Relationship(type="mother", direction=Direction.OUTGOING) + private Person mother; - @Relationship(type="boss", direction=Direction.INCOMING) - Person boss; - - @Relationship(type="friend", direction=Direction.BOTH) - Person friend; + @Graph.Entity.Relationship(type="boss", direction=Direction.INCOMING) + private Person boss; // @Property(serialize=SerializationPolicy.STRING, index=true, queryable=true, removeOnReset=true) // Date birthday; - /* - { - Field f; - if (f.getGenericType() instanceof ParameterizedType) { - ((ParameterizedType)(f.getGenericType())).getActualTypeArguments(); - } - } - @Relationship( target=Person.class, cardinality="", type="children",) - Collection children; - */ public Person(String name, int age) { this.name = name; @@ -93,17 +76,11 @@ public class Person { public Person getBoss() { return boss; } + public void setBoss(Person boss) { this.boss = boss; } - - public Person getFriend() { - return friend; - } - - public void setFriend(Person friend) { - this.friend = friend; - } + @Override public String toString() { return name; diff --git a/src/test/java/org/springframework/persistence/test/graph/Neo4jGraphPersistenceTest.java b/src/test/java/org/springframework/persistence/test/graph/Neo4jGraphPersistenceTest.java index 8d3103e9e..27445e47d 100644 --- a/src/test/java/org/springframework/persistence/test/graph/Neo4jGraphPersistenceTest.java +++ b/src/test/java/org/springframework/persistence/test/graph/Neo4jGraphPersistenceTest.java @@ -5,7 +5,6 @@ import junit.framework.Assert; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.neo4j.graphdb.Direction; @@ -23,13 +22,13 @@ import org.springframework.persistence.graph.neo4j.FinderFactory; import org.springframework.persistence.graph.neo4j.Neo4jHelper; import org.springframework.persistence.graph.neo4j.NodeBacked; import org.springframework.persistence.support.EntityInstantiator; +import org.springframework.persistence.test.Friendship; import org.springframework.persistence.test.Group; import org.springframework.persistence.test.Person; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; @@ -43,7 +42,7 @@ public class Neo4jGraphPersistenceTest { protected final Log log = LogFactory.getLog(getClass()); @Autowired - private EntityInstantiator nodeInstantiator; + private EntityInstantiator graphEntityInstantiator; @Autowired protected GraphDatabaseService graphDatabaseService; @@ -56,7 +55,7 @@ public class Neo4jGraphPersistenceTest { @Test public void testStuffWasAutowired() { Assert.assertNotNull( graphDatabaseService ); - Assert.assertNotNull( nodeInstantiator ); + Assert.assertNotNull( graphEntityInstantiator ); } @Before @@ -73,7 +72,7 @@ public class Neo4jGraphPersistenceTest { Assert.assertEquals(p.getAge(), p.getUnderlyingNode().getProperty("Person.age")); insertedId = p.getId(); Node n = findPersonTestNode(); - Person found = nodeInstantiator.createEntityFromState(n, Person.class); + Person found = graphEntityInstantiator.createEntityFromState(n, Person.class); Assert.assertEquals("Rod", found.getUnderlyingNode().getProperty("Person.name")); Assert.assertEquals(39, found.getUnderlyingNode().getProperty("Person.age")); } @@ -276,23 +275,91 @@ public class Neo4jGraphPersistenceTest { Person p = new Person("Michael", 35); Assert.assertEquals(1, finder.count()); } - + @Test - public void printNeo4jData() { - StringBuilder ret = new StringBuilder(); - for (Node n : graphDatabaseService.getAllNodes()) { - ret.append("ID: " + n.getId() + " ["); - int x = 0; - for (String prop : n.getPropertyKeys()) { - if (x++ > 0) { - ret.append(", "); - } - ret.append(prop + "=" + n.getProperty(prop)); - } - ret.append("] "); - } - System.out.println("*** NEO4J DATA: " + ret); + @Transactional + public void testRelationshipCreate() { + Person p = new Person("Michael", 35); + Person p2 = new Person("David", 25); + Friendship f = new Friendship(p, p2); + Relationship rel = p.getUnderlyingNode().getSingleRelationship(DynamicRelationshipType.withName("Friendship"), Direction.OUTGOING); + Assert.assertEquals(f.getUnderlyingRelationship(), rel); + Assert.assertEquals(p2.getUnderlyingNode(), rel.getEndNode()); } + + @Test + @Transactional + public void testRelationshipSetProperty() { + Person p = new Person("Michael", 35); + Person p2 = new Person("David", 25); + Friendship f = new Friendship(p, p2); + f.setYears(1); + Assert.assertEquals(1, f.getUnderlyingRelationship().getProperty("Friendship.years")); + } + + @Test + @Transactional + public void testRelationshipGetProperty() { + Person p = new Person("Michael", 35); + Person p2 = new Person("David", 25); + Friendship f = new Friendship(p, p2); + f.getUnderlyingRelationship().setProperty("Friendship.years", 1); + Assert.assertEquals(1, f.getYears()); + } + + @Test(expected = InvalidDataAccessApiUsageException.class) + @Transactional + public void testRelationshipSetPropertyBeforeCreated() { + Friendship f = new Friendship(); + f.setYears(1); + } + + @Test(expected = InvalidDataAccessApiUsageException.class) + @Transactional + public void testRelationshipGetPropertyBeforeCreated() { + Friendship f = new Friendship(); + f.getYears(); + } + + @Test + @Transactional + public void testRelationshipSetEndNodeBeforeStartNode() { + Person p = new Person("Michael", 35); + Person p2 = new Person("David", 25); + Friendship f = new Friendship(); + f.setPerson2(p2); + f.setPerson1(p); + Relationship rel = p.getUnderlyingNode().getSingleRelationship(DynamicRelationshipType.withName("Friendship"), Direction.OUTGOING); + Assert.assertEquals(f.getUnderlyingRelationship(), rel); + Assert.assertEquals(p2.getUnderlyingNode(), rel.getEndNode()); + } + + @Test + @Transactional + public void testRelationshipGetStartNodeAndEndNode() { + Person p = new Person("Michael", 35); + Person p2 = new Person("David", 25); + Friendship f = new Friendship(p, p2); + Assert.assertEquals(p, f.getPerson1()); + Assert.assertEquals(p2, f.getPerson2()); + } + +// @Test +// public void printNeo4jData() { +// StringBuilder ret = new StringBuilder(); +// for (Node n : graphDatabaseService.getAllNodes()) { +// ret.append("ID: " + n.getId() + " ["); +// int x = 0; +// for (String prop : n.getPropertyKeys()) { +// if (x++ > 0) { +// ret.append(", "); +// } +// ret.append(prop + "=" + n.getProperty(prop)); +// } +// ret.append("] "); +// } +// System.out.println("*** NEO4J DATA: " + ret); +// } private Node findPersonTestNode() { return graphDatabaseService.getNodeById(insertedId); diff --git a/src/test/resources/org/springframework/persistence/test/graph/Neo4jGraphPersistenceTest-context.xml b/src/test/resources/org/springframework/persistence/test/graph/Neo4jGraphPersistenceTest-context.xml index 3a5ba3ce8..59cbfd34d 100644 --- a/src/test/resources/org/springframework/persistence/test/graph/Neo4jGraphPersistenceTest-context.xml +++ b/src/test/resources/org/springframework/persistence/test/graph/Neo4jGraphPersistenceTest-context.xml @@ -69,8 +69,9 @@ Start datastore config --> - + + +