Added support for relationship entities. Support for having them as fields in graph entities is still missing though.
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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" >
|
||||
|
||||
Reference in New Issue
Block a user