added attach method to entity(+accessors), still have to work out the transient case

This commit is contained in:
Michael Hunger
2011-02-10 00:08:44 +01:00
parent b16c884f7a
commit 0867562ee6
12 changed files with 121 additions and 71 deletions

View File

@@ -16,7 +16,7 @@
<org.mockito.version>1.8.4</org.mockito.version>
<org.slf4j.version>1.5.10</org.slf4j.version>
<org.springframework.version>3.0.5.RELEASE</org.springframework.version>
<data.commons.version>1.0.0.M2</data.commons.version>
<data.commons.version>1.0.0.BUILD-SNAPSHOT</data.commons.version>
<neo4j.version>1.2</neo4j.version>
<neo4j-utils.version>1.2-${neo4j.version}</neo4j-utils.version>
</properties>

View File

@@ -51,4 +51,11 @@ public @interface NodeEntity {
* @return true if the entity is only partially managed by the {@link org.springframework.data.graph.neo4j.support.node.Neo4jNodeBacking} aspect.
*/
boolean partial() default false;
/**
* if set the entity will be attached to the graph store at creation time, otherwise entity.attach() has to be called manually.
* @return
*/
boolean autoAttach() default false;
}

View File

@@ -182,6 +182,17 @@ public class DetachableEntityStateAccessors<ENTITY extends GraphBacked<STATE>, S
public GraphDatabaseContext getGraphDatabaseContext() {
return graphDatabaseContext;
}
@Override
public ENTITY attach() {
if (graphDatabaseContext.transactionIsRunning()) {
return delegate.attach();
} else {
log.warn("New Nodebacked tried to attach outside of transaction " + delegate.getEntity().getClass());
return getEntity();
}
}
}

View File

@@ -57,4 +57,6 @@ public interface EntityStateAccessors<ENTITY extends GraphBacked<STATE>,STATE> {
boolean hasUnderlyingState();
STATE getUnderlyingState();
ENTITY attach();
}

View File

@@ -47,7 +47,7 @@ public class JpaIdFieldAccessListenerFactory implements FieldAccessorListenerFac
public void valueChanged(NodeBacked nodeBacked, Object oldVal, Object newVal) {
if (newVal != null) {
EntityStateAccessors stateAccessors=nodeBacked.getStateAccessors();
stateAccessors.createAndAssignState();
stateAccessors.attach();
}
}
}

View File

@@ -7,84 +7,89 @@ import org.springframework.data.graph.core.GraphBacked;
import org.springframework.data.graph.neo4j.support.GraphDatabaseContext;
import java.lang.reflect.Field;
import java.util.concurrent.Callable;
public class NestedTransactionEntityStateAccessors<ENTITY extends GraphBacked<STATE>, STATE> implements
EntityStateAccessors<ENTITY, STATE>
{
EntityStateAccessors<ENTITY, STATE> {
protected final EntityStateAccessors<ENTITY, STATE> delegate;
private final static Log log = LogFactory.getLog( NestedTransactionEntityStateAccessors.class );
private final static Log log = LogFactory.getLog(NestedTransactionEntityStateAccessors.class);
private GraphDatabaseContext graphDatabaseContext;
public NestedTransactionEntityStateAccessors( final EntityStateAccessors<ENTITY, STATE> delegate,
GraphDatabaseContext graphDatabaseContext )
{
public NestedTransactionEntityStateAccessors(final EntityStateAccessors<ENTITY, STATE> delegate,
GraphDatabaseContext graphDatabaseContext) {
this.delegate = delegate;
this.graphDatabaseContext = graphDatabaseContext;
}
@Override
public ENTITY getEntity()
{
public ENTITY getEntity() {
return delegate.getEntity();
}
public void setUnderlyingState( STATE state )
{
delegate.setUnderlyingState( state );
public void setUnderlyingState(STATE state) {
delegate.setUnderlyingState(state);
}
@Override
public Object getValue( Field field )
{
return delegate.getValue( field );
public Object getValue(Field field) {
return delegate.getValue(field);
}
@Override
public boolean isWritable( Field field )
{
return delegate.isWritable( field );
public boolean isWritable(Field field) {
return delegate.isWritable(field);
}
@Override
public Object setValue( Field field, Object newVal )
{
Transaction tx = graphDatabaseContext.beginTx();
try
{
Object result = delegate.setValue( field, newVal );
tx.success();
return result;
} finally
{
tx.finish();
}
public Object setValue(final Field field, final Object newVal) {
return doInTransaction(new Callable<Object>() {
public Object call() throws Exception {
return delegate.setValue(field,newVal);
}
});
}
@Override
public void createAndAssignState()
{
Transaction tx = graphDatabaseContext.beginTx();
try
{
delegate.createAndAssignState();
tx.success();
} finally
{
tx.finish();
}
public void createAndAssignState() {
doInTransaction(new Callable<Void>() {
public Void call() throws Exception {
delegate.createAndAssignState();
return null;
}
});
}
@Override
public boolean hasUnderlyingState()
{
public boolean hasUnderlyingState() {
return delegate.hasUnderlyingState();
}
@Override
public STATE getUnderlyingState()
{
public STATE getUnderlyingState() {
return delegate.getUnderlyingState();
}
@Override
public ENTITY attach() {
return doInTransaction(new Callable<ENTITY>() {
public ENTITY call() throws Exception {
return delegate.attach();
}
});
}
protected <T> T doInTransaction(Callable<T> call) {
Transaction tx = graphDatabaseContext.beginTx();
try {
T result = call.call();
tx.success();
return result;
} catch (Exception e) {
tx.failure();
if (e instanceof RuntimeException) throw (RuntimeException) e;
throw new RuntimeException(e);
} finally {
tx.finish();
}
}
}

View File

@@ -21,6 +21,7 @@ import org.neo4j.graphdb.NotInTransactionException;
import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.data.graph.core.NodeBacked;
import org.springframework.data.graph.neo4j.support.GraphDatabaseContext;
import org.springframework.persistence.support.StateProvider;
/**
* @author Michael Hunger
@@ -56,4 +57,16 @@ public class NodeEntityStateAccessors<ENTITY extends NodeBacked> extends Default
throw new InvalidDataAccessResourceUsageException("Not in a Neo4j transaction.", e);
}
}
@Override
public ENTITY attach() {
Node node = StateProvider.retrieveState();
if (node != null) {
setUnderlyingState(node);
} else {
createAndAssignState();
}
return entity;
}
}

View File

@@ -33,18 +33,19 @@ public class NodeEntityStateAccessorsFactory {
private NodeDelegatingFieldAccessorFactory nodeDelegatingFieldAccessorFactory;
public EntityStateAccessors<NodeBacked,Node> getEntityStateAccessors(final NodeBacked entity) {
final NodeEntity graphEntityAnnotation = entity.getClass().getAnnotation(NodeEntity.class);
if (graphEntityAnnotation!=null && graphEntityAnnotation.partial()) {
return new DetachableEntityStateAccessors<NodeBacked, Node>(
new PartialNodeEntityStateAccessors<NodeBacked>(null, entity, entity.getClass(), graphDatabaseContext, finderFactory), graphDatabaseContext) {
final NodeEntity graphEntityAnnotation = entity.getClass().getAnnotation(NodeEntity.class); // todo cache ??
boolean autoAttach = graphEntityAnnotation.autoAttach();
if (graphEntityAnnotation.partial()) {
PartialNodeEntityStateAccessors<NodeBacked> partialNodeEntityStateAccessors = new PartialNodeEntityStateAccessors<NodeBacked>(null, entity, entity.getClass(), graphDatabaseContext, finderFactory);
return new DetachableEntityStateAccessors<NodeBacked, Node>(partialNodeEntityStateAccessors, graphDatabaseContext) {
@Override
protected boolean transactionIsRunning() {
return super.transactionIsRunning() && getId(entity, entity.getClass()) != null;
}
};
} else {
return new NestedTransactionEntityStateAccessors<NodeBacked, Node>(
new NodeEntityStateAccessors<NodeBacked>(null,entity,entity.getClass(), graphDatabaseContext, nodeDelegatingFieldAccessorFactory),graphDatabaseContext);
NodeEntityStateAccessors<NodeBacked> nodeEntityStateAccessors = new NodeEntityStateAccessors<NodeBacked>(null, entity, entity.getClass(), graphDatabaseContext, nodeDelegatingFieldAccessorFactory);
return new NestedTransactionEntityStateAccessors<NodeBacked, Node>(nodeEntityStateAccessors,graphDatabaseContext);
}
}

View File

@@ -25,6 +25,7 @@ import org.springframework.data.graph.annotation.RelatedTo;
import org.springframework.data.graph.core.NodeBacked;
import org.springframework.data.graph.neo4j.finder.FinderFactory;
import org.springframework.data.graph.neo4j.support.GraphDatabaseContext;
import org.springframework.persistence.support.StateProvider;
import javax.persistence.Id;
import java.lang.reflect.Field;
@@ -109,7 +110,7 @@ public class PartialNodeEntityStateAccessors<ENTITY extends NodeBacked> extends
final Object id = getId(entity,type);
if (id == null) return;
final String foreignId = createForeignId(id);
IndexHits<Node> indexHits = graphDatabaseContext.getNodeIndex(FOREIGN_ID_INDEX).get(FOREIGN_ID, foreignId.toString());
IndexHits<Node> indexHits = graphDatabaseContext.getNodeIndex(FOREIGN_ID_INDEX).get(FOREIGN_ID, foreignId);
Node node = indexHits.hasNext() ? indexHits.next() : null;
if (node == null) {
node = graphDatabaseContext.createNode();
@@ -127,6 +128,17 @@ public class PartialNodeEntityStateAccessors<ENTITY extends NodeBacked> extends
}
}
@Override
public ENTITY attach() {
Node node = StateProvider.retrieveState();
if (node != null) {
setUnderlyingState(node);
} else {
createAndAssignState();
}
return entity;
}
private void persistForeignId(Node node, Object id) {
if (!node.hasProperty(FOREIGN_ID) && id != null) {
final String foreignId = createForeignId(id);

View File

@@ -86,4 +86,11 @@ public class RelationshipEntityStateAccessors<ENTITY extends RelationshipBacked>
throw new InvalidDataAccessResourceUsageException("Not in a Neo4j transaction.", e);
}
}
@Override
public ENTITY attach() {
createAndAssignState();
return entity;
}
}

View File

@@ -76,15 +76,13 @@ public aspect Neo4jNodeBacking extends AbstractTypeAnnotatingMixinFields<NodeEnt
} else {
if (entity.stateAccessors != null) return;
entity.stateAccessors = entityStateAccessorsFactory.getEntityStateAccessors(entity);
Node node = StateProvider.retrieveState();
if (node != null) {
entity.setUnderlyingState(node);
} else {
entity.stateAccessors.createAndAssignState();
}
entity.attach();
}
}
public NodeBacked NodeBacked.attach() {
return this.stateAccessors.attach();
}
/**
* State accessors that encapsulate the underlying state and the behaviour related to it (field access, creation)
*/

View File

@@ -54,10 +54,6 @@ public aspect Neo4jRelationshipBacking extends AbstractTypeAnnotatingMixinFields
this.entityStateAccessorsFactory = entityStateAccessorsFactory;
}
/**
* field for underlying relationship
*/
private Relationship RelationshipBacked.underlyingRelationship;
/**
* field for {@link EntityStateAccessors} that takes care of all entity operations
*/
@@ -68,20 +64,18 @@ public aspect Neo4jRelationshipBacking extends AbstractTypeAnnotatingMixinFields
* @param r
*/
public void RelationshipBacked.setUnderlyingState(Relationship r) {
this.underlyingRelationship = r;
if (this.stateAccessors == null) {
this.stateAccessors = Neo4jRelationshipBacking.aspectOf().entityStateAccessorsFactory.getEntityStateAccessors(this);
} else {
this.stateAccessors.setUnderlyingState(r);
}
this.stateAccessors.setUnderlyingState(r);
}
public Relationship RelationshipBacked.getUnderlyingState() {
return underlyingRelationship;
return this.stateAccessors.getUnderlyingState();
}
public boolean RelationshipBacked.hasUnderlyingRelationship() {
return underlyingRelationship!=null;
return this.stateAccessors.hasUnderlyingState();
}
/**
@@ -89,7 +83,7 @@ public aspect Neo4jRelationshipBacking extends AbstractTypeAnnotatingMixinFields
*/
public Long RelationshipBacked.getId() {
if (!hasUnderlyingRelationship()) return null;
return underlyingRelationship.getId();
return getUnderlyingState().getId();
}