refactoring relationship-accessors
This commit is contained in:
@@ -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<? extends NodeBacked> relatedType;
|
||||
protected final EntityInstantiator<NodeBacked, Node> graphEntityInstantiator;
|
||||
|
||||
public AbstractFieldAccessor(Class<? extends NodeBacked> clazz, EntityInstantiator<NodeBacked, Node> 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<Relationship> getRelationships(final NodeBacked entity) {
|
||||
return entity.getUnderlyingNode().getRelationships(type, direction);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -24,31 +24,62 @@ public class FieldAccessorFactory {
|
||||
if (Modifier.isTransient(field.getModifiers())) return null;
|
||||
GraphEntityRelationship relAnnotation = field.getAnnotation(GraphEntityRelationship.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);
|
||||
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<? extends RelationshipBacked> targetFrom(GraphEntityRelationshipEntity relEntityAnnotation) {
|
||||
return relEntityAnnotation.elementClass();
|
||||
}
|
||||
|
||||
private Direction dirFrom(GraphEntityRelationshipEntity relEntityAnnotation) {
|
||||
return relEntityAnnotation.direction().toNeo4jDir();
|
||||
}
|
||||
|
||||
private Class<? extends NodeBacked> targetFrom(Field field) {
|
||||
return (Class<? extends NodeBacked>) field.getType();
|
||||
}
|
||||
|
||||
private Class<? extends NodeBacked> 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)
|
||||
|
||||
@@ -6,43 +6,56 @@ import java.util.AbstractSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* TODO handle all mutating methods
|
||||
* @param <T>
|
||||
*/
|
||||
public class ManagedFieldAccessorSet<T> extends AbstractSet<T> {
|
||||
private final NodeBacked entity;
|
||||
final Set<T> 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<T>) newVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return delegate.iterator();
|
||||
final Iterator<T> iterator = delegate.iterator();
|
||||
return new Iterator<T>() {
|
||||
@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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -75,7 +75,7 @@ public class Neo4jConversionServiceFactoryBean implements FactoryBean<Conversion
|
||||
return new StringToEnum(targetType);
|
||||
}
|
||||
|
||||
private class StringToEnum<T extends Enum> implements Converter<String, T> {
|
||||
private static class StringToEnum<T extends Enum> implements Converter<String, T> {
|
||||
|
||||
private final Class<T> enumType;
|
||||
|
||||
@@ -83,7 +83,10 @@ public class Neo4jConversionServiceFactoryBean implements FactoryBean<Conversion
|
||||
this.enumType = enumType;
|
||||
}
|
||||
public T convert(String source) {
|
||||
return source.length() == 0 ? null : Enum.valueOf( this.enumType, source.trim() );
|
||||
if (source == null) return null;
|
||||
final String trimmed=source.trim();
|
||||
if (trimmed.isEmpty()) return null;
|
||||
return Enum.valueOf(this.enumType, trimmed);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,12 +26,12 @@ public class OneToNRelationshipEntityFieldAccessor implements FieldAccessor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object apply(NodeBacked entity, Object newVal) {
|
||||
public Object setValue(NodeBacked entity, Object newVal) {
|
||||
throw new InvalidDataAccessApiUsageException("Cannot set read-only relationship entity field.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object readObject(NodeBacked entity) {
|
||||
public Object getValue(NodeBacked entity) {
|
||||
Set<RelationshipBacked> result = new HashSet<RelationshipBacked>();
|
||||
for (Relationship rel : entity.getUnderlyingNode().getRelationships(type, direction)) {
|
||||
result.add(relationshipEntityInstantiator.createEntityFromState(rel, elementClass));
|
||||
|
||||
@@ -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<Node> newNodes=new HashSet<Node>();
|
||||
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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<? 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;
|
||||
}
|
||||
public class SingleRelationshipFieldAccessor extends AbstractFieldAccessor {
|
||||
public SingleRelationshipFieldAccessor(final RelationshipType type, final Direction direction, final Class<? extends NodeBacked> clazz, final EntityInstantiator<NodeBacked, Node> 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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<GraphEn
|
||||
|
||||
FieldAccessor accessor = fieldAccessorFactory.forField(field);
|
||||
if (accessor!=null) {
|
||||
Object obj = accessor.readObject(entity);
|
||||
Object obj = accessor.getValue(entity);
|
||||
if (obj != null) {
|
||||
return new ShouldProceedOrReturn(obj);
|
||||
}
|
||||
@@ -330,7 +327,7 @@ public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields<GraphEn
|
||||
return new ShouldProceedOrReturn(true,newVal);
|
||||
}
|
||||
log.info("SET " + field + " -> 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);
|
||||
|
||||
Reference in New Issue
Block a user