From 9a073f38a79b451439deff51ae81e1f33249367d Mon Sep 17 00:00:00 2001 From: Michael Hunger Date: Sun, 12 Sep 2010 16:21:09 +0200 Subject: [PATCH] refactoring fieldaccessors to be used independently --- .../ConvertingNodePropertyFieldAccessor.java | 67 ++++++++ .../DelegatingFieldAccessorFactory.java | 152 ++++++++++++++++++ .../fieldaccess/EntityStateAccessors.java | 60 +++++++ .../fieldaccess/FieldAccessListener.java | 7 + .../fieldaccess/FieldAccessorFactory.java | 125 +------------- .../FieldAccessorListenerFactory.java | 13 ++ ...xingNodePropertyFieldAccessorListener.java | 49 ++++++ .../NodePropertyFieldAccessor.java | 57 +++++++ ...OneToNRelationshipEntityFieldAccessor.java | 86 +++++++--- .../OneToNRelationshipFieldAccessor.java | 47 ++++-- ...adOnlyOneToNRelationshipFieldAccessor.java | 24 ++- .../RelationshipFieldAccessorFactory.java | 51 ++++++ .../SingleRelationshipFieldAccessor.java | 23 ++- .../graph/neo4j/spi/node/Neo4jNodeBacking.aj | 14 +- .../relationship/Neo4jRelationshipBacking.aj | 11 +- 15 files changed, 614 insertions(+), 172 deletions(-) create mode 100644 src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/ConvertingNodePropertyFieldAccessor.java create mode 100644 src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/DelegatingFieldAccessorFactory.java create mode 100644 src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/EntityStateAccessors.java create mode 100644 src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/FieldAccessListener.java create mode 100644 src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/FieldAccessorListenerFactory.java create mode 100644 src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/IndexingNodePropertyFieldAccessorListener.java create mode 100644 src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/NodePropertyFieldAccessor.java create mode 100644 src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/RelationshipFieldAccessorFactory.java diff --git a/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/ConvertingNodePropertyFieldAccessor.java b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/ConvertingNodePropertyFieldAccessor.java new file mode 100644 index 000000000..823ee84a4 --- /dev/null +++ b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/ConvertingNodePropertyFieldAccessor.java @@ -0,0 +1,67 @@ +package org.springframework.datastore.graph.neo4j.fieldaccess; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.core.convert.ConversionService; +import org.springframework.datastore.graph.api.NodeBacked; + +import java.lang.reflect.Field; + +/** + * @author Michael Hunger + * @since 12.09.2010 + */ +@Configurable +public class ConvertingNodePropertyFieldAccessor extends NodePropertyFieldAccessor { + + private final ConversionService conversionService; + + public ConvertingNodePropertyFieldAccessor(final Field field, final ConversionService conversionService) { + super(field); + this.conversionService = conversionService; + } + + @Override + public Object setValue(final NodeBacked nodeBacked, final Object newVal) { + super.setValue(nodeBacked,serializePropertyValue(newVal)); + return newVal; + } + + @Override + public Object getValue(final NodeBacked nodeBacked) { + return deserializePropertyValue(super.getValue(nodeBacked)); + } + + private Object serializePropertyValue(final Object newVal) { + return conversionService.convert(newVal, String.class); + } + + private Object deserializePropertyValue(final Object value) { + return conversionService.convert(value, field.getType()); + } + + public static FieldAccessorFactory factory() { + return new FieldAccessorFactory() { + @Autowired + ConversionService conversionService; + + @Override + public boolean accept(final Field field) { + return isSerializableField(field) && isDeserializableField(field); + } + + @Override + public FieldAccessor forField(final Field field) { + return new ConvertingNodePropertyFieldAccessor(field,conversionService); + } + + private boolean isSerializableField(final Field field) { + return !DelegatingFieldAccessorFactory.isRelationshipField(field) && conversionService.canConvert(field.getType(), String.class); + } + + private boolean isDeserializableField(final Field field) { + return !DelegatingFieldAccessorFactory.isRelationshipField(field) && conversionService.canConvert(String.class, field.getType()); + } + }; + } +} diff --git a/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/DelegatingFieldAccessorFactory.java b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/DelegatingFieldAccessorFactory.java new file mode 100644 index 000000000..d37b244fc --- /dev/null +++ b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/DelegatingFieldAccessorFactory.java @@ -0,0 +1,152 @@ +package org.springframework.datastore.graph.neo4j.fieldaccess; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import org.neo4j.graphdb.Direction; +import org.neo4j.graphdb.DynamicRelationshipType; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Relationship; +import org.springframework.datastore.graph.api.*; +import org.springframework.persistence.support.EntityInstantiator; + +public class DelegatingFieldAccessorFactory implements FieldAccessorFactory { + private final EntityInstantiator graphEntityInstantiator; + private final EntityInstantiator relationshipEntityInstantiator; + + public DelegatingFieldAccessorFactory(EntityInstantiator graphEntityInstantiator, EntityInstantiator relationshipEntityInstantiator) { + this.graphEntityInstantiator = graphEntityInstantiator; + this.relationshipEntityInstantiator = relationshipEntityInstantiator; + } + + @Override + public boolean accept(Field f) { + return isSingleRelationshipField(f) || isOneToNRelationshipEntityField(f) || isReadOnlyOneToNRelationshipField(f) || isOneToNRelationshipField(f); + } + + final Collection> fieldAccessorFactories = Arrays.>asList( + NodePropertyFieldAccessor.factory(), + ConvertingNodePropertyFieldAccessor.factory(), + SingleRelationshipFieldAccessor.factory(), + OneToNRelationshipFieldAccessor.factory(), + ReadOnlyOneToNRelationshipFieldAccessor.factory(), + OneToNRelationshipEntityFieldAccessor.factory() + ); + final Collection> fieldAccessorListenerFactories = Arrays.>asList( + IndexingNodePropertyFieldAccessorListener.factory() + ); + + public FieldAccessor forField(Field field) { + if (Modifier.isTransient(field.getModifiers())) return null; + 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(field), Direction.OUTGOING, targetFrom(field), graphEntityInstantiator); + } + if (isOneToNRelationshipField(field)) { + return new OneToNRelationshipFieldAccessor(typeFrom(relAnnotation), dirFrom(relAnnotation), targetFrom(relAnnotation), graphEntityInstantiator); + } + if (isReadOnlyOneToNRelationshipField(field)) { + 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); + } + throw new IllegalArgumentException("Not a Neo4j relationship field: " + field); + } + + + 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 Class targetFrom(GraphEntityRelationshipEntity relEntityAnnotation) { + return relEntityAnnotation.elementClass(); + } + + private Direction dirFrom(GraphEntityRelationshipEntity relEntityAnnotation) { + return relEntityAnnotation.direction().toNeo4jDir(); + } + 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) + || isReadOnlyOneToNRelationshipField(f); + } + + 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; + GraphEntityRelationship relAnnotation = f.getAnnotation(GraphEntityRelationship.class); + return relAnnotation != null && NodeBacked.class.isAssignableFrom(relAnnotation.elementClass()) && !relAnnotation.elementClass().equals(NodeBacked.class); + } + + private static boolean isReadOnlyOneToNRelationshipField(Field f) { + GraphEntityRelationship relAnnotation = f.getAnnotation(GraphEntityRelationship.class); + return Iterable.class.equals(f.getType()) + && relAnnotation != null + && !NodeBacked.class.equals(relAnnotation.elementClass()); + } + + private static boolean isOneToNRelationshipEntityField(Field f) { + GraphEntityRelationshipEntity relEntityAnnotation = f.getAnnotation(GraphEntityRelationshipEntity.class); + return Iterable.class.isAssignableFrom(f.getType()) + && relEntityAnnotation != null + && !RelationshipBacked.class.equals(relEntityAnnotation.elementClass()); + } + + public static String getNeo4jPropertyName(Field field) { + final Class entityClass = field.getDeclaringClass(); + 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); + if (graphEntity!=null) return graphEntity.useShortNames(); + final GraphRelationship graphRelationship = entityClass.getAnnotation(GraphRelationship.class); + if (graphRelationship!=null) return graphRelationship.useShortNames(); + return false; + } + + public List> listenersFor(Field field) { + List> result=new ArrayList>(); + for (FieldAccessorListenerFactory fieldAccessorListenerFactory : fieldAccessorListenerFactories) { + if (fieldAccessorListenerFactory.accept(field)) { + final FieldAccessor listener = (FieldAccessor) fieldAccessorListenerFactory.forField(field); + result.add(listener); + } + } + return result; + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/EntityStateAccessors.java b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/EntityStateAccessors.java new file mode 100644 index 000000000..2f31f905f --- /dev/null +++ b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/EntityStateAccessors.java @@ -0,0 +1,60 @@ +package org.springframework.datastore.graph.neo4j.fieldaccess; + +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Relationship; +import org.springframework.datastore.graph.api.NodeBacked; +import org.springframework.datastore.graph.api.RelationshipBacked; +import org.springframework.persistence.support.EntityInstantiator; +import org.springframework.util.ReflectionUtils; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Michael Hunger + * @since 12.09.2010 + */ +public class EntityStateAccessors { + private final STATE underlyingState; + private final ENTITY entity; + private final Class type; + private final Map> fieldAccessors=new HashMap>(); + private final Map>> fieldAccessorListeners=new HashMap>>(); + + public EntityStateAccessors(final STATE underlyingState, final ENTITY entity, final Class type, final EntityInstantiator nodeInstantiator, final EntityInstantiator relationshipInstantiator) { + this.underlyingState = underlyingState; + this.entity = entity; + this.type = type; + createAccessorsAndListeners(type, nodeInstantiator, relationshipInstantiator); + } + + private void createAccessorsAndListeners(final Class type, final EntityInstantiator nodeInstantiator, final EntityInstantiator relationshipInstantiator) { + final DelegatingFieldAccessorFactory fieldAccessorFactory = new DelegatingFieldAccessorFactory(nodeInstantiator,relationshipInstantiator); + ReflectionUtils.doWithFields(type, new ReflectionUtils.FieldCallback() { + public void doWith(final Field field) throws IllegalArgumentException, IllegalAccessException { + fieldAccessors.put(field, fieldAccessorFactory.forField(field)); + fieldAccessorListeners.put(field, fieldAccessorFactory.listenersFor(field)); // TODO Bad code + } + }); + } + + public Object getValue(final Field field) { + return fieldAccessors.get(field).getValue(entity); + } + public Object setValue(final Field field, final Object newVal) { + final Object result = fieldAccessors.get(field).setValue(entity, newVal); + notifyListeners(field, result); // async ? + return result; + } + + private void notifyListeners(Field field, Object result) { + if (fieldAccessorListeners.containsKey(field)) { + for (final FieldAccessListener listener : fieldAccessorListeners.get(field)) { + listener.valueChanged(entity,null,result); // todo oldValue + } + } + } + +} diff --git a/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/FieldAccessListener.java b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/FieldAccessListener.java new file mode 100644 index 000000000..a2b5ba88b --- /dev/null +++ b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/FieldAccessListener.java @@ -0,0 +1,7 @@ +package org.springframework.datastore.graph.neo4j.fieldaccess; + +public interface FieldAccessListener { + + void valueChanged(ENTITY entity, Object oldVal, Object newVal); + +} 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 9dbceffe1..6603fc36c 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 @@ -1,121 +1,12 @@ package org.springframework.datastore.graph.neo4j.fieldaccess; import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -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.datastore.graph.api.*; -import org.springframework.persistence.support.EntityInstantiator; - -public class FieldAccessorFactory { - private final EntityInstantiator graphEntityInstantiator; - private final EntityInstantiator relationshipEntityInstantiator; - - public FieldAccessorFactory(EntityInstantiator graphEntityInstantiator, EntityInstantiator relationshipEntityInstantiator) { - this.graphEntityInstantiator = graphEntityInstantiator; - this.relationshipEntityInstantiator = relationshipEntityInstantiator; - } - - public FieldAccessor forField(Field field) { - if (Modifier.isTransient(field.getModifiers())) return null; - 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(field), Direction.OUTGOING, targetFrom(field), graphEntityInstantiator); - } - if (isOneToNRelationshipField(field)) { - return new OneToNRelationshipFieldAccessor(typeFrom(relAnnotation), dirFrom(relAnnotation), targetFrom(relAnnotation), graphEntityInstantiator); - } - if (isReadOnlyOneToNRelationshipField(field)) { - 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); - } - throw new IllegalArgumentException("Not a Neo4j relationship field: " + field); - } - - 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) - || isReadOnlyOneToNRelationshipField(f); - } - - 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; - GraphEntityRelationship relAnnotation = f.getAnnotation(GraphEntityRelationship.class); - return relAnnotation != null && NodeBacked.class.isAssignableFrom(relAnnotation.elementClass()) && !relAnnotation.elementClass().equals(NodeBacked.class); - } - - private static boolean isReadOnlyOneToNRelationshipField(Field f) { - GraphEntityRelationship relAnnotation = f.getAnnotation(GraphEntityRelationship.class); - return Iterable.class.equals(f.getType()) - && relAnnotation != null - && !NodeBacked.class.equals(relAnnotation.elementClass()); - } - - private static boolean isOneToNRelationshipEntityField(Field f) { - GraphEntityRelationshipEntity relEntityAnnotation = f.getAnnotation(GraphEntityRelationshipEntity.class); - return Iterable.class.isAssignableFrom(f.getType()) - && relEntityAnnotation != null - && !RelationshipBacked.class.equals(relEntityAnnotation.elementClass()); - } - - public static String getNeo4jPropertyName(Field field) { - final Class entityClass = field.getDeclaringClass(); - 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); - if (graphEntity!=null) return graphEntity.useShortNames(); - final GraphRelationship graphRelationship = entityClass.getAnnotation(GraphRelationship.class); - if (graphRelationship!=null) return graphRelationship.useShortNames(); - return false; - } -} \ No newline at end of file +/** +* @author Michael Hunger +* @since 12.09.2010 +*/ +interface FieldAccessorFactory { + boolean accept(Field f); + FieldAccessor forField(Field f); +} diff --git a/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/FieldAccessorListenerFactory.java b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/FieldAccessorListenerFactory.java new file mode 100644 index 000000000..3ab225961 --- /dev/null +++ b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/FieldAccessorListenerFactory.java @@ -0,0 +1,13 @@ +package org.springframework.datastore.graph.neo4j.fieldaccess; + +import java.lang.reflect.Field; + +/** + * @author Michael Hunger + * @since 12.09.2010 + */ +interface FieldAccessorListenerFactory { + boolean accept(Field f); + + FieldAccessListener forField(Field f); +} diff --git a/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/IndexingNodePropertyFieldAccessorListener.java b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/IndexingNodePropertyFieldAccessorListener.java new file mode 100644 index 000000000..2ba6508fe --- /dev/null +++ b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/IndexingNodePropertyFieldAccessorListener.java @@ -0,0 +1,49 @@ +package org.springframework.datastore.graph.neo4j.fieldaccess; + +import org.neo4j.index.IndexService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.datastore.graph.api.NodeBacked; + +import java.lang.reflect.Field; + +/** + * @author Michael Hunger + * @since 12.09.2010 + */ +public class IndexingNodePropertyFieldAccessorListener implements FieldAccessListener { + protected final Field field; + private final IndexService indexService; + + public IndexingNodePropertyFieldAccessorListener(final Field field, final IndexService indexService) { + this.field = field; + this.indexService = indexService; + } + + @Override + public void valueChanged(final NodeBacked nodeBacked, final Object oldVal, final Object newVal) { + if (newVal==null) indexService.removeIndex(nodeBacked.getUnderlyingNode(),getPropertyName()); + else indexService.index(nodeBacked.getUnderlyingNode(), getPropertyName(),newVal); + } + + // todo + private String getPropertyName() { + return DelegatingFieldAccessorFactory.getNeo4jPropertyName(field); + } + + public static FieldAccessorListenerFactory factory() { + return new FieldAccessorListenerFactory() { + @Autowired + IndexService indexService; + + @Override + public boolean accept(final Field f) { + return NodePropertyFieldAccessor.factory().accept(f) || ConvertingNodePropertyFieldAccessor.factory().accept(f); + } + + @Override + public FieldAccessListener forField(final Field field) { + return new IndexingNodePropertyFieldAccessorListener(field,indexService); + } + }; + } +} diff --git a/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/NodePropertyFieldAccessor.java b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/NodePropertyFieldAccessor.java new file mode 100644 index 000000000..5e4e4234d --- /dev/null +++ b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/NodePropertyFieldAccessor.java @@ -0,0 +1,57 @@ +package org.springframework.datastore.graph.neo4j.fieldaccess; + +import org.springframework.datastore.graph.api.NodeBacked; + +import java.lang.reflect.Field; + +/** + * @author Michael Hunger + * @since 12.09.2010 + */ +public class NodePropertyFieldAccessor implements FieldAccessor { + protected final Field field; + + public NodePropertyFieldAccessor(final Field field) { + this.field = field; + } + + @Override + public Object setValue(final NodeBacked nodeBacked, final Object newVal) { + nodeBacked.getUnderlyingNode().setProperty(getPropertyName(),newVal); + return newVal; + } + + @Override + public Object getValue(final NodeBacked nodeBacked) { + return nodeBacked.getUnderlyingNode().getProperty(getPropertyName()); + } + + private String getPropertyName() { + return DelegatingFieldAccessorFactory.getNeo4jPropertyName(field); + } + + public static FieldAccessorFactory factory() { + return new FieldAccessorFactory() { + @Override + public boolean accept(final Field f) { + return isNeo4jPropertyType(f.getType()); + } + + @Override + public FieldAccessor forField(final Field field) { + return new NodePropertyFieldAccessor(field); + } + + private boolean isNeo4jPropertyType(final Class fieldType) { + // todo: add array support + return fieldType.isPrimitive() + || (fieldType.isArray() && !fieldType.getComponentType().isArray() && isNeo4jPropertyType(fieldType.getComponentType())) + || fieldType.equals(String.class) + || fieldType.equals(Character.class) + || fieldType.equals(Boolean.class) + || (fieldType.getName().startsWith("java.lang") && Number.class.isAssignableFrom(fieldType)); + } + + }; + } +} 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 52fcfbc2d..2451af0ef 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 @@ -1,34 +1,34 @@ package org.springframework.datastore.graph.neo4j.fieldaccess; -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.neo4j.graphdb.*; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.datastore.graph.api.GraphEntityRelationshipEntity; import org.springframework.datastore.graph.api.NodeBacked; import org.springframework.datastore.graph.api.RelationshipBacked; import org.springframework.persistence.support.EntityInstantiator; -public class OneToNRelationshipEntityFieldAccessor extends AbstractRelationshipFieldAccessor { +import java.lang.reflect.Field; +import java.util.HashSet; +import java.util.Set; - public OneToNRelationshipEntityFieldAccessor(final RelationshipType type, final Direction direction, final Class elementClass, final EntityInstantiator relationshipEntityInstantiator) { - super(elementClass,relationshipEntityInstantiator,direction,type); - } +public class OneToNRelationshipEntityFieldAccessor extends AbstractRelationshipFieldAccessor { - @Override - public Object setValue(final NodeBacked entity, final Object newVal) { - throw new InvalidDataAccessApiUsageException("Cannot set read-only relationship entity field."); - } + public OneToNRelationshipEntityFieldAccessor(final RelationshipType type, final Direction direction, final Class elementClass, final EntityInstantiator relationshipEntityInstantiator) { + super(elementClass, relationshipEntityInstantiator, direction, type); + } - @Override - public Object getValue(final NodeBacked entity) { + @Override + public Object setValue(final NodeBacked entity, final Object newVal) { + throw new InvalidDataAccessApiUsageException("Cannot set read-only relationship entity field."); + } + + @Override + public Object getValue(final NodeBacked entity) { checkUnderlyingNode(entity); final Set result = createEntitySetFromRelationships(entity); - return new ManagedFieldAccessorSet(entity, result, this); - } + return new ManagedFieldAccessorSet(entity, result, this); + } private Set createEntitySetFromRelationships(final NodeBacked entity) { final Set result = new HashSet(); @@ -39,17 +39,59 @@ public class OneToNRelationshipEntityFieldAccessor extends AbstractRelationshipF } @Override - protected Iterable getStatesFromEntity(NodeBacked entity) { + protected Iterable getStatesFromEntity(final NodeBacked entity) { return entity.getUnderlyingNode().getRelationships(type, direction); } @Override - protected Relationship obtainSingleRelationship(Node start, Relationship end) { + protected Relationship obtainSingleRelationship(final Node start, final Relationship end) { return null; } @Override - protected Node getState(NodeBacked nodeBacked) { + protected Node getState(final NodeBacked nodeBacked) { return nodeBacked.getUnderlyingNode(); } + + public static FieldAccessorFactory factory() { + return new RelationshipEntityFieldAccessorFactory(); + } + + + private static class RelationshipEntityFieldAccessorFactory implements FieldAccessorFactory { + @Autowired + private EntityInstantiator relationshipEntityInstantiator; + + @Override + public boolean accept(final Field f) { + return Iterable.class.isAssignableFrom(f.getType()) && hasValidRelationshipAnnotation(f); + } + + @Override + public FieldAccessor forField(final Field field) { + final GraphEntityRelationshipEntity relEntityAnnotation = getRelationshipAnnotation(field); + return new OneToNRelationshipEntityFieldAccessor(typeFrom(relEntityAnnotation), dirFrom(relEntityAnnotation), targetFrom(relEntityAnnotation), relationshipEntityInstantiator); + } + + private boolean hasValidRelationshipAnnotation(final Field f) { + final GraphEntityRelationshipEntity relEntityAnnotation = getRelationshipAnnotation(f); + return relEntityAnnotation != null && !RelationshipBacked.class.equals(relEntityAnnotation.elementClass()); + } + + private GraphEntityRelationshipEntity getRelationshipAnnotation(final Field field) { + return field.getAnnotation(GraphEntityRelationshipEntity.class); + } + + private Class targetFrom(final GraphEntityRelationshipEntity relEntityAnnotation) { + return relEntityAnnotation.elementClass(); + } + + private Direction dirFrom(final GraphEntityRelationshipEntity relEntityAnnotation) { + return relEntityAnnotation.direction().toNeo4jDir(); + } + + private DynamicRelationshipType typeFrom(final GraphEntityRelationshipEntity relEntityAnnotation) { + return DynamicRelationshipType.withName(relEntityAnnotation.type()); + } + } } 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 1758b1ed7..2ee2478d4 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,39 +1,58 @@ package org.springframework.datastore.graph.neo4j.fieldaccess; -import java.util.Collections; -import java.util.Set; - import org.neo4j.graphdb.Direction; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.RelationshipType; +import org.springframework.datastore.graph.api.GraphEntityRelationship; import org.springframework.datastore.graph.api.NodeBacked; import org.springframework.persistence.support.EntityInstantiator; +import java.lang.reflect.Field; +import java.util.Collection; +import java.util.Collections; +import java.util.Set; + public class OneToNRelationshipFieldAccessor extends NodeToNodesRelationshipFieldAccessor { - public OneToNRelationshipFieldAccessor(final RelationshipType type, final Direction direction, final Class elementClass, final EntityInstantiator 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) { + public Object setValue(final NodeBacked entity, final Object newVal) { final Node node = checkUnderlyingNode(entity); - if (newVal==null) { + if (newVal == null) { removeMissingRelationships(node, Collections.emptySet()); return null; } final Set targetNodes = checkTargetIsSetOfNodebacked(newVal); - checkNoCircularReference(node,targetNodes); + checkNoCircularReference(node, targetNodes); removeMissingRelationships(node, targetNodes); - createAddedRelationships(node,targetNodes); - return createManagedSet(entity, (Set)newVal); - } + createAddedRelationships(node, targetNodes); + return createManagedSet(entity, (Set) newVal); + } @Override - public Object getValue(final NodeBacked entity) { + public Object getValue(final NodeBacked entity) { checkUnderlyingNode(entity); final Set result = createEntitySetFromRelationshipEndNodes(entity); - return createManagedSet(entity, result); - } + return createManagedSet(entity, result); + } + + public static FieldAccessorFactory factory() { + return new RelationshipFieldAccessorFactory() { + @Override + public boolean accept(final Field f) { + return Collection.class.isAssignableFrom(f.getType()) && hasValidRelationshipAnnotation(f); + } + + @Override + public FieldAccessor forField(final Field field) { + final GraphEntityRelationship relAnnotation = getRelationshipAnnotation(field); + return new OneToNRelationshipFieldAccessor(typeFrom(relAnnotation), dirFrom(relAnnotation), targetFrom(relAnnotation), graphEntityInstantiator); + } + + }; + } } \ 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 1fa46619b..3ff6de44e 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 @@ -1,23 +1,39 @@ package org.springframework.datastore.graph.neo4j.fieldaccess; -import java.util.HashSet; -import java.util.Set; +import java.lang.reflect.Field; 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.GraphEntityRelationship; import org.springframework.datastore.graph.api.NodeBacked; import org.springframework.persistence.support.EntityInstantiator; public class ReadOnlyOneToNRelationshipFieldAccessor extends OneToNRelationshipFieldAccessor { - public ReadOnlyOneToNRelationshipFieldAccessor(RelationshipType type, Direction direction, Class elementClass, EntityInstantiator graphEntityInstantiator) { + public ReadOnlyOneToNRelationshipFieldAccessor(final RelationshipType type, final Direction direction, final Class elementClass, final EntityInstantiator 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."); } + + public static FieldAccessorFactory factory() { + return new RelationshipFieldAccessorFactory() { + @Override + public boolean accept(final Field f) { + return Iterable.class.equals(f.getType()) && hasValidRelationshipAnnotation(f); + } + + @Override + public FieldAccessor forField(final Field field) { + final GraphEntityRelationship relAnnotation = getRelationshipAnnotation(field); + return new ReadOnlyOneToNRelationshipFieldAccessor(typeFrom(relAnnotation), dirFrom(relAnnotation), targetFrom(relAnnotation), graphEntityInstantiator); + } + + }; + } + } diff --git a/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/RelationshipFieldAccessorFactory.java b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/RelationshipFieldAccessorFactory.java new file mode 100644 index 000000000..fdcc2fbf3 --- /dev/null +++ b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/RelationshipFieldAccessorFactory.java @@ -0,0 +1,51 @@ +package org.springframework.datastore.graph.neo4j.fieldaccess; + +import org.neo4j.graphdb.Direction; +import org.neo4j.graphdb.DynamicRelationshipType; +import org.neo4j.graphdb.Node; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Configurable; +import org.springframework.datastore.graph.api.GraphEntityRelationship; +import org.springframework.datastore.graph.api.NodeBacked; +import org.springframework.persistence.support.EntityInstantiator; + +import java.lang.reflect.Field; + +/** + * @author Michael Hunger + * @since 12.09.2010 + */ +@Configurable +abstract class RelationshipFieldAccessorFactory implements FieldAccessorFactory { + @Autowired + protected EntityInstantiator graphEntityInstantiator; + + protected Class targetFrom(Field field) { + return (Class) field.getType(); + } + + protected Class targetFrom(GraphEntityRelationship relAnnotation) { + return (Class) relAnnotation.elementClass(); + } + + protected Direction dirFrom(GraphEntityRelationship relAnnotation) { + return relAnnotation.direction().toNeo4jDir(); + } + + protected DynamicRelationshipType typeFrom(Field field) { + return DynamicRelationshipType.withName(DelegatingFieldAccessorFactory.getNeo4jPropertyName(field)); + } + + protected DynamicRelationshipType typeFrom(GraphEntityRelationship relAnnotation) { + return DynamicRelationshipType.withName(relAnnotation.type()); + } + + protected GraphEntityRelationship getRelationshipAnnotation(Field field) { + return field.getAnnotation(GraphEntityRelationship.class); + } + + protected boolean hasValidRelationshipAnnotation(Field field) { + final GraphEntityRelationship relAnnotation = getRelationshipAnnotation(field); + return relAnnotation != null && !relAnnotation.elementClass().equals(NodeBacked.class); + } +} 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 8a371e4af..84bdbc7a4 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 @@ -3,10 +3,11 @@ package org.springframework.datastore.graph.neo4j.fieldaccess; import org.neo4j.graphdb.Direction; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.RelationshipType; +import org.springframework.datastore.graph.api.GraphEntityRelationship; import org.springframework.datastore.graph.api.NodeBacked; -import org.springframework.datastore.graph.api.RelationshipBacked; import org.springframework.persistence.support.EntityInstantiator; +import java.lang.reflect.Field; import java.util.Collections; import java.util.Set; @@ -17,7 +18,7 @@ public class SingleRelationshipFieldAccessor extends NodeToNodesRelationshipFiel @Override public Object setValue(final NodeBacked entity, final Object newVal) { - Node node=checkUnderlyingNode(entity); + final Node node=checkUnderlyingNode(entity); if (newVal == null) { removeMissingRelationships(node, Collections.emptySet()); return null; @@ -35,4 +36,22 @@ public class SingleRelationshipFieldAccessor extends NodeToNodesRelationshipFiel final Set result = createEntitySetFromRelationshipEndNodes(entity); return result.isEmpty() ? null : result.iterator().next(); } + + public static FieldAccessorFactory factory() { + return new RelationshipFieldAccessorFactory() { + @Override + public boolean accept(final Field f) { + return NodeBacked.class.isAssignableFrom(f.getType()); + } + + @Override + public FieldAccessor forField(final Field field) { + final GraphEntityRelationship relAnnotation = getRelationshipAnnotation(field); + if (relAnnotation == null) + return new SingleRelationshipFieldAccessor(typeFrom(field), Direction.OUTGOING, targetFrom(field), graphEntityInstantiator); + return new SingleRelationshipFieldAccessor(typeFrom(relAnnotation), dirFrom(relAnnotation), targetFrom(field), graphEntityInstantiator); + } + }; + } + } \ 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 531e630fc..030797d60 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 @@ -19,8 +19,8 @@ import org.springframework.datastore.graph.api.NodeBacked; import org.springframework.datastore.graph.api.RelationshipBacked; import org.springframework.datastore.graph.api.GraphEntity; +import org.springframework.datastore.graph.neo4j.fieldaccess.DelegatingFieldAccessorFactory; import org.springframework.datastore.graph.neo4j.fieldaccess.FieldAccessor; -import org.springframework.datastore.graph.neo4j.fieldaccess.FieldAccessorFactory; import org.springframework.persistence.support.AbstractTypeAnnotatingMixinFields; import org.springframework.persistence.support.EntityInstantiator; import org.springframework.util.ObjectUtils; @@ -49,7 +49,7 @@ public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields graphEntityInstantiator; - private FieldAccessorFactory fieldAccessorFactory; + private DelegatingFieldAccessorFactory fieldAccessorFactory; public EntityInstantiator relationshipEntityInstantiator; @@ -66,7 +66,7 @@ public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields fieldType) { diff --git a/src/main/java/org/springframework/datastore/graph/neo4j/spi/relationship/Neo4jRelationshipBacking.aj b/src/main/java/org/springframework/datastore/graph/neo4j/spi/relationship/Neo4jRelationshipBacking.aj index f413ca05e..2c7c51815 100644 --- a/src/main/java/org/springframework/datastore/graph/neo4j/spi/relationship/Neo4jRelationshipBacking.aj +++ b/src/main/java/org/springframework/datastore/graph/neo4j/spi/relationship/Neo4jRelationshipBacking.aj @@ -12,8 +12,7 @@ import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.dao.InvalidDataAccessResourceUsageException; import org.springframework.datastore.graph.api.*; -import org.springframework.datastore.graph.neo4j.fieldaccess.FieldAccessorFactory; -import org.springframework.datastore.graph.neo4j.finder.FinderFactory; +import org.springframework.datastore.graph.neo4j.fieldaccess.DelegatingFieldAccessorFactory; import org.springframework.persistence.support.AbstractTypeAnnotatingMixinFields; import org.springframework.persistence.support.EntityInstantiator; @@ -104,7 +103,7 @@ public aspect Neo4jRelationshipBacking extends AbstractTypeAnnotatingMixinFields if (rel == null) { throw new InvalidDataAccessApiUsageException("Please set start node and end node before reading from other fields."); } - String propName = FieldAccessorFactory.getNeo4jPropertyName(f); + String propName = DelegatingFieldAccessorFactory.getNeo4jPropertyName(f); log.info("GET " + f + " <- Neo4J simple relationship property [" + propName + "]"); return deserializePropertyValue(rel.getProperty(propName, null), f.getType()); } @@ -129,7 +128,7 @@ public aspect Neo4jRelationshipBacking extends AbstractTypeAnnotatingMixinFields if (rel == null) { throw new InvalidDataAccessApiUsageException("Please set start node and end node before assigning to other fields."); } - String propName = FieldAccessorFactory.getNeo4jPropertyName(f); + String propName = DelegatingFieldAccessorFactory.getNeo4jPropertyName(f); if (newVal==null) { entity.getUnderlyingRelationship().removeProperty(propName); } @@ -164,11 +163,11 @@ public aspect Neo4jRelationshipBacking extends AbstractTypeAnnotatingMixinFields } private boolean isSerializableField(Field field) { - return !FieldAccessorFactory.isRelationshipField(field) && conversionService.canConvert(field.getType(), String.class); + return !DelegatingFieldAccessorFactory.isRelationshipField(field) && conversionService.canConvert(field.getType(), String.class); } private boolean isDeserializableField(Field field) { - return !FieldAccessorFactory.isRelationshipField(field) && conversionService.canConvert(String.class, field.getType()); + return !DelegatingFieldAccessorFactory.isRelationshipField(field) && conversionService.canConvert(String.class, field.getType()); } private Object serializePropertyValue(Object newVal, Class fieldType) {