Extracted inner classes from aspect to top level classes.
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
package org.springframework.persistence.graph.neo4j;
|
||||
|
||||
public interface FieldAccessor {
|
||||
|
||||
// Set entity field to newVal
|
||||
Object apply(NodeBacked entity, Object newVal);
|
||||
|
||||
// Read object from entity field
|
||||
Object readObject(NodeBacked entity);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package org.springframework.persistence.graph.neo4j;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.neo4j.graphdb.Direction;
|
||||
import org.neo4j.graphdb.DynamicRelationshipType;
|
||||
import org.neo4j.graphdb.Node;
|
||||
import org.neo4j.graphdb.Relationship;
|
||||
import org.springframework.persistence.graph.Graph;
|
||||
import org.springframework.persistence.support.EntityInstantiator;
|
||||
|
||||
public class FieldAccessorFactory {
|
||||
private final EntityInstantiator<NodeBacked, Node> graphEntityInstantiator;
|
||||
private final EntityInstantiator<RelationshipBacked, Relationship> relationshipEntityInstantiator;
|
||||
|
||||
public FieldAccessorFactory(EntityInstantiator<NodeBacked,Node> graphEntityInstantiator, EntityInstantiator<RelationshipBacked, Relationship> relationshipEntityInstantiator) {
|
||||
this.graphEntityInstantiator = graphEntityInstantiator;
|
||||
this.relationshipEntityInstantiator = relationshipEntityInstantiator;
|
||||
}
|
||||
|
||||
public FieldAccessor forField(Field field) {
|
||||
Graph.Entity.Relationship relAnnotation = field.getAnnotation(Graph.Entity.Relationship.class);
|
||||
if (isSingleRelationshipField(field)) {
|
||||
Class<? extends NodeBacked> relatedType = (Class<? extends NodeBacked>) field.getType();
|
||||
if (relAnnotation != null) {
|
||||
return new SingleRelationshipFieldAccessor(DynamicRelationshipType.withName(relAnnotation.type()),
|
||||
relAnnotation.direction().toNeo4jDir(), relatedType, graphEntityInstantiator);
|
||||
}
|
||||
return new SingleRelationshipFieldAccessor(DynamicRelationshipType.withName(getNeo4jPropertyName(field)),
|
||||
Direction.OUTGOING, relatedType, graphEntityInstantiator);
|
||||
}
|
||||
if (isOneToNRelationshipField(field)) {
|
||||
return new OneToNRelationshipFieldAccessor(DynamicRelationshipType.withName(relAnnotation.type()),
|
||||
relAnnotation.direction().toNeo4jDir(), relAnnotation.elementClass(), graphEntityInstantiator);
|
||||
}
|
||||
if (isReadOnlyOneToNRelationshipField(field)) {
|
||||
return new ReadOnlyOneToNRelationshipFieldAccessor(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 OneToNRelationshipEntityFieldAccessor(DynamicRelationshipType.withName(relEntityAnnotation.type()),
|
||||
relEntityAnnotation.direction().toNeo4jDir(), relEntityAnnotation.elementClass(), relationshipEntityInstantiator);
|
||||
}
|
||||
throw new IllegalArgumentException("Not a Neo4j relationship field: " + field);
|
||||
}
|
||||
|
||||
private static boolean isSingleRelationshipField(Field f) {
|
||||
return NodeBacked.class.isAssignableFrom(f.getType());
|
||||
}
|
||||
|
||||
private static boolean isOneToNRelationshipField(Field f) {
|
||||
if (!Collection.class.isAssignableFrom(f.getType())) return false;
|
||||
Graph.Entity.Relationship relAnnotation = f.getAnnotation(Graph.Entity.Relationship.class);
|
||||
return relAnnotation != null && NodeBacked.class.isAssignableFrom(relAnnotation.elementClass()) && !relAnnotation.elementClass().equals(NodeBacked.class);
|
||||
}
|
||||
|
||||
private static boolean isReadOnlyOneToNRelationshipField(Field f) {
|
||||
Graph.Entity.Relationship relAnnotation = f.getAnnotation(Graph.Entity.Relationship.class);
|
||||
return Iterable.class.equals(f.getType())
|
||||
&& relAnnotation != null
|
||||
&& !NodeBacked.class.equals(relAnnotation.elementClass());
|
||||
}
|
||||
|
||||
private static boolean isOneToNRelationshipEntityField(Field f) {
|
||||
Graph.Entity.RelationshipEntity relEntityAnnotation = f.getAnnotation(Graph.Entity.RelationshipEntity.class);
|
||||
return Iterable.class.isAssignableFrom(f.getType())
|
||||
&& relEntityAnnotation != null
|
||||
&& !RelationshipBacked.class.equals(relEntityAnnotation.elementClass());
|
||||
}
|
||||
|
||||
public static String getNeo4jPropertyName(Field field) {
|
||||
return String.format("%s.%s",field.getDeclaringClass().getSimpleName(),field.getName());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.springframework.persistence.graph.neo4j;
|
||||
|
||||
import java.util.AbstractSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
public class ManagedFieldAccessorSet<T> extends AbstractSet<T> {
|
||||
private final NodeBacked entity;
|
||||
final Set<T> delegate;
|
||||
private final FieldAccessor relationshipInfo;
|
||||
|
||||
public ManagedFieldAccessorSet(NodeBacked entity, Object newVal, FieldAccessor relationshipInfo) {
|
||||
this.entity = entity;
|
||||
this.relationshipInfo = relationshipInfo;
|
||||
delegate = (Set<T>) newVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return delegate.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return delegate.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(T e) {
|
||||
boolean res = delegate.add(e);
|
||||
if (res) {
|
||||
relationshipInfo.apply(entity, delegate);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
boolean res = delegate.remove(o);
|
||||
if (res) {
|
||||
relationshipInfo.apply(entity, delegate);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -41,10 +41,8 @@ public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields<Graph.E
|
||||
private GraphDatabaseService graphDatabaseService;
|
||||
|
||||
private EntityInstantiator<NodeBacked, Node> graphEntityInstantiator;
|
||||
|
||||
private GraphDatabaseUtil graphDatabaseUtil;
|
||||
|
||||
private RelationshipInfoFactory relationshipInfoFactory;
|
||||
private FieldAccessorFactory fieldAccessorFactory;
|
||||
|
||||
private EntityInstantiator<RelationshipBacked, Relationship> relationshipEntityInstantiator;
|
||||
|
||||
@@ -53,8 +51,7 @@ public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields<Graph.E
|
||||
this.graphDatabaseService = gds;
|
||||
this.graphEntityInstantiator = gei;
|
||||
this.relationshipEntityInstantiator = rei;
|
||||
this.graphDatabaseUtil = new GraphDatabaseUtil(gds);
|
||||
this.relationshipInfoFactory = new RelationshipInfoFactory(graphEntityInstantiator, relationshipEntityInstantiator);
|
||||
this.fieldAccessorFactory = new FieldAccessorFactory(graphEntityInstantiator, relationshipEntityInstantiator);
|
||||
}
|
||||
|
||||
|
||||
@@ -124,20 +121,19 @@ public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields<Graph.E
|
||||
return getUnderlyingNode().hashCode();
|
||||
}
|
||||
|
||||
|
||||
Object around(NodeBacked entity) : entityFieldGet(entity) {
|
||||
FieldSignature fieldSignature=(FieldSignature) thisJoinPoint.getSignature();
|
||||
Field f = fieldSignature.getField();
|
||||
|
||||
// TODO fix arrays, TODO serialize other types as byte[] or string (for indexing, querying) via Annotation
|
||||
if (isPropertyType(f.getType())) {
|
||||
String propName = getNeo4jPropertyName(f);
|
||||
String propName = FieldAccessorFactory.getNeo4jPropertyName(f);
|
||||
log.info("GET " + f + " <- Neo4J simple node property [" + propName + "]");
|
||||
return entity.getUnderlyingNode().getProperty(propName, null);
|
||||
}
|
||||
|
||||
RelationshipInfo relInfo = relationshipInfoFactory.forField(f);
|
||||
Object obj = relInfo.readObject(entity);
|
||||
FieldAccessor accessor = fieldAccessorFactory.forField(f);
|
||||
Object obj = accessor.readObject(entity);
|
||||
if (obj == null) {
|
||||
log.info("Ignored GET " + f + ": " + f.getType().getName() + " not primitive or GraphEntity");
|
||||
return proceed(entity);
|
||||
@@ -152,19 +148,19 @@ public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields<Graph.E
|
||||
// TODO fix arrays
|
||||
Class<?> fieldType = f.getType();
|
||||
if (isPropertyType(fieldType)) {
|
||||
String propName = getNeo4jPropertyName(f);
|
||||
String propName = FieldAccessorFactory.getNeo4jPropertyName(f);
|
||||
entity.getUnderlyingNode().setProperty(propName, newVal);
|
||||
log.info("SET " + f + " -> Neo4J simple node property [" + propName + "] with value=[" + newVal + "]");
|
||||
return proceed(entity, newVal);
|
||||
}
|
||||
|
||||
RelationshipInfo relInfo = relationshipInfoFactory.forField(f);
|
||||
if (relInfo == null) {
|
||||
FieldAccessor accessor = fieldAccessorFactory.forField(f);
|
||||
if (accessor == null) {
|
||||
log.info("Ignored SET " + f + ": " + f.getType().getName() + " not primitive or GraphEntity");
|
||||
return proceed(entity, newVal);
|
||||
}
|
||||
log.info("SET " + f + " -> Neo4J relationship with value=[" + newVal + "]");
|
||||
Object result = relInfo.apply(entity, newVal);
|
||||
Object result = accessor.apply(entity, newVal);
|
||||
return proceed(entity,result);
|
||||
} catch(NotInTransactionException e) {
|
||||
throw new InvalidDataAccessResourceUsageException("Not in a Neo4j transaction.", e);
|
||||
@@ -181,301 +177,4 @@ public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields<Graph.E
|
||||
|| (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());
|
||||
}
|
||||
|
||||
public static interface RelationshipInfo {
|
||||
// Set entity field to newVal
|
||||
Object apply(NodeBacked entity, Object newVal);
|
||||
|
||||
// Read object from entity field
|
||||
Object readObject(NodeBacked entity);
|
||||
}
|
||||
|
||||
public static class RelationshipInfoFactory {
|
||||
private final EntityInstantiator<NodeBacked, Node> graphEntityInstantiator;
|
||||
private final EntityInstantiator<RelationshipBacked, Relationship> relationshipEntityInstantiator;
|
||||
|
||||
public RelationshipInfoFactory(EntityInstantiator<NodeBacked,Node> graphEntityInstantiator, EntityInstantiator<RelationshipBacked, Relationship> relationshipEntityInstantiator) {
|
||||
this.graphEntityInstantiator = graphEntityInstantiator;
|
||||
this.relationshipEntityInstantiator = relationshipEntityInstantiator;
|
||||
}
|
||||
|
||||
public RelationshipInfo forField(Field field) {
|
||||
Graph.Entity.Relationship relAnnotation = field.getAnnotation(Graph.Entity.Relationship.class);
|
||||
if (isSingleRelationshipField(field)) {
|
||||
Class<? extends NodeBacked> relatedType = (Class<? extends NodeBacked>) field.getType();
|
||||
if (relAnnotation != null) {
|
||||
return new SingleRelationshipInfo(DynamicRelationshipType.withName(relAnnotation.type()),
|
||||
relAnnotation.direction().toNeo4jDir(), relatedType, graphEntityInstantiator);
|
||||
}
|
||||
return new SingleRelationshipInfo(DynamicRelationshipType.withName(getNeo4jPropertyName(field)),
|
||||
Direction.OUTGOING, relatedType, graphEntityInstantiator);
|
||||
}
|
||||
if (isOneToNRelationshipField(field)) {
|
||||
return new OneToNRelationshipInfo(DynamicRelationshipType.withName(relAnnotation.type()),
|
||||
relAnnotation.direction().toNeo4jDir(), relAnnotation.elementClass(), graphEntityInstantiator);
|
||||
}
|
||||
if (isReadOnlyOneToNRelationshipField(field)) {
|
||||
return new ReadOnlyOneToNRelationshipInfo(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(), relationshipEntityInstantiator);
|
||||
}
|
||||
throw new IllegalArgumentException("Not a Neo4j relationship field: " + field);
|
||||
}
|
||||
|
||||
private static boolean isSingleRelationshipField(Field f) {
|
||||
return NodeBacked.class.isAssignableFrom(f.getType());
|
||||
}
|
||||
|
||||
private static boolean isOneToNRelationshipField(Field f) {
|
||||
if (!Collection.class.isAssignableFrom(f.getType())) return false;
|
||||
Graph.Entity.Relationship relAnnotation = f.getAnnotation(Graph.Entity.Relationship.class);
|
||||
return relAnnotation != null && NodeBacked.class.isAssignableFrom(relAnnotation.elementClass()) && !relAnnotation.elementClass().equals(NodeBacked.class);
|
||||
}
|
||||
|
||||
private boolean isReadOnlyOneToNRelationshipField(Field f) {
|
||||
Graph.Entity.Relationship relAnnotation = f.getAnnotation(Graph.Entity.Relationship.class);
|
||||
return Iterable.class.equals(f.getType())
|
||||
&& relAnnotation != null
|
||||
&& !NodeBacked.class.equals(relAnnotation.elementClass());
|
||||
}
|
||||
|
||||
private boolean isOneToNRelationshipEntityField(Field f) {
|
||||
Graph.Entity.RelationshipEntity relEntityAnnotation = f.getAnnotation(Graph.Entity.RelationshipEntity.class);
|
||||
return Iterable.class.isAssignableFrom(f.getType())
|
||||
&& relEntityAnnotation != null
|
||||
&& !RelationshipBacked.class.equals(relEntityAnnotation.elementClass());
|
||||
}
|
||||
}
|
||||
|
||||
public static class SingleRelationshipInfo implements RelationshipInfo {
|
||||
|
||||
private final RelationshipType type;
|
||||
private final Direction direction;
|
||||
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) {
|
||||
this.type = type;
|
||||
this.direction = direction;
|
||||
this.relatedType = clazz;
|
||||
this.graphEntityInstantiator = graphEntityInstantiator;
|
||||
}
|
||||
|
||||
public Object apply(NodeBacked entity, Object newVal) {
|
||||
if (newVal != null && !(newVal instanceof NodeBacked)) {
|
||||
throw new IllegalArgumentException("New value must be NodeBacked.");
|
||||
}
|
||||
Node entityNode = entity.getUnderlyingNode();
|
||||
for ( Relationship relationship : entityNode.getRelationships(type, direction) ) {
|
||||
relationship.delete();
|
||||
}
|
||||
if (newVal == null) {
|
||||
return null;
|
||||
}
|
||||
Node targetNode = ((NodeBacked) newVal).getUnderlyingNode();
|
||||
if (entityNode.equals(targetNode)) {
|
||||
throw new InvalidDataAccessApiUsageException("Cannot create circular reference.");
|
||||
}
|
||||
switch(direction) {
|
||||
case OUTGOING : entityNode.createRelationshipTo(targetNode, type); break;
|
||||
case INCOMING : targetNode.createRelationshipTo(entityNode, type); break;
|
||||
default : throw new IllegalArgumentException("invalid direction " + direction);
|
||||
}
|
||||
return newVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object readObject(NodeBacked entity) {
|
||||
Node entityNode = entity.getUnderlyingNode();
|
||||
if (entityNode == null) {
|
||||
throw new IllegalStateException("Entity must have a backing Node");
|
||||
}
|
||||
Relationship singleRelationship = entityNode.getSingleRelationship(type, direction);
|
||||
|
||||
if (singleRelationship == null) {
|
||||
return null;
|
||||
}
|
||||
Node targetNode = singleRelationship.getOtherNode(entityNode);
|
||||
return graphEntityInstantiator.createEntityFromState(targetNode, relatedType);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class OneToNRelationshipInfo implements RelationshipInfo {
|
||||
|
||||
private final RelationshipType type;
|
||||
private final Direction direction;
|
||||
private final Class<? extends NodeBacked> relatedType;
|
||||
private final EntityInstantiator<NodeBacked, Node> graphEntityInstantiator;
|
||||
|
||||
public OneToNRelationshipInfo(RelationshipType type, Direction direction, Class<? extends NodeBacked> elementClass, EntityInstantiator<NodeBacked, Node> graphEntityInstantiator) {
|
||||
this.type = type;
|
||||
this.direction = direction;
|
||||
this.relatedType = elementClass;
|
||||
this.graphEntityInstantiator = graphEntityInstantiator;
|
||||
}
|
||||
|
||||
public Object apply(final NodeBacked entity, final Object newVal) {
|
||||
Node entityNode = entity.getUnderlyingNode();
|
||||
|
||||
Set<Node> newNodes=new HashSet<Node>();
|
||||
if (newVal != null) {
|
||||
if (!(newVal instanceof Set)) {
|
||||
throw new IllegalArgumentException("New value must be a Set, was: " + newVal.getClass());
|
||||
}
|
||||
Set<Object> set = (Set<Object>) newVal;
|
||||
for (Object obj : set) {
|
||||
if (!(obj instanceof NodeBacked)) {
|
||||
throw new IllegalArgumentException("New value elements must be NodeBacked.");
|
||||
}
|
||||
Node newNode=((NodeBacked) obj).getUnderlyingNode();
|
||||
if (entityNode.equals(newNode)) {
|
||||
throw new InvalidDataAccessApiUsageException("Cannot create circular reference.");
|
||||
}
|
||||
newNodes.add(newNode);
|
||||
}
|
||||
}
|
||||
for ( Relationship relationship : entityNode.getRelationships(type, direction) ) {
|
||||
if (!newNodes.remove(relationship.getOtherNode(entityNode)))
|
||||
relationship.delete();
|
||||
}
|
||||
if (newVal == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (Node newNode : newNodes) {
|
||||
switch(direction) {
|
||||
case OUTGOING : entityNode.createRelationshipTo(newNode, type); break;
|
||||
case INCOMING : newNode.createRelationshipTo(entityNode, type); break;
|
||||
default : throw new IllegalArgumentException("invalid direction " + direction);
|
||||
}
|
||||
}
|
||||
return new ManagedSet<NodeBacked>(entity, newVal,this); // TODO managedSet that for each mutating method calls this apply (todo use AspectJ to handle that?)
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object readObject(NodeBacked entity) {
|
||||
Node entityNode = entity.getUnderlyingNode();
|
||||
if (entityNode == null) {
|
||||
throw new IllegalStateException("Entity must have a backing Node");
|
||||
}
|
||||
Set<NodeBacked> result = new HashSet<NodeBacked>();
|
||||
for (Relationship rel : entityNode.getRelationships(type, direction)) {
|
||||
result.add(graphEntityInstantiator.createEntityFromState(rel.getOtherNode(entityNode), relatedType));
|
||||
}
|
||||
return new ManagedSet<NodeBacked>(entity, result, this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ReadOnlyOneToNRelationshipInfo implements RelationshipInfo {
|
||||
|
||||
private final RelationshipType type;
|
||||
private final Direction direction;
|
||||
private final Class<? extends NodeBacked> relatedType;
|
||||
private final EntityInstantiator<NodeBacked, Node> graphEntityInstantiator;
|
||||
|
||||
public ReadOnlyOneToNRelationshipInfo(RelationshipType type, Direction direction, Class<? extends NodeBacked> elementClass, EntityInstantiator<NodeBacked, Node> graphEntityInstantiator) {
|
||||
this.type = type;
|
||||
this.direction = direction;
|
||||
this.relatedType = elementClass;
|
||||
this.graphEntityInstantiator = graphEntityInstantiator;
|
||||
}
|
||||
|
||||
public Object apply(final NodeBacked entity, final Object newVal) {
|
||||
throw new InvalidDataAccessApiUsageException("Cannot set read-only relationship entity field.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object readObject(NodeBacked entity) {
|
||||
Node entityNode = entity.getUnderlyingNode();
|
||||
if (entityNode == null) {
|
||||
throw new IllegalStateException("Entity must have a backing Node");
|
||||
}
|
||||
Set<NodeBacked> result = new HashSet<NodeBacked>();
|
||||
for (Relationship rel : entityNode.getRelationships(type, direction)) {
|
||||
result.add(graphEntityInstantiator.createEntityFromState(rel.getOtherNode(entityNode), relatedType));
|
||||
}
|
||||
return new ManagedSet<NodeBacked>(entity, result, this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class OneToNRelationshipEntityInfo implements RelationshipInfo {
|
||||
|
||||
private final RelationshipType type;
|
||||
private final Direction direction;
|
||||
private final Class<? extends RelationshipBacked> elementClass;
|
||||
private final EntityInstantiator<RelationshipBacked, Relationship> relationshipEntityInstantiator;
|
||||
|
||||
public OneToNRelationshipEntityInfo(RelationshipType type, Direction direction, Class<? extends RelationshipBacked> elementClass, EntityInstantiator<RelationshipBacked, Relationship> relationshipEntityInstantiator) {
|
||||
this.type = type;
|
||||
this.direction = direction;
|
||||
this.elementClass = elementClass;
|
||||
this.relationshipEntityInstantiator = relationshipEntityInstantiator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object apply(NodeBacked entity, Object newVal) {
|
||||
throw new InvalidDataAccessApiUsageException("Cannot set read-only relationship entity field.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object readObject(NodeBacked entity) {
|
||||
Set<RelationshipBacked> result = new HashSet<RelationshipBacked>();
|
||||
for (Relationship rel : entity.getUnderlyingNode().getRelationships(type, direction)) {
|
||||
result.add(relationshipEntityInstantiator.createEntityFromState(rel, elementClass));
|
||||
}
|
||||
return new ManagedSet<RelationshipBacked>(entity, result, this);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ManagedSet<T> extends AbstractSet<T> {
|
||||
private final NodeBacked entity;
|
||||
final Set<T> delegate;
|
||||
private final RelationshipInfo relationshipInfo;
|
||||
|
||||
private ManagedSet(NodeBacked entity, Object newVal, RelationshipInfo relationshipInfo) {
|
||||
this.entity = entity;
|
||||
this.relationshipInfo = relationshipInfo;
|
||||
delegate = (Set<T>) newVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return delegate.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return delegate.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(T e) {
|
||||
boolean res = delegate.add(e);
|
||||
if (res) {
|
||||
relationshipInfo.apply(entity, delegate);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
boolean res = delegate.remove(o);
|
||||
if (res) {
|
||||
relationshipInfo.apply(entity, delegate);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package org.springframework.persistence.graph.neo4j;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.neo4j.graphdb.Direction;
|
||||
import org.neo4j.graphdb.Relationship;
|
||||
import org.neo4j.graphdb.RelationshipType;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.persistence.support.EntityInstantiator;
|
||||
|
||||
public class OneToNRelationshipEntityFieldAccessor implements FieldAccessor {
|
||||
|
||||
private final RelationshipType type;
|
||||
private final Direction direction;
|
||||
private final Class<? extends RelationshipBacked> elementClass;
|
||||
private final EntityInstantiator<RelationshipBacked, Relationship> relationshipEntityInstantiator;
|
||||
|
||||
public OneToNRelationshipEntityFieldAccessor(RelationshipType type, Direction direction, Class<? extends RelationshipBacked> elementClass, EntityInstantiator<RelationshipBacked, Relationship> relationshipEntityInstantiator) {
|
||||
this.type = type;
|
||||
this.direction = direction;
|
||||
this.elementClass = elementClass;
|
||||
this.relationshipEntityInstantiator = relationshipEntityInstantiator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object apply(NodeBacked entity, Object newVal) {
|
||||
throw new InvalidDataAccessApiUsageException("Cannot set read-only relationship entity field.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object readObject(NodeBacked entity) {
|
||||
Set<RelationshipBacked> result = new HashSet<RelationshipBacked>();
|
||||
for (Relationship rel : entity.getUnderlyingNode().getRelationships(type, direction)) {
|
||||
result.add(relationshipEntityInstantiator.createEntityFromState(rel, elementClass));
|
||||
}
|
||||
return new ManagedFieldAccessorSet<RelationshipBacked>(entity, result, this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package org.springframework.persistence.graph.neo4j;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.neo4j.graphdb.Direction;
|
||||
import org.neo4j.graphdb.Node;
|
||||
import org.neo4j.graphdb.Relationship;
|
||||
import org.neo4j.graphdb.RelationshipType;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.persistence.graph.neo4j.ManagedFieldAccessorSet;
|
||||
import org.springframework.persistence.support.EntityInstantiator;
|
||||
|
||||
public class OneToNRelationshipFieldAccessor implements FieldAccessor {
|
||||
|
||||
private final RelationshipType type;
|
||||
private final Direction direction;
|
||||
private final Class<? extends NodeBacked> relatedType;
|
||||
private final EntityInstantiator<NodeBacked, Node> graphEntityInstantiator;
|
||||
|
||||
public OneToNRelationshipFieldAccessor(RelationshipType type, Direction direction, Class<? extends NodeBacked> elementClass, EntityInstantiator<NodeBacked, Node> graphEntityInstantiator) {
|
||||
this.type = type;
|
||||
this.direction = direction;
|
||||
this.relatedType = elementClass;
|
||||
this.graphEntityInstantiator = graphEntityInstantiator;
|
||||
}
|
||||
|
||||
public Object apply(final NodeBacked entity, final Object newVal) {
|
||||
Node entityNode = entity.getUnderlyingNode();
|
||||
|
||||
Set<Node> newNodes=new HashSet<Node>();
|
||||
if (newVal != null) {
|
||||
if (!(newVal instanceof Set)) {
|
||||
throw new IllegalArgumentException("New value must be a Set, was: " + newVal.getClass());
|
||||
}
|
||||
Set<Object> set = (Set<Object>) newVal;
|
||||
for (Object obj : set) {
|
||||
if (!(obj instanceof NodeBacked)) {
|
||||
throw new IllegalArgumentException("New value elements must be NodeBacked.");
|
||||
}
|
||||
Node newNode=((NodeBacked) obj).getUnderlyingNode();
|
||||
if (entityNode.equals(newNode)) {
|
||||
throw new InvalidDataAccessApiUsageException("Cannot create circular reference.");
|
||||
}
|
||||
newNodes.add(newNode);
|
||||
}
|
||||
}
|
||||
for ( Relationship relationship : entityNode.getRelationships(type, direction) ) {
|
||||
if (!newNodes.remove(relationship.getOtherNode(entityNode)))
|
||||
relationship.delete();
|
||||
}
|
||||
if (newVal == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (Node newNode : newNodes) {
|
||||
switch(direction) {
|
||||
case OUTGOING : entityNode.createRelationshipTo(newNode, type); break;
|
||||
case INCOMING : newNode.createRelationshipTo(entityNode, type); break;
|
||||
default : throw new IllegalArgumentException("invalid direction " + direction);
|
||||
}
|
||||
}
|
||||
return new ManagedFieldAccessorSet<NodeBacked>(entity, newVal, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object readObject(NodeBacked entity) {
|
||||
Node entityNode = entity.getUnderlyingNode();
|
||||
if (entityNode == null) {
|
||||
throw new IllegalStateException("Entity must have a backing Node");
|
||||
}
|
||||
Set<NodeBacked> result = new HashSet<NodeBacked>();
|
||||
for (Relationship rel : entityNode.getRelationships(type, direction)) {
|
||||
result.add(graphEntityInstantiator.createEntityFromState(rel.getOtherNode(entityNode), relatedType));
|
||||
}
|
||||
return new ManagedFieldAccessorSet<NodeBacked>(entity, result, this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.springframework.persistence.graph.neo4j;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.neo4j.graphdb.Direction;
|
||||
import org.neo4j.graphdb.Node;
|
||||
import org.neo4j.graphdb.Relationship;
|
||||
import org.neo4j.graphdb.RelationshipType;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.persistence.support.EntityInstantiator;
|
||||
|
||||
public class ReadOnlyOneToNRelationshipFieldAccessor implements FieldAccessor {
|
||||
|
||||
private final RelationshipType type;
|
||||
private final Direction direction;
|
||||
private final Class<? extends NodeBacked> relatedType;
|
||||
private final EntityInstantiator<NodeBacked, Node> graphEntityInstantiator;
|
||||
|
||||
public ReadOnlyOneToNRelationshipFieldAccessor(RelationshipType type, Direction direction, Class<? extends NodeBacked> elementClass, EntityInstantiator<NodeBacked, Node> graphEntityInstantiator) {
|
||||
this.type = type;
|
||||
this.direction = direction;
|
||||
this.relatedType = elementClass;
|
||||
this.graphEntityInstantiator = graphEntityInstantiator;
|
||||
}
|
||||
|
||||
public Object apply(final NodeBacked entity, final Object newVal) {
|
||||
throw new InvalidDataAccessApiUsageException("Cannot set read-only relationship entity field.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object readObject(NodeBacked entity) {
|
||||
Node entityNode = entity.getUnderlyingNode();
|
||||
if (entityNode == null) {
|
||||
throw new IllegalStateException("Entity must have a backing Node");
|
||||
}
|
||||
Set<NodeBacked> result = new HashSet<NodeBacked>();
|
||||
for (Relationship rel : entityNode.getRelationships(type, direction)) {
|
||||
result.add(graphEntityInstantiator.createEntityFromState(rel.getOtherNode(entityNode), relatedType));
|
||||
}
|
||||
return new ManagedFieldAccessorSet<NodeBacked>(entity, result, this);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.springframework.persistence.graph.neo4j;
|
||||
|
||||
import org.neo4j.graphdb.Direction;
|
||||
import org.neo4j.graphdb.Node;
|
||||
import org.neo4j.graphdb.Relationship;
|
||||
import org.neo4j.graphdb.RelationshipType;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.persistence.support.EntityInstantiator;
|
||||
|
||||
public class SingleRelationshipFieldAccessor implements FieldAccessor {
|
||||
|
||||
private final RelationshipType type;
|
||||
private final Direction direction;
|
||||
private final Class<? extends NodeBacked> relatedType;
|
||||
private final EntityInstantiator<NodeBacked, Node> graphEntityInstantiator;
|
||||
|
||||
public SingleRelationshipFieldAccessor(RelationshipType type, Direction direction, Class<? extends NodeBacked> clazz, EntityInstantiator<NodeBacked, Node> graphEntityInstantiator) {
|
||||
this.type = type;
|
||||
this.direction = direction;
|
||||
this.relatedType = clazz;
|
||||
this.graphEntityInstantiator = graphEntityInstantiator;
|
||||
}
|
||||
|
||||
public Object apply(NodeBacked entity, Object newVal) {
|
||||
if (newVal != null && !(newVal instanceof NodeBacked)) {
|
||||
throw new IllegalArgumentException("New value must be NodeBacked.");
|
||||
}
|
||||
Node entityNode = entity.getUnderlyingNode();
|
||||
for ( Relationship relationship : entityNode.getRelationships(type, direction) ) {
|
||||
relationship.delete();
|
||||
}
|
||||
if (newVal == null) {
|
||||
return null;
|
||||
}
|
||||
Node targetNode = ((NodeBacked) newVal).getUnderlyingNode();
|
||||
if (entityNode.equals(targetNode)) {
|
||||
throw new InvalidDataAccessApiUsageException("Cannot create circular reference.");
|
||||
}
|
||||
switch(direction) {
|
||||
case OUTGOING : entityNode.createRelationshipTo(targetNode, type); break;
|
||||
case INCOMING : targetNode.createRelationshipTo(entityNode, type); break;
|
||||
default : throw new IllegalArgumentException("invalid direction " + direction);
|
||||
}
|
||||
return newVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object readObject(NodeBacked entity) {
|
||||
Node entityNode = entity.getUnderlyingNode();
|
||||
if (entityNode == null) {
|
||||
throw new IllegalStateException("Entity must have a backing Node");
|
||||
}
|
||||
Relationship singleRelationship = entityNode.getSingleRelationship(type, direction);
|
||||
|
||||
if (singleRelationship == null) {
|
||||
return null;
|
||||
}
|
||||
Node targetNode = singleRelationship.getOtherNode(entityNode);
|
||||
return graphEntityInstantiator.createEntityFromState(targetNode, relatedType);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user