Added basic relationship entity support. Read access tbd.

This commit is contained in:
David Montag
2010-08-24 11:08:16 +02:00
parent 8143657e41
commit 34d95d3e34
7 changed files with 101 additions and 111 deletions

View File

@@ -6,6 +6,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.persistence.graph.neo4j.NodeBacked;
import org.springframework.persistence.graph.neo4j.RelationshipBacked;
public @interface Graph {
@@ -19,7 +20,7 @@ public @interface Graph {
String type();
Direction direction();
Direction direction() default Direction.OUTGOING;
Class<? extends NodeBacked> elementClass() default NodeBacked.class;
}
@@ -29,7 +30,9 @@ public @interface Graph {
public @interface RelationshipEntity {
String type();
Direction direction();
Direction direction() default Direction.OUTGOING;
Class<? extends RelationshipBacked> elementClass() default RelationshipBacked.class;
}
}

View File

@@ -82,7 +82,7 @@ public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields<Graph.E
private void postEntityCreation(NodeBacked entity) {
Node subReference = Neo4jHelper.findSubreferenceNode(entity.getClass(), graphDatabaseService);
entity.getUnderlyingNode().createRelationshipTo(subReference, Neo4jHelper.INSTANCE_OF_RELATIONSHIP_TYPE);
graphDatabaseUtil.incrementAndGetCounter(subReference, Neo4jHelper.SUBREFERENCE_NODE_COUNTER_KEY);
GraphDatabaseUtil.incrementAndGetCounter(subReference, Neo4jHelper.SUBREFERENCE_NODE_COUNTER_KEY);
}
// Introduced field
@@ -95,6 +95,10 @@ public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields<Graph.E
public Node NodeBacked.getUnderlyingNode() {
return underlyingNode;
}
public Relationship NodeBacked.relateTo(NodeBacked nb, RelationshipType type) {
return this.underlyingNode.createRelationshipTo(nb.getUnderlyingNode(), type);
}
public long NodeBacked.getId() {
return underlyingNode.getId();
@@ -208,9 +212,19 @@ public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields<Graph.E
return new OneToNRelationshipInfo(DynamicRelationshipType.withName(relAnnotation.type()),
relAnnotation.direction().toNeo4jDir(), relAnnotation.elementClass(), graphEntityInstantiator);
}
if (isOneToNRelationshipEntityField(field)) {
Graph.Entity.RelationshipEntity relEntityAnnotation = field.getAnnotation(Graph.Entity.RelationshipEntity.class);
return new OneToNRelationshipEntityInfo(DynamicRelationshipType.withName(relEntityAnnotation.type()),
relEntityAnnotation.direction().toNeo4jDir(), relEntityAnnotation.elementClass());
}
throw new IllegalArgumentException("Not a Neo4j relationship field: " + field);
}
private boolean isOneToNRelationshipEntityField(Field f) {
if (!Iterable.class.isAssignableFrom(f.getType())) return false;
return f.isAnnotationPresent(Graph.Entity.RelationshipEntity.class);
}
private static boolean isSingleRelationshipField(Field f) {
return NodeBacked.class.isAssignableFrom(f.getType());
}
@@ -374,5 +388,30 @@ public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields<Graph.E
}
}
public static class OneToNRelationshipEntityInfo implements RelationshipInfo {
private final RelationshipType type;
private final Direction direction;
private final Class<? extends RelationshipBacked> elementClass;
public OneToNRelationshipEntityInfo(RelationshipType type, Direction direction, Class<? extends RelationshipBacked> elementClass) {
this.type = type;
this.direction = direction;
this.elementClass = elementClass;
}
@Override
public Object apply(NodeBacked entity, Object newVal) {
throw new InvalidDataAccessApiUsageException("Cannot set read-only relationship entity field.");
}
@Override
public Object readObject(NodeBacked entity) {
// TODO Auto-generated method stub
return null;
}
}
}

View File

@@ -50,34 +50,14 @@ public aspect Neo4jRelationshipBacking extends AbstractTypeAnnotatingMixinFields
// 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();
@@ -106,14 +86,14 @@ public aspect Neo4jRelationshipBacking extends AbstractTypeAnnotatingMixinFields
Field f = fieldSignature.getField();
if (isStartNodeField(f)) {
Node startNode = entity.getUnderlyingStartNode();
Node startNode = entity.getUnderlyingRelationship().getStartNode();
if (startNode == null) {
return null;
}
return graphEntityInstantiator.createEntityFromState(startNode, (Class<? extends NodeBacked>) f.getType());
}
if (isEndNodeField(f)) {
Node endNode = entity.getUnderlyingEndNode();
Node endNode = entity.getUnderlyingRelationship().getEndNode();
if (endNode == null) {
return null;
}
@@ -138,23 +118,8 @@ public aspect Neo4jRelationshipBacking extends AbstractTypeAnnotatingMixinFields
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 (isStartNodeField(f) || isEndNodeField(f)) {
throw new InvalidDataAccessApiUsageException("Cannot change start node or end node of existing relationship.");
}
if (isPropertyType(f.getType())) {
Relationship rel = entity.getUnderlyingRelationship();

View File

@@ -1,6 +1,8 @@
package org.springframework.persistence.graph.neo4j;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
/**
* Interface introduced to objects annotated with GraphEntity
@@ -12,5 +14,7 @@ public interface NodeBacked {
Node getUnderlyingNode();
void setUnderlyingNode(Node n);
Relationship relateTo(NodeBacked nb, RelationshipType type);
}

View File

@@ -1,6 +1,5 @@
package org.springframework.persistence.graph.neo4j;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
public interface RelationshipBacked {
@@ -8,13 +7,4 @@ public interface RelationshipBacked {
Relationship getUnderlyingRelationship();
void setUnderlyingRelationship(Relationship r);
void setUnderlyingStartNode(Node n);
Node getUnderlyingStartNode();
void setUnderlyingEndNode(Node n);
Node getUnderlyingEndNode();
}

View File

@@ -1,5 +1,9 @@
package org.springframework.persistence.test;
import java.util.Collection;
import org.neo4j.graphdb.DynamicRelationshipType;
import org.neo4j.graphdb.Relationship;
import org.springframework.persistence.graph.Direction;
import org.springframework.persistence.graph.Graph;
@@ -16,12 +20,15 @@ public class Person {
private Person spouse;
@Graph.Entity.Relationship(type="mother", direction=Direction.OUTGOING)
@Graph.Entity.Relationship(type = "mother", direction = Direction.OUTGOING)
private Person mother;
@Graph.Entity.Relationship(type="boss", direction=Direction.INCOMING)
@Graph.Entity.Relationship(type = "boss", direction = Direction.INCOMING)
private Person boss;
@Graph.Entity.RelationshipEntity(type = "knows", elementClass = Friendship.class)
private Iterable<Friendship> friendships;
// @Property(serialize=SerializationPolicy.STRING, index=true, queryable=true, removeOnReset=true)
// Date birthday;
@@ -85,4 +92,19 @@ public class Person {
public String toString() {
return name;
}
public Iterable<Friendship> getFriendships() {
return friendships;
}
public void setFriendships(Iterable<Friendship> f) {
friendships = f;
}
public Friendship knows(Person p) {
Relationship rel = this.getUnderlyingNode().createRelationshipTo(p.getUnderlyingNode(), DynamicRelationshipType.withName("knows"));
Friendship friendship = new Friendship();
friendship.setUnderlyingRelationship(rel);
return friendship;
}
}

View File

@@ -5,6 +5,7 @@ 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;
@@ -50,8 +51,6 @@ public class Neo4jGraphPersistenceTest {
@Autowired
private FinderFactory finderFactory;
private Long insertedId = 0L;
@Test
public void testStuffWasAutowired() {
Assert.assertNotNull( graphDatabaseService );
@@ -70,9 +69,7 @@ public class Neo4jGraphPersistenceTest {
Person p = new Person("Rod", 39);
Assert.assertEquals(p.getName(), p.getUnderlyingNode().getProperty("Person.name"));
Assert.assertEquals(p.getAge(), p.getUnderlyingNode().getProperty("Person.age"));
insertedId = p.getId();
Node n = findPersonTestNode();
Person found = graphEntityInstantiator.createEntityFromState(n, Person.class);
Person found = graphEntityInstantiator.createEntityFromState(graphDatabaseService.getNodeById(p.getId()), Person.class);
Assert.assertEquals("Rod", found.getUnderlyingNode().getProperty("Person.name"));
Assert.assertEquals(39, found.getUnderlyingNode().getProperty("Person.age"));
}
@@ -281,8 +278,8 @@ public class Neo4jGraphPersistenceTest {
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);
Friendship f = p.knows(p2);
Relationship rel = p.getUnderlyingNode().getSingleRelationship(DynamicRelationshipType.withName("knows"), Direction.OUTGOING);
Assert.assertEquals(f.getUnderlyingRelationship(), rel);
Assert.assertEquals(p2.getUnderlyingNode(), rel.getEndNode());
}
@@ -292,7 +289,7 @@ public class Neo4jGraphPersistenceTest {
public void testRelationshipSetProperty() {
Person p = new Person("Michael", 35);
Person p2 = new Person("David", 25);
Friendship f = new Friendship(p, p2);
Friendship f = p.knows(p2);
f.setYears(1);
Assert.assertEquals(1, f.getUnderlyingRelationship().getProperty("Friendship.years"));
}
@@ -302,67 +299,37 @@ public class Neo4jGraphPersistenceTest {
public void testRelationshipGetProperty() {
Person p = new Person("Michael", 35);
Person p2 = new Person("David", 25);
Friendship f = new Friendship(p, p2);
Friendship f = p.knows(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);
Friendship f = p.knows(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);
@Test
@Ignore
@Transactional
public void testRelationshipGetEntities() {
Person p = new Person("Michael", 35);
Person p2 = new Person("David", 25);
Person p3 = new Person("Emil", 32);
Friendship f2 = p.knows(p2);
Friendship f3 = p.knows(p3);
Assert.assertEquals(new HashSet<Friendship>(Arrays.asList(f2, f3)), IteratorUtil.addToCollection(p.getFriendships().iterator(), new HashSet<Friendship>()));
}
@Test(expected = InvalidDataAccessApiUsageException.class)
@Transactional
public void testRelationshipSetEntitiesShouldThrowException() {
Person p = new Person("Michael", 35);
p.setFriendships(new HashSet<Friendship>());
}
}