Added basic relationship entity support. Read access tbd.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user