From 8410d5e02410eb41ee3fb896df6aaaffed2c6a09 Mon Sep 17 00:00:00 2001 From: Michael Hunger Date: Wed, 15 Sep 2010 03:15:16 +0200 Subject: [PATCH] finished detachable entity accessors --- .../DetachableEntityStateAccessors.java | 159 ++++++++++++++++++ .../fieldaccess/EntityStateAccessors.java | 63 ++----- .../graph/neo4j/spi/node/Neo4jNodeBacking.aj | 10 +- 3 files changed, 173 insertions(+), 59 deletions(-) create mode 100644 src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/DetachableEntityStateAccessors.java diff --git a/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/DetachableEntityStateAccessors.java b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/DetachableEntityStateAccessors.java new file mode 100644 index 000000000..1a78e5575 --- /dev/null +++ b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/DetachableEntityStateAccessors.java @@ -0,0 +1,159 @@ +package org.springframework.datastore.graph.neo4j.fieldaccess; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.neo4j.graphdb.Node; +import org.springframework.datastore.graph.api.NodeBacked; +import org.springframework.datastore.graph.neo4j.support.GraphDatabaseContext; +import org.springframework.util.ObjectUtils; + +import java.lang.reflect.Field; +import java.util.ConcurrentModificationException; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Michael Hunger + * @since 15.09.2010 + */ +public class DetachableEntityStateAccessors implements EntityStateAccessors { + private final Map dirty = new HashMap(); + private final EntityStateAccessors delegate; + private final static Log log = LogFactory.getLog(DetachableEntityStateAccessors.class); + + public DetachableEntityStateAccessors(final STATE underlyingState, final ENTITY entity, final Class type, final GraphDatabaseContext graphDatabaseContext) { + this(new DefaultEntityStateAccessors(underlyingState, entity, type, graphDatabaseContext)); + } + + public DetachableEntityStateAccessors(final EntityStateAccessors delegate) { + this.delegate = delegate; + } + + @Override + public ENTITY getEntity() { + return delegate.getEntity(); + } + + @Override + public GraphDatabaseContext getGraphDatabaseContext() { + return delegate.getGraphDatabaseContext(); + } + + @Override + public Object getValue(final Field field) { + if (!transactionIsRunning()) { + if (!getEntity().hasUnderlyingNode() || isDirty(field)) { + log.warn("Outside of transaction, GET value from field " + field); + return null; + } + } + flushDirty(); + return delegate.getValue(field); + } + + private boolean transactionIsRunning() { + return delegate.getGraphDatabaseContext().transactionIsRunning(); + } + + @Override + public Object setValue(final Field field, final Object newVal) { + if (!transactionIsRunning()) { + final ENTITY entity = getEntity(); + if (!isDirty(field)) { + Object existingValue; + if (entity.hasUnderlyingNode()) existingValue = unwrap(delegate.getValue(field)); + else { + existingValue = getValueFromEntity(field); + if (existingValue == null) existingValue = getDefaultValue(field.getType()); + } + addDirty(field, existingValue); + } + return newVal; + } + flushDirty(); + return delegate.setValue(field, newVal); + } + + private Object getDefaultValue(final Class type) { + if (type.isPrimitive()) { + if (type.equals(boolean.class)) return false; + return 0; + } + return null; + } + + @Override + public void createAndAssignNode() { + delegate.createAndAssignNode(); + } + + /** + * always runs inside of a transaction + */ + private void flushDirty() { + final NodeBacked entity = getEntity(); + final boolean newNode = !entity.hasUnderlyingNode(); + if (newNode) { + createAndAssignNode(); + } + if (isDirty()) { + for (final Map.Entry entry : dirty.entrySet()) { + final Field field = entry.getKey(); + log.warn("Flushing dirty Entity new node " + newNode + " field " + field); + if (!newNode) { + checkConcurrentModification(entity, entry, field); + } + delegate.setValue(field, getValueFromEntity(field)); + } + clearDirty(); + } + } + + @Override + public void setNode(final Node node) { + delegate.setNode(node); + } + + + private Object getValueFromEntity(final Field field) { + final ENTITY entity = getEntity(); + try { + field.setAccessible(true); + return field.get(entity); + } catch (IllegalAccessException e) { + throw new RuntimeException("Error accessing field " + field + " in " + entity.getClass(), e); + } + } + + private void checkConcurrentModification(final NodeBacked entity, final Map.Entry entry, final Field field) { + final Object nodeValue = unwrap(delegate.getValue(field)); + final Object previousValue = entry.getValue(); + if (!ObjectUtils.nullSafeEquals(nodeValue, previousValue)) { + throw new ConcurrentModificationException("Node " + entity.getUnderlyingNode() + " field " + field + " changed in between previous " + previousValue + " current " + nodeValue); // todo or just overwrite + } + } + + private Object unwrap(final Object value) { + return (value instanceof DoReturn) ? ((DoReturn) value).value : value; + } + + private boolean isDirty() { + return !this.dirty.isEmpty(); + } + + private boolean isDirty(final Field f) { + return this.dirty.containsKey(f); + } + + private void clearDirty() { + this.dirty.clear(); + } + + private void addDirty(final Field f, final Object previousValue) { + this.dirty.put(f, previousValue); + } + +} + + + 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 index 210c20f62..0cf2f003e 100644 --- a/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/EntityStateAccessors.java +++ b/src/main/java/org/springframework/datastore/graph/neo4j/fieldaccess/EntityStateAccessors.java @@ -1,68 +1,25 @@ package org.springframework.datastore.graph.neo4j.fieldaccess; +import org.neo4j.graphdb.Node; +import org.springframework.datastore.graph.api.NodeBacked; import org.springframework.datastore.graph.neo4j.support.GraphDatabaseContext; -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 + * @since 15.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 interface EntityStateAccessors { + ENTITY getEntity(); - public EntityStateAccessors(final STATE underlyingState, final ENTITY entity, final Class type, final GraphDatabaseContext graphDatabaseContext) { - this.underlyingState = underlyingState; - this.entity = entity; - this.type = type; - createAccessorsAndListeners(type, graphDatabaseContext); - } + GraphDatabaseContext getGraphDatabaseContext(); - private void createAccessorsAndListeners(final Class type, final GraphDatabaseContext graphDatabaseContext) { - final DelegatingFieldAccessorFactory fieldAccessorFactory = new DelegatingFieldAccessorFactory(graphDatabaseContext); - 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 - } - }); - } + void setNode(Node node); - public Object getValue(final Field field) { - final FieldAccessor accessor = accessorFor(field); - if (accessor == null) { - System.err.println("No accessor for "+field); - return null; - } - else return accessor.getValue(entity); - } - public Object setValue(final Field field, final Object newVal) { - final FieldAccessor accessor = accessorFor(field); - Object result=newVal; - if (accessor!=null) result = accessor.setValue(entity, newVal); - else System.err.println("No accessor for "+field); - notifyListeners(field, result); // async ? - return result; - } + Object getValue(Field field); - private FieldAccessor accessorFor(Field field) { - return fieldAccessors.get(field); - } - - private void notifyListeners(Field field, Object result) { - if (!fieldAccessorListeners.containsKey(field) || fieldAccessorListeners.get(field) == null) return; - - for (final FieldAccessListener listener : fieldAccessorListeners.get(field)) { - listener.valueChanged(entity, null, result); // todo oldValue - } - } + Object setValue(Field field, Object newVal); + void createAndAssignNode(); } 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 30ad3ef0b..cf49c484a 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 @@ -15,10 +15,7 @@ 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.DoReturn; -import org.springframework.datastore.graph.neo4j.fieldaccess.EntityStateAccessors; -import org.springframework.datastore.graph.neo4j.fieldaccess.FieldAccessor; +import org.springframework.datastore.graph.neo4j.fieldaccess.*; import org.springframework.datastore.graph.neo4j.support.GraphDatabaseContext; import org.springframework.persistence.support.AbstractTypeAnnotatingMixinFields; import org.springframework.util.ObjectUtils; @@ -59,6 +56,7 @@ public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields(null,entity,entity.getClass(),graphDatabaseContext)); if (!graphDatabaseContext.transactionIsRunning()) { log.warn("New Nodebacked created outside of transaction "+ entity.getClass()); @@ -70,8 +68,8 @@ public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields(node,entity,entity.getClass(),graphDatabaseContext); log.info("User-defined constructor called on class " + entity.getClass() + "; created Node [" + entity.getUnderlyingNode() +"]; " + "Updating metamodel"); graphDatabaseContext.postEntityCreation(entity); @@ -82,7 +80,7 @@ public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields NodeBacked.underlyingState; + private EntityStateAccessors NodeBacked.underlyingState; private Map NodeBacked.dirty;