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 index 536399c0b..0f98356b9 100644 --- a/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/AbstractFieldAccessor.java +++ b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/AbstractFieldAccessor.java @@ -8,6 +8,9 @@ import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.datastore.graph.api.NodeBacked; import org.springframework.persistence.support.EntityInstantiator; +import java.util.HashSet; +import java.util.Set; + /** * @author Michael Hunger * @since 11.09.2010 @@ -27,16 +30,25 @@ public abstract class AbstractFieldAccessor implements FieldAccessor { 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); + case OUTGOING : { + obtainSingleRelationship(entity.getUnderlyingNode(), target.getUnderlyingNode()); + break; + } + case INCOMING : + obtainSingleRelationship(target.getUnderlyingNode(), entity.getUnderlyingNode()); + break; + default : throw new InvalidDataAccessApiUsageException("invalid direction " + direction); } return target; } + private Relationship obtainSingleRelationship(Node start, Node end) { + final Relationship existingRelationship = start.getSingleRelationship(type, direction); + if (existingRelationship!=null && existingRelationship.getOtherNode(start).equals(end)) return existingRelationship; + return start.createRelationshipTo(end, type); + } + protected void checkCircularReference(NodeBacked entity, NodeBacked target) { Node entityNode = entity.getUnderlyingNode(); Node targetNode = target.getUnderlyingNode(); @@ -90,4 +102,65 @@ public abstract class AbstractFieldAccessor implements FieldAccessor { private Iterable getRelationships(final NodeBacked entity) { return entity.getUnderlyingNode().getRelationships(type, direction); } + + protected void removeMissingRelationships(NodeBacked entity, Set target) { + Set newNodes = extractNodes(target); + Node entityNode = entity.getUnderlyingNode(); + for ( Relationship relationship : entityNode.getRelationships(type, direction) ) { + if (!newNodes.remove(relationship.getOtherNode(entityNode))) + relationship.delete(); + } + } + + private Set extractNodes(Set target) { + Set newNodes=new HashSet(); + for (NodeBacked nodeBacked : target) { + newNodes.add(nodeBacked.getUnderlyingNode()); + } + return newNodes; + } + + protected void createNewRelationshipsFrom(NodeBacked entity, Set target) { + for (NodeBacked nodeBacked : target) { + createSingleRelationship(entity,nodeBacked); + } + } + + protected void checkNoCircularReference(NodeBacked entity, Set target) { + if (target.contains(entity)) throw new InvalidDataAccessApiUsageException("Cannot create a circular reference to "+target); + } + + protected Set checkTargetIsSetOfNodebacked(Object newVal) { + if (!(newVal instanceof Set)) { + throw new IllegalArgumentException("New value must be a Set, was: " + newVal.getClass()); + } + for (Object value : (Set) newVal) { + if (!(value instanceof NodeBacked)) { + throw new IllegalArgumentException("New value elements must be NodeBacked."); + } + } + return (Set) newVal; + } + + protected ManagedFieldAccessorSet createManagedSet(NodeBacked entity, Set result) { + return new ManagedFieldAccessorSet(entity, result, this); + } + + protected Set createEntitySetFromRelationshipEndNodes(NodeBacked entity) { + final Set nodes = getStatesFromEntity(entity); + final Set result = new HashSet(); + for (final Node otherNode : nodes) { + result.add(graphEntityInstantiator.createEntityFromState(otherNode, relatedType)); + } + return result; + } + + private Set getStatesFromEntity(NodeBacked entity) { + final Node entityNode = entity.getUnderlyingNode(); + final Set result = new HashSet(); + for (final Relationship rel : entityNode.getRelationships(type, direction)) { + result.add(rel.getOtherNode(entityNode)); + } + return result; + } } 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 02714a70a..9dbceffe1 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 @@ -25,24 +25,19 @@ public class FieldAccessorFactory { GraphEntityRelationship relAnnotation = field.getAnnotation(GraphEntityRelationship.class); if (isSingleRelationshipField(field)) { if (relAnnotation != null) { - return new SingleRelationshipFieldAccessor(typeFrom(relAnnotation), - dirFrom(relAnnotation), targetFrom(field), graphEntityInstantiator); + return new SingleRelationshipFieldAccessor(typeFrom(relAnnotation), dirFrom(relAnnotation), targetFrom(field), graphEntityInstantiator); } - return new SingleRelationshipFieldAccessor(typeFrom(field), - Direction.OUTGOING, targetFrom(field), graphEntityInstantiator); + return new SingleRelationshipFieldAccessor(typeFrom(field), Direction.OUTGOING, targetFrom(field), graphEntityInstantiator); } if (isOneToNRelationshipField(field)) { - return new OneToNRelationshipFieldAccessor(typeFrom(relAnnotation), - dirFrom(relAnnotation), targetFrom(relAnnotation), graphEntityInstantiator); + return new OneToNRelationshipFieldAccessor(typeFrom(relAnnotation), dirFrom(relAnnotation), targetFrom(relAnnotation), graphEntityInstantiator); } if (isReadOnlyOneToNRelationshipField(field)) { - return new ReadOnlyOneToNRelationshipFieldAccessor(typeFrom(relAnnotation), - dirFrom(relAnnotation), targetFrom(relAnnotation), graphEntityInstantiator); + return new ReadOnlyOneToNRelationshipFieldAccessor(typeFrom(relAnnotation), dirFrom(relAnnotation), targetFrom(relAnnotation), graphEntityInstantiator); } if (isOneToNRelationshipEntityField(field)) { GraphEntityRelationshipEntity relEntityAnnotation = field.getAnnotation(GraphEntityRelationshipEntity.class); - return new OneToNRelationshipEntityFieldAccessor(typeFrom(relEntityAnnotation), - dirFrom(relEntityAnnotation), targetFrom(relEntityAnnotation), relationshipEntityInstantiator); + return new OneToNRelationshipEntityFieldAccessor(typeFrom(relEntityAnnotation), dirFrom(relEntityAnnotation), targetFrom(relEntityAnnotation), relationshipEntityInstantiator); } throw new IllegalArgumentException("Not a Neo4j relationship field: " + field); } @@ -112,8 +107,9 @@ public class FieldAccessorFactory { public static String getNeo4jPropertyName(Field field) { final Class entityClass = field.getDeclaringClass(); - return useShortNames(entityClass) ? field.getName() : String.format("%s.%s", entityClass.getSimpleName(),field.getName()); - } + if (useShortNames(entityClass)) return field.getName(); + return String.format("%s.%s", entityClass.getSimpleName(), field.getName()); + } private static boolean useShortNames(Class entityClass) { final GraphEntity graphEntity = entityClass.getAnnotation(GraphEntity.class); diff --git a/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/OneToNRelationshipEntityFieldAccessor.java b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/OneToNRelationshipEntityFieldAccessor.java index 0771bb55f..b08e9eeda 100644 --- a/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/OneToNRelationshipEntityFieldAccessor.java +++ b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/OneToNRelationshipEntityFieldAccessor.java @@ -11,31 +11,38 @@ import org.springframework.datastore.graph.api.NodeBacked; import org.springframework.datastore.graph.api.RelationshipBacked; import org.springframework.persistence.support.EntityInstantiator; -public class OneToNRelationshipEntityFieldAccessor implements FieldAccessor { +public class OneToNRelationshipEntityFieldAccessor extends AbstractFieldAccessor { - private final RelationshipType type; - private final Direction direction; private final Class elementClass; private final EntityInstantiator relationshipEntityInstantiator; - public OneToNRelationshipEntityFieldAccessor(RelationshipType type, Direction direction, Class elementClass, EntityInstantiator relationshipEntityInstantiator) { - this.type = type; - this.direction = direction; + public OneToNRelationshipEntityFieldAccessor(final RelationshipType type, final Direction direction, final Class elementClass, final EntityInstantiator relationshipEntityInstantiator) { + super(null,null,direction,type); this.elementClass = elementClass; this.relationshipEntityInstantiator = relationshipEntityInstantiator; } @Override - public Object setValue(NodeBacked entity, Object newVal) { + public Object setValue(final NodeBacked entity, final Object newVal) { throw new InvalidDataAccessApiUsageException("Cannot set read-only relationship entity field."); } @Override - public Object getValue(NodeBacked entity) { - Set result = new HashSet(); - for (Relationship rel : entity.getUnderlyingNode().getRelationships(type, direction)) { - result.add(relationshipEntityInstantiator.createEntityFromState(rel, elementClass)); - } + public Object getValue(final NodeBacked entity) { + checkUnderlyingNode(entity); + final Set result = createEntitySetFromRelationships(entity); return new ManagedFieldAccessorSet(entity, result, this); } + + private Set createEntitySetFromRelationships(final NodeBacked entity) { + final Set result = new HashSet(); + for (final Relationship rel : getStatesFromEntity(entity)) { + result.add(relationshipEntityInstantiator.createEntityFromState(rel, elementClass)); + } + return result; + } + + private Iterable getStatesFromEntity(NodeBacked entity) { + return entity.getUnderlyingNode().getRelationships(type, direction); + } } 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 e53916c8a..73fd82ea0 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 @@ -1,81 +1,39 @@ package org.springframework.datastore.graph.neo4j.fieldaccess; -import java.util.HashSet; +import java.util.Collections; 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.datastore.graph.api.NodeBacked; import org.springframework.persistence.support.EntityInstantiator; -public class OneToNRelationshipFieldAccessor implements FieldAccessor { +public class OneToNRelationshipFieldAccessor extends AbstractFieldAccessor { - private final RelationshipType type; - private final Direction direction; - private final Class relatedType; - private final EntityInstantiator graphEntityInstantiator; - - public OneToNRelationshipFieldAccessor(RelationshipType type, Direction direction, Class elementClass, EntityInstantiator graphEntityInstantiator) { - this.type = type; - this.direction = direction; - this.relatedType = elementClass; - this.graphEntityInstantiator = graphEntityInstantiator; + public OneToNRelationshipFieldAccessor(final RelationshipType type, final Direction direction, final Class elementClass, final EntityInstantiator graphEntityInstantiator) { + super(elementClass, graphEntityInstantiator, direction, type); } public Object setValue(final NodeBacked entity, final Object newVal) { - Node entityNode = entity.getUnderlyingNode(); - - Set newNodes=new HashSet(); - if (newVal != null) { - if (!(newVal instanceof Set)) { - throw new IllegalArgumentException("New value must be a Set, was: " + newVal.getClass()); - } - Set set = (Set) 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(entity, newVal, this); + checkUnderlyingNode(entity); + if (newVal==null) { + removeMissingRelationships(entity, Collections.emptySet()); + return null; + } + final Set target = checkTargetIsSetOfNodebacked(newVal); + checkNoCircularReference(entity,target); + removeMissingRelationships(entity, target); + createNewRelationshipsFrom(entity,target); + return createManagedSet(entity, target); } - - @Override - public Object getValue(NodeBacked entity) { - Node entityNode = entity.getUnderlyingNode(); - if (entityNode == null) { - throw new IllegalStateException("Entity must have a backing Node"); - } - Set result = new HashSet(); - for (Relationship rel : entityNode.getRelationships(type, direction)) { - result.add(graphEntityInstantiator.createEntityFromState(rel.getOtherNode(entityNode), relatedType)); - } - return new ManagedFieldAccessorSet(entity, result, this); + + @Override + public Object getValue(final NodeBacked entity) { + checkUnderlyingNode(entity); + final Set result = createEntitySetFromRelationshipEndNodes(entity); + return createManagedSet(entity, result); } - } \ No newline at end of file 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 74095633b..1fa46619b 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 @@ -11,35 +11,13 @@ import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.datastore.graph.api.NodeBacked; import org.springframework.persistence.support.EntityInstantiator; -public class ReadOnlyOneToNRelationshipFieldAccessor implements FieldAccessor { - - private final RelationshipType type; - private final Direction direction; - private final Class relatedType; - private final EntityInstantiator graphEntityInstantiator; +public class ReadOnlyOneToNRelationshipFieldAccessor extends OneToNRelationshipFieldAccessor { public ReadOnlyOneToNRelationshipFieldAccessor(RelationshipType type, Direction direction, Class elementClass, EntityInstantiator graphEntityInstantiator) { - this.type = type; - this.direction = direction; - this.relatedType = elementClass; - this.graphEntityInstantiator = graphEntityInstantiator; + super(type,direction,elementClass,graphEntityInstantiator); } public Object setValue(final NodeBacked entity, final Object newVal) { throw new InvalidDataAccessApiUsageException("Cannot set read-only relationship entity field."); } - - @Override - public Object getValue(NodeBacked entity) { - Node entityNode = entity.getUnderlyingNode(); - if (entityNode == null) { - throw new IllegalStateException("Entity must have a backing Node"); - } - Set result = new HashSet(); - for (Relationship rel : entityNode.getRelationships(type, direction)) { - result.add(graphEntityInstantiator.createEntityFromState(rel.getOtherNode(entityNode), relatedType)); - } - return new ManagedFieldAccessorSet(entity, result, this); - } - } 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 ec982b000..abd1a8c0d 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 @@ -6,6 +6,9 @@ import org.neo4j.graphdb.RelationshipType; import org.springframework.datastore.graph.api.NodeBacked; import org.springframework.persistence.support.EntityInstantiator; +import java.util.Collections; +import java.util.Set; + public class SingleRelationshipFieldAccessor extends AbstractFieldAccessor { public SingleRelationshipFieldAccessor(final RelationshipType type, final Direction direction, final Class clazz, final EntityInstantiator graphEntityInstantiator) { super(clazz, graphEntityInstantiator, direction, type); @@ -15,20 +18,20 @@ public class SingleRelationshipFieldAccessor extends AbstractFieldAccessor { public Object setValue(final NodeBacked entity, final Object newVal) { checkUnderlyingNode(entity); if (newVal == null) { - removeRelationships(entity); + removeMissingRelationships(entity, Collections.emptySet()); return null; } - - final NodeBacked target=checkTargetTypeNodebacked(newVal); - if (isExistingRelationship(entity, target)) return target; - checkCircularReference(entity, target); - removeRelationships(entity); - return createSingleRelationship(entity, target); + final Set target=checkTargetIsSetOfNodebacked(Collections.singleton(newVal)); + checkNoCircularReference(entity,target); + removeMissingRelationships(entity, target); + createNewRelationshipsFrom(entity,target); + return newVal; } @Override public Object getValue(final NodeBacked entity) { checkUnderlyingNode(entity); - return createEntityFromRelationshipEndNode(entity.getUnderlyingNode()); + final Set result = createEntitySetFromRelationshipEndNodes(entity); + return result.isEmpty() ? null : result.iterator().next(); } } \ No newline at end of file