finished detachable entity accessors

This commit is contained in:
Michael Hunger
2010-09-15 03:15:16 +02:00
parent 392d4098aa
commit 8410d5e024
3 changed files with 173 additions and 59 deletions

View File

@@ -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<ENTITY extends NodeBacked, STATE> implements EntityStateAccessors<ENTITY> {
private final Map<Field, Object> dirty = new HashMap<Field, Object>();
private final EntityStateAccessors<ENTITY> delegate;
private final static Log log = LogFactory.getLog(DetachableEntityStateAccessors.class);
public DetachableEntityStateAccessors(final STATE underlyingState, final ENTITY entity, final Class<? extends ENTITY> type, final GraphDatabaseContext graphDatabaseContext) {
this(new DefaultEntityStateAccessors<ENTITY, STATE>(underlyingState, entity, type, graphDatabaseContext));
}
public DetachableEntityStateAccessors(final EntityStateAccessors<ENTITY> 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<Field, Object> 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<Field, Object> 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);
}
}

View File

@@ -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<ENTITY, STATE> {
private final STATE underlyingState;
private final ENTITY entity;
private final Class<? extends ENTITY> type;
private final Map<Field,FieldAccessor<ENTITY,?>> fieldAccessors=new HashMap<Field, FieldAccessor<ENTITY,?>>();
private final Map<Field,List<FieldAccessListener<ENTITY,?>>> fieldAccessorListeners=new HashMap<Field, List<FieldAccessListener<ENTITY,?>>>();
public interface EntityStateAccessors<ENTITY extends NodeBacked> {
ENTITY getEntity();
public EntityStateAccessors(final STATE underlyingState, final ENTITY entity, final Class<? extends ENTITY> type, final GraphDatabaseContext graphDatabaseContext) {
this.underlyingState = underlyingState;
this.entity = entity;
this.type = type;
createAccessorsAndListeners(type, graphDatabaseContext);
}
GraphDatabaseContext getGraphDatabaseContext();
private void createAccessorsAndListeners(final Class<? extends ENTITY> 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<ENTITY, ?> 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<ENTITY, ?> 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<ENTITY, ?> 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<ENTITY, ?> listener : fieldAccessorListeners.get(field)) {
listener.valueChanged(entity, null, result); // todo oldValue
}
}
Object setValue(Field field, Object newVal);
void createAndAssignNode();
}

View File

@@ -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<GraphEn
// Create a new node in the Graph if no Node was passed in a constructor
before(NodeBacked entity) : arbitraryUserConstructorOfNodeBackedObject(entity) {
entity.underlyingState=new DetachableEntityStateAccessors(new DefaultEntityStateAccessors<NodeBacked,Node>(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<GraphEn
private void createAndAssignNode(NodeBacked entity) {
try {
Node node=graphDatabaseContext.createNode();
entity.underlyingState.setNode(node);
entity.setUnderlyingNode(node);
entity.underlyingState=new EntityStateAccessors<NodeBacked,Node>(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<GraphEn
// Introduced field
private Node NodeBacked.underlyingNode;
private EntityStateAccessors<NodeBacked,Node> NodeBacked.underlyingState;
private EntityStateAccessors<NodeBacked> NodeBacked.underlyingState;
private Map<Field,Object> NodeBacked.dirty;