finished detachable entity accessors
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user