From 51b4d64b9f84b6d3068b5d784b7eacaac97e7f00 Mon Sep 17 00:00:00 2001 From: Michael Hunger Date: Sat, 11 Sep 2010 13:56:26 +0200 Subject: [PATCH] refactoring relationship-accessors --- .../fieldaccess/AbstractFieldAccessor.java | 93 +++++++++++++++++++ .../neo4j/fieldaccess/FieldAccessor.java | 6 +- .../fieldaccess/FieldAccessorFactory.java | 59 +++++++++--- .../fieldaccess/ManagedFieldAccessorSet.java | 53 +++++++---- .../Neo4jConversionServiceFactoryBean.java | 7 +- ...OneToNRelationshipEntityFieldAccessor.java | 4 +- .../OneToNRelationshipFieldAccessor.java | 4 +- ...adOnlyOneToNRelationshipFieldAccessor.java | 4 +- .../SingleRelationshipFieldAccessor.java | 73 +++++---------- .../graph/neo4j/spi/node/Neo4jNodeBacking.aj | 7 +- 10 files changed, 208 insertions(+), 102 deletions(-) create mode 100644 src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/AbstractFieldAccessor.java diff --git a/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/AbstractFieldAccessor.java b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/AbstractFieldAccessor.java new file mode 100644 index 000000000..536399c0b --- /dev/null +++ b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/AbstractFieldAccessor.java @@ -0,0 +1,93 @@ +package org.springframework.datastore.graph.neo4j.fieldaccess; + +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.datastore.graph.api.NodeBacked; +import org.springframework.persistence.support.EntityInstantiator; + +/** + * @author Michael Hunger + * @since 11.09.2010 + */ +public abstract class AbstractFieldAccessor implements FieldAccessor { + protected final RelationshipType type; + protected final Direction direction; + protected final Class relatedType; + protected final EntityInstantiator graphEntityInstantiator; + + public AbstractFieldAccessor(Class clazz, EntityInstantiator graphEntityInstantiator, Direction direction, RelationshipType type) { + this.relatedType = clazz; + this.graphEntityInstantiator = graphEntityInstantiator; + this.direction = direction; + this.type = type; + } + + protected NodeBacked createSingleRelationship(NodeBacked entity, NodeBacked target) { + if (target==null) return null; + Node entityNode = entity.getUnderlyingNode(); + Node targetNode = target.getUnderlyingNode(); + switch(direction) { + case OUTGOING : entityNode.createRelationshipTo(targetNode, type); break; + case INCOMING : targetNode.createRelationshipTo(entityNode, type); break; + default : throw new IllegalArgumentException("invalid direction " + direction); + } + return target; + } + + protected void checkCircularReference(NodeBacked entity, NodeBacked target) { + Node entityNode = entity.getUnderlyingNode(); + Node targetNode = target.getUnderlyingNode(); + if (entityNode.equals(targetNode)) { + throw new InvalidDataAccessApiUsageException("Cannot create circular reference."); + } + } + + protected NodeBacked checkTargetTypeNodebacked(Object newVal) { + if (newVal != null && !(newVal instanceof NodeBacked)) { + throw new IllegalArgumentException("New value must be NodeBacked."); + } + final NodeBacked target = (NodeBacked) newVal; + if (target!=null) checkUnderlyingNode(target); + return target; + } + + protected void removeRelationships(NodeBacked entity) { + Node entityNode = entity.getUnderlyingNode(); + for ( Relationship relationship : entityNode.getRelationships(type, direction) ) { + relationship.delete(); + } + } + + protected Object createEntityFromRelationshipEndNode(Node entityNode) { + Relationship singleRelationship = entityNode.getSingleRelationship(type, direction); + + if (singleRelationship == null) { + return null; + } + Node targetNode = singleRelationship.getOtherNode(entityNode); + return graphEntityInstantiator.createEntityFromState(targetNode, relatedType); + } + + protected void checkUnderlyingNode(NodeBacked entity) { + if (entity==null) throw new IllegalStateException("Entity is null"); + Node entityNode = entity.getUnderlyingNode(); + if (entityNode == null) { + throw new IllegalStateException("Entity must have a backing Node"); + } + } + + protected boolean isExistingRelationship(final NodeBacked entity, final NodeBacked target) { + final Node targetNode = target.getUnderlyingNode(); + for (final Relationship relationship : getRelationships(entity)) { + if (relationship.getEndNode().equals(targetNode)) return true; + } + return false; + } + + private Iterable getRelationships(final NodeBacked entity) { + return entity.getUnderlyingNode().getRelationships(type, direction); + } +} diff --git a/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/FieldAccessor.java b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/FieldAccessor.java index 7def9d85b..fbed48ba2 100644 --- a/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/FieldAccessor.java +++ b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/FieldAccessor.java @@ -4,10 +4,8 @@ import org.springframework.datastore.graph.api.NodeBacked; public interface FieldAccessor { - // Set entity field to newVal - Object apply(NodeBacked entity, Object newVal); + Object setValue(NodeBacked entity, Object newVal); - // Read object from entity field - Object readObject(NodeBacked entity); + Object getValue(NodeBacked entity); } diff --git a/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/FieldAccessorFactory.java b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/FieldAccessorFactory.java index 671b97ba9..02714a70a 100644 --- a/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/FieldAccessorFactory.java +++ b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/FieldAccessorFactory.java @@ -24,31 +24,62 @@ public class FieldAccessorFactory { if (Modifier.isTransient(field.getModifiers())) return null; GraphEntityRelationship relAnnotation = field.getAnnotation(GraphEntityRelationship.class); if (isSingleRelationshipField(field)) { - Class relatedType = (Class) field.getType(); - if (relAnnotation != null) { - return new SingleRelationshipFieldAccessor(DynamicRelationshipType.withName(relAnnotation.type()), - relAnnotation.direction().toNeo4jDir(), relatedType, graphEntityInstantiator); + if (relAnnotation != null) { + return new SingleRelationshipFieldAccessor(typeFrom(relAnnotation), + dirFrom(relAnnotation), targetFrom(field), graphEntityInstantiator); } - return new SingleRelationshipFieldAccessor(DynamicRelationshipType.withName(getNeo4jPropertyName(field)), - Direction.OUTGOING, relatedType, graphEntityInstantiator); + return new SingleRelationshipFieldAccessor(typeFrom(field), + Direction.OUTGOING, targetFrom(field), graphEntityInstantiator); } if (isOneToNRelationshipField(field)) { - return new OneToNRelationshipFieldAccessor(DynamicRelationshipType.withName(relAnnotation.type()), - relAnnotation.direction().toNeo4jDir(), relAnnotation.elementClass(), graphEntityInstantiator); + return new OneToNRelationshipFieldAccessor(typeFrom(relAnnotation), + dirFrom(relAnnotation), targetFrom(relAnnotation), graphEntityInstantiator); } if (isReadOnlyOneToNRelationshipField(field)) { - return new ReadOnlyOneToNRelationshipFieldAccessor(DynamicRelationshipType.withName(relAnnotation.type()), - relAnnotation.direction().toNeo4jDir(), relAnnotation.elementClass(), graphEntityInstantiator); + return new ReadOnlyOneToNRelationshipFieldAccessor(typeFrom(relAnnotation), + dirFrom(relAnnotation), targetFrom(relAnnotation), graphEntityInstantiator); } if (isOneToNRelationshipEntityField(field)) { GraphEntityRelationshipEntity relEntityAnnotation = field.getAnnotation(GraphEntityRelationshipEntity.class); - return new OneToNRelationshipEntityFieldAccessor(DynamicRelationshipType.withName(relEntityAnnotation.type()), - relEntityAnnotation.direction().toNeo4jDir(), relEntityAnnotation.elementClass(), relationshipEntityInstantiator); + return new OneToNRelationshipEntityFieldAccessor(typeFrom(relEntityAnnotation), + dirFrom(relEntityAnnotation), targetFrom(relEntityAnnotation), relationshipEntityInstantiator); } throw new IllegalArgumentException("Not a Neo4j relationship field: " + field); } - - public static boolean isRelationshipField(Field f) { + + private Class targetFrom(GraphEntityRelationshipEntity relEntityAnnotation) { + return relEntityAnnotation.elementClass(); + } + + private Direction dirFrom(GraphEntityRelationshipEntity relEntityAnnotation) { + return relEntityAnnotation.direction().toNeo4jDir(); + } + + private Class targetFrom(Field field) { + return (Class) field.getType(); + } + + private Class targetFrom(GraphEntityRelationship relAnnotation) { + return relAnnotation.elementClass(); + } + + private Direction dirFrom(GraphEntityRelationship relAnnotation) { + return relAnnotation.direction().toNeo4jDir(); + } + + private DynamicRelationshipType typeFrom(Field field) { + return DynamicRelationshipType.withName(getNeo4jPropertyName(field)); + } + + private DynamicRelationshipType typeFrom(GraphEntityRelationshipEntity relEntityAnnotation) { + return DynamicRelationshipType.withName(relEntityAnnotation.type()); + } + + private DynamicRelationshipType typeFrom(GraphEntityRelationship relAnnotation) { + return DynamicRelationshipType.withName(relAnnotation.type()); + } + + public static boolean isRelationshipField(Field f) { return isSingleRelationshipField(f) || isOneToNRelationshipField(f) || isOneToNRelationshipEntityField(f) diff --git a/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/ManagedFieldAccessorSet.java b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/ManagedFieldAccessorSet.java index 4abd67db5..b2c02674e 100644 --- a/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/ManagedFieldAccessorSet.java +++ b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/ManagedFieldAccessorSet.java @@ -6,43 +6,56 @@ import java.util.AbstractSet; import java.util.Iterator; import java.util.Set; +/** + * TODO handle all mutating methods + * @param + */ public class ManagedFieldAccessorSet extends AbstractSet { private final NodeBacked entity; final Set delegate; - private final FieldAccessor relationshipInfo; + private final FieldAccessor fieldAccessor; - public ManagedFieldAccessorSet(NodeBacked entity, Object newVal, FieldAccessor relationshipInfo) { + public ManagedFieldAccessorSet(final NodeBacked entity, final Object newVal, final FieldAccessor fieldAccessor) { this.entity = entity; - this.relationshipInfo = relationshipInfo; + this.fieldAccessor = fieldAccessor; delegate = (Set) newVal; } @Override public Iterator iterator() { - return delegate.iterator(); + final Iterator iterator = delegate.iterator(); + return new Iterator() { + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public T next() { + return iterator.next(); + } + + @Override + public void remove() { + iterator.remove(); + update(); + } + }; } - @Override + private void update() { + fieldAccessor.setValue(entity, delegate); + } + + @Override public int size() { return delegate.size(); } @Override - public boolean add(T e) { - boolean res = delegate.add(e); - if (res) { - relationshipInfo.apply(entity, delegate); - } + public boolean add(final T e) { + final boolean res = delegate.add(e); + if (res) update(); return res; } - - @Override - public boolean remove(Object o) { - boolean res = delegate.remove(o); - if (res) { - relationshipInfo.apply(entity, delegate); - } - return res; - } - } \ No newline at end of file diff --git a/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/Neo4jConversionServiceFactoryBean.java b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/Neo4jConversionServiceFactoryBean.java index 152593487..67ec43ca6 100644 --- a/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/Neo4jConversionServiceFactoryBean.java +++ b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/Neo4jConversionServiceFactoryBean.java @@ -75,7 +75,7 @@ public class Neo4jConversionServiceFactoryBean implements FactoryBean implements Converter { + private static class StringToEnum implements Converter { private final Class enumType; @@ -83,7 +83,10 @@ public class Neo4jConversionServiceFactoryBean implements FactoryBean result = new HashSet(); for (Relationship rel : entity.getUnderlyingNode().getRelationships(type, direction)) { result.add(relationshipEntityInstantiator.createEntityFromState(rel, elementClass)); diff --git a/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/OneToNRelationshipFieldAccessor.java b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/OneToNRelationshipFieldAccessor.java index 81bf1c579..e53916c8a 100644 --- a/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/OneToNRelationshipFieldAccessor.java +++ b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/OneToNRelationshipFieldAccessor.java @@ -25,7 +25,7 @@ public class OneToNRelationshipFieldAccessor implements FieldAccessor { this.graphEntityInstantiator = graphEntityInstantiator; } - public Object apply(final NodeBacked entity, final Object newVal) { + public Object setValue(final NodeBacked entity, final Object newVal) { Node entityNode = entity.getUnderlyingNode(); Set newNodes=new HashSet(); @@ -64,7 +64,7 @@ public class OneToNRelationshipFieldAccessor implements FieldAccessor { } @Override - public Object readObject(NodeBacked entity) { + public Object getValue(NodeBacked entity) { Node entityNode = entity.getUnderlyingNode(); if (entityNode == null) { throw new IllegalStateException("Entity must have a backing Node"); diff --git a/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/ReadOnlyOneToNRelationshipFieldAccessor.java b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/ReadOnlyOneToNRelationshipFieldAccessor.java index e65511c97..74095633b 100644 --- a/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/ReadOnlyOneToNRelationshipFieldAccessor.java +++ b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/ReadOnlyOneToNRelationshipFieldAccessor.java @@ -25,12 +25,12 @@ public class ReadOnlyOneToNRelationshipFieldAccessor implements FieldAccessor { this.graphEntityInstantiator = graphEntityInstantiator; } - public Object apply(final NodeBacked entity, final Object newVal) { + public Object setValue(final NodeBacked entity, final Object newVal) { throw new InvalidDataAccessApiUsageException("Cannot set read-only relationship entity field."); } @Override - public Object readObject(NodeBacked entity) { + public Object getValue(NodeBacked entity) { Node entityNode = entity.getUnderlyingNode(); if (entityNode == null) { throw new IllegalStateException("Entity must have a backing Node"); diff --git a/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/SingleRelationshipFieldAccessor.java b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/SingleRelationshipFieldAccessor.java index 236c9f2df..ec982b000 100644 --- a/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/SingleRelationshipFieldAccessor.java +++ b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/SingleRelationshipFieldAccessor.java @@ -2,62 +2,33 @@ package org.springframework.datastore.graph.neo4j.fieldaccess; 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.datastore.graph.api.NodeBacked; import org.springframework.persistence.support.EntityInstantiator; -public class SingleRelationshipFieldAccessor implements FieldAccessor { - - private final RelationshipType type; - private final Direction direction; - private final Class relatedType; - private final EntityInstantiator graphEntityInstantiator; - - public SingleRelationshipFieldAccessor(RelationshipType type, Direction direction, Class clazz, EntityInstantiator 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; - } +public class SingleRelationshipFieldAccessor extends AbstractFieldAccessor { + public SingleRelationshipFieldAccessor(final RelationshipType type, final Direction direction, final Class clazz, final EntityInstantiator graphEntityInstantiator) { + super(clazz, graphEntityInstantiator, direction, type); + } @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 Object setValue(final NodeBacked entity, final Object newVal) { + checkUnderlyingNode(entity); + if (newVal == null) { + removeRelationships(entity); + return null; + } + + final NodeBacked target=checkTargetTypeNodebacked(newVal); + if (isExistingRelationship(entity, target)) return target; + checkCircularReference(entity, target); + removeRelationships(entity); + return createSingleRelationship(entity, target); + } + + @Override + public Object getValue(final NodeBacked entity) { + checkUnderlyingNode(entity); + return createEntityFromRelationshipEndNode(entity.getUnderlyingNode()); } - } \ No newline at end of file diff --git a/src/main/java/org/springframework/datastore/graph/neo4j/spi/node/Neo4jNodeBacking.aj b/src/main/java/org/springframework/datastore/graph/neo4j/spi/node/Neo4jNodeBacking.aj index 7bd8b1375..531e630fc 100644 --- a/src/main/java/org/springframework/datastore/graph/neo4j/spi/node/Neo4jNodeBacking.aj +++ b/src/main/java/org/springframework/datastore/graph/neo4j/spi/node/Neo4jNodeBacking.aj @@ -1,8 +1,6 @@ package org.springframework.datastore.graph.neo4j.spi.node; -import java.lang.reflect.Constructor; import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Modifier; import java.util.*; @@ -11,7 +9,6 @@ import org.neo4j.graphdb.*; import org.neo4j.graphdb.traversal.*; import org.neo4j.graphdb.traversal.Traverser; import org.neo4j.helpers.collection.IterableWrapper; -import org.neo4j.index.IndexHits; import org.neo4j.index.IndexService; import org.neo4j.kernel.EmbeddedGraphDatabase; import org.neo4j.util.GraphDatabaseUtil; @@ -248,7 +245,7 @@ public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields Neo4J relationship with value=[" + newVal + "]"); - Object result = accessor.apply(entity, newVal); + Object result = accessor.setValue(entity, newVal); return new ShouldProceedOrReturn(true,result); } catch(NotInTransactionException e) { throw new InvalidDataAccessResourceUsageException("Not in a Neo4j transaction.", e);