Added support for relationship entities. Support for having them as fields in graph entities is still missing though.

This commit is contained in:
David Montag
2010-08-18 21:37:02 +02:00
parent d54cc08a07
commit 8143657e41
12 changed files with 424 additions and 125 deletions

View File

@@ -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<? extends NodeBacked> 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 {

View File

@@ -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<? extends NodeBacked> elementClass() default NodeBacked.class;
}

View File

@@ -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<NodeBacked,Node> {
public class ConstructorBypassingGraphEntityInstantiator implements EntityInstantiator<NodeBacked, Node> {
private static <T> T createWithoutConstructorInvocation(Class<T> clazz) {
return createWithoutConstructorInvocation(clazz, Object.class);
}
protected static <T> T createWithoutConstructorInvocation(Class<T> clazz) {
return createWithoutConstructorInvocation(clazz, Object.class);
}
@SuppressWarnings("unchecked")
private static <T> T createWithoutConstructorInvocation(Class<T> clazz, Class<? super T> parent) {
protected static <T> T createWithoutConstructorInvocation(Class<T> clazz, Class<? super T> 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 extends NodeBacked> T createEntityFromState(Node n, Class<T> c) {
T t = createWithoutConstructorInvocation(c);

View File

@@ -9,7 +9,7 @@ import org.springframework.persistence.support.AbstractConstructorEntityInstanti
*
* @author Rod Johnson
*/
public class Neo4jConstructorEntityInstantiator extends AbstractConstructorEntityInstantiator<NodeBacked, Node>{
public class Neo4jConstructorGraphEntityInstantiator extends AbstractConstructorEntityInstantiator<NodeBacked, Node>{
@Override
protected void setState(NodeBacked entity, Node s) {

View File

@@ -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<GraphEntity,NodeBacked> {
public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields<Graph.Entity,NodeBacked> {
//-------------------------------------------------------------------------
// Configure aspect for whole system.
@@ -43,7 +40,7 @@ public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields<GraphEn
// Aspect shared Neo4J Graph Database Service
private GraphDatabaseService graphDatabaseService;
private EntityInstantiator<NodeBacked,Node> graphEntityInstantiator;
private EntityInstantiator<NodeBacked, Node> graphEntityInstantiator;
private GraphDatabaseUtil graphDatabaseUtil;
@@ -63,8 +60,8 @@ public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields<GraphEn
// Neo4J backing node
//-------------------------------------------------------------------------
pointcut arbitraryUserConstructorOfNodeBackedObject(NodeBacked entity) :
execution((@GraphEntity *).new(..)) &&
!execution((@GraphEntity *).new(Node)) &&
execution((@Graph.Entity *).new(..)) &&
!execution((@Graph.Entity *).new(Node)) &&
this(entity);
@@ -197,12 +194,12 @@ public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields<GraphEn
}
public RelationshipInfo forField(Field field) {
final Relationship relAnnotation = field.getAnnotation(Relationship.class);
Graph.Entity.Relationship relAnnotation = field.getAnnotation(Graph.Entity.Relationship.class);
if (isSingleRelationshipField(field)) {
Class<? extends NodeBacked> relatedType=(Class<? extends NodeBacked>)field.getType();
Class<? extends NodeBacked> relatedType = (Class<? extends NodeBacked>) 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<GraphEn
return new OneToNRelationshipInfo(DynamicRelationshipType.withName(relAnnotation.type()),
relAnnotation.direction().toNeo4jDir(), relAnnotation.elementClass(), graphEntityInstantiator);
}
throw new IllegalArgumentException("Not a Neo4j relationship field: "+field);
throw new IllegalArgumentException("Not a Neo4j relationship field: " + field);
}
private static boolean isSingleRelationshipField(Field f) {
@@ -220,8 +217,8 @@ public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields<GraphEn
private static boolean isOneToNRelationshipField(Field f) {
if (!Collection.class.isAssignableFrom(f.getType())) return false;
Relationship relationship=f.getAnnotation(Relationship.class);
return relationship!=null && NodeBacked.class.isAssignableFrom(relationship.elementClass()) && !relationship.elementClass().equals(NodeBacked.class);
Graph.Entity.Relationship relationship = f.getAnnotation(Graph.Entity.Relationship.class);
return relationship != null && NodeBacked.class.isAssignableFrom(relationship.elementClass()) && !relationship.elementClass().equals(NodeBacked.class);
}
}
@@ -232,7 +229,7 @@ public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields<GraphEn
private final Class<? extends NodeBacked> relatedType;
private final EntityInstantiator<NodeBacked, Node> graphEntityInstantiator;
public SingleRelationshipInfo(RelationshipType type, Direction direction, Class<? extends NodeBacked> clazz, EntityInstantiator<NodeBacked,Node> graphEntityInstantiator) {
public SingleRelationshipInfo(RelationshipType type, Direction direction, Class<? extends NodeBacked> clazz, EntityInstantiator<NodeBacked, Node> graphEntityInstantiator) {
this.type = type;
this.direction = direction;
this.relatedType = clazz;
@@ -244,7 +241,7 @@ public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields<GraphEn
throw new IllegalArgumentException("New value must be NodeBacked.");
}
Node entityNode = entity.getUnderlyingNode();
for ( org.neo4j.graphdb.Relationship relationship : entityNode.getRelationships(type, direction) ) {
for ( Relationship relationship : entityNode.getRelationships(type, direction) ) {
relationship.delete();
}
if (newVal == null) {
@@ -268,7 +265,7 @@ public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields<GraphEn
if (entityNode == null) {
throw new IllegalStateException("Entity must have a backing Node");
}
org.neo4j.graphdb.Relationship singleRelationship = entityNode.getSingleRelationship(type, direction);
Relationship singleRelationship = entityNode.getSingleRelationship(type, direction);
if (singleRelationship == null) {
return null;
@@ -344,7 +341,7 @@ public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields<GraphEn
newNodes.add(newNode);
}
}
for ( org.neo4j.graphdb.Relationship relationship : entityNode.getRelationships(type, direction) ) {
for ( Relationship relationship : entityNode.getRelationships(type, direction) ) {
if (!newNodes.remove(relationship.getOtherNode(entityNode)))
relationship.delete();
}
@@ -369,13 +366,13 @@ public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields<GraphEn
throw new IllegalStateException("Entity must have a backing Node");
}
Set<NodeBacked> result = new HashSet<NodeBacked>();
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);
}
}
}

View File

@@ -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<Graph.Relationship,RelationshipBacked> {
//-------------------------------------------------------------------------
// 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<NodeBacked, Node> graphEntityInstantiator;
@Autowired
public void init(EntityInstantiator<NodeBacked, Node> 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<? extends NodeBacked>) f.getType());
}
if (isEndNodeField(f)) {
Node endNode = entity.getUnderlyingEndNode();
if (endNode == null) {
return null;
}
return graphEntityInstantiator.createEntityFromState(endNode, (Class<? extends NodeBacked>) 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());
}
}

View File

@@ -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();
}

View File

@@ -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;
}
}

View File

@@ -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<Person> persons;
public void setPersons(Collection<Person> persons) {

View File

@@ -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<Person> 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;

View File

@@ -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<NodeBacked,Node> nodeInstantiator;
private EntityInstantiator<NodeBacked,Node> 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);

View File

@@ -69,8 +69,9 @@
Start datastore config
-->
<bean class="org.springframework.persistence.graph.neo4j.Neo4jNodeBacking"
factory-method="aspectOf" />
<bean class="org.springframework.persistence.graph.neo4j.Neo4jNodeBacking" factory-method="aspectOf" />
<bean class="org.springframework.persistence.graph.neo4j.Neo4jRelationshipBacking" factory-method="aspectOf" />
<!--
<bean class="org.springframework.persistence.graph.Neo4jSimpleNodePropertyStorageForeignStoreKeyManager"/>
@@ -83,11 +84,11 @@
<bean id="finderFactory" class="org.springframework.persistence.graph.neo4j.FinderFactory">
<constructor-arg ref="graphDbService" />
<constructor-arg ref="entityInstantiator" />
<constructor-arg ref="graphEntityInstantiator" />
</bean>
<bean id="entityInstantiator" class="org.springframework.persistence.graph.neo4j.Neo4jConstructorEntityInstantiator" />
<bean id="graphEntityInstantiator" name="gei" class="org.springframework.persistence.graph.neo4j.Neo4jConstructorGraphEntityInstantiator" />
<!--
-->
<bean id="transactionManager" class="org.springframework.persistence.transaction.NaiveDoubleTransactionManager" >