Removed backreferences stuff. Added cascading of persist of dirty fields pointing to node entities or collections thereof.

This commit is contained in:
David Montag
2011-04-11 15:17:47 -07:00
parent 92f91e026d
commit b7077e89f6
8 changed files with 85 additions and 129 deletions

View File

@@ -62,6 +62,4 @@ public interface EntityState<ENTITY extends GraphBacked<STATE>,STATE> {
STATE getPersistentState();
ENTITY persist();
boolean refersTo(GraphBacked target);
}

View File

@@ -147,7 +147,4 @@ public interface NodeBacked extends GraphBacked<Node> {
* @return the newly created relationship to the target node
*/
Relationship relateTo(NodeBacked target, String type);
// will possibly be used for object graphs
boolean refersTo(GraphBacked target);
}

View File

@@ -1,59 +0,0 @@
/**
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.graph.neo4j.fieldaccess;
import org.springframework.data.graph.core.EntityState;
import org.springframework.data.graph.core.GraphBacked;
import org.springframework.data.graph.core.NodeBacked;
import java.util.*;
/**
* @author mh
* @since 12.03.11
*/
public class BackReferences {
private List<NodeBacked> backrefs=new ArrayList<NodeBacked>();
private EntityState<?, ?> entityState;
public BackReferences(EntityState<?,?> entityState) {
this.entityState = entityState;
}
public void addBackReferences(Collection<NodeBacked> backReference) {
this.backrefs.addAll(backReference);
}
private void pruneInvalidBackRefs() {
GraphBacked entity = entityState.getEntity();
for (Iterator<NodeBacked> it = backrefs.iterator(); it.hasNext();) {
NodeBacked backRef = it.next();
if (backRef.refersTo(entity)) continue;
it.remove();
}
}
public void persistNeighbours() {
pruneInvalidBackRefs();
for (NodeBacked backref : backrefs) {
backref.persist();
}
}
}

View File

@@ -108,7 +108,6 @@ public abstract class DefaultEntityState<ENTITY extends GraphBacked<STATE>, STAT
}
}
protected Object getIdFromEntity() {
final Field idField = fieldAccessorFactoryProviders.getIdField();
if (idField==null) return null;
@@ -120,9 +119,4 @@ public abstract class DefaultEntityState<ENTITY extends GraphBacked<STATE>, STAT
return null;
}
}
@Override
public boolean refersTo(GraphBacked target) {
return false;
}
}

View File

@@ -19,15 +19,17 @@ package org.springframework.data.graph.neo4j.fieldaccess;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.neo4j.graphdb.Transaction;
import org.springframework.data.graph.annotation.RelatedTo;
import org.springframework.data.graph.core.EntityState;
import org.springframework.data.graph.core.GraphBacked;
import org.springframework.data.graph.core.NodeBacked;
import org.springframework.data.graph.core.EntityState;
import org.springframework.data.graph.neo4j.support.GraphDatabaseContext;
import org.springframework.util.ObjectUtils;
import java.lang.reflect.Field;
import java.util.*;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Map;
import static org.springframework.data.graph.neo4j.support.DoReturn.unwrap;
@@ -40,11 +42,9 @@ public class DetachedEntityState<ENTITY extends GraphBacked<STATE>, STATE> imple
protected final EntityState<ENTITY,STATE> delegate;
private final static Log log = LogFactory.getLog(DetachedEntityState.class);
private GraphDatabaseContext graphDatabaseContext;
private final BackReferences backReferences = null;
public DetachedEntityState(final EntityState<ENTITY, STATE> delegate, GraphDatabaseContext graphDatabaseContext) {
this.delegate = delegate;
this.graphDatabaseContext = graphDatabaseContext;
//this.backReferences = new BackReferences(this);
}
@Override
@@ -152,14 +152,31 @@ public class DetachedEntityState<ENTITY extends GraphBacked<STATE>, STATE> imple
// createAndAssignState();
throw new IllegalStateException("Flushing detached entity without a persistent state, this had to be created first.");
}
if (isDirty()) {
for (final Map.Entry<Field, ExistingValue> entry : dirty.entrySet()) {
final Field field = entry.getKey();
if (log.isDebugEnabled()) log.debug("Flushing dirty Entity new node " + entity.getPersistentState() + " field " + field+ " with value "+getValueFromEntity(field));
checkConcurrentModification(entity, entry, field);
delegate.setValue(field, getValueFromEntity(field));
}
final Map<Field, ExistingValue> dirtyCopy = new HashMap<Field, ExistingValue>(dirty);
clearDirty();
for (final Map.Entry<Field, ExistingValue> entry : dirtyCopy.entrySet()) {
final Field field = entry.getKey();
Object valueFromEntity = getValueFromEntity(field);
cascadePersist(valueFromEntity);
if (log.isDebugEnabled()) log.debug("Flushing dirty Entity new node " + entity.getPersistentState() + " field " + field+ " with value "+ valueFromEntity);
checkConcurrentModification(entity, entry, field);
delegate.setValue(field, valueFromEntity);
}
}
}
private void cascadePersist(Object valueFromEntity) {
if (valueFromEntity instanceof NodeBacked) {
((NodeBacked) valueFromEntity).persist();
}
if (valueFromEntity instanceof Collection) {
for (Object o : (Collection<Object>)valueFromEntity) {
if (o instanceof NodeBacked) {
((NodeBacked) o).persist();
}
}
}
}
@@ -217,7 +234,6 @@ public class DetachedEntityState<ENTITY extends GraphBacked<STATE>, STATE> imple
Transaction tx = graphDatabaseContext.beginTx();
try {
ENTITY result = delegate.persist();
//persistNeighbours();
flushDirty();
tx.success();
@@ -226,46 +242,4 @@ public class DetachedEntityState<ENTITY extends GraphBacked<STATE>, STATE> imple
tx.finish();
}
}
private void persistNeighbours() {
backReferences.persistNeighbours();
for (NodeBacked nodeBacked : getOutboundDirtyNodeEntities()) {
nodeBacked.persist();
}
}
private Set<NodeBacked> getOutboundDirtyNodeEntities() {
HashSet<NodeBacked> result = new HashSet<NodeBacked>();
for (Field field : dirty.keySet()) {
if (handleSingleField(result, field)) continue;
handleOneToMany(result, field);
}
return result;
}
private boolean handleOneToMany(HashSet<NodeBacked> result, Field field) {
if ((Collection.class.isAssignableFrom(field.getType())) && field.isAnnotationPresent(RelatedTo.class)) {
result.addAll((Collection) getValueFromEntity(field));
return true;
}
return false;
}
private boolean handleSingleField(HashSet<NodeBacked> result, Field field) {
if (NodeBacked.class.isAssignableFrom(field.getType())) {
Object value = getValueFromEntity(field);
if (value!=null) {
result.add((NodeBacked) value);
}
return true;
}
return false;
}
public boolean refersTo(GraphBacked target) {
return getOutboundDirtyNodeEntities().contains(target);
}
}

View File

@@ -119,9 +119,6 @@ public aspect Neo4jNodeBacking { // extends AbstractTypeAnnotatingMixinFields<No
public <T extends NodeBacked> T NodeBacked.persist() {
return (T)this.entityState.persist();
}
public boolean NodeBacked.refersTo(GraphBacked target) {
return this.entityState.refersTo(target);
}
public void NodeBacked.setPersistentState(Node n) {
if (this.entityState == null) {

View File

@@ -67,6 +67,10 @@ class HasRelationshipMatcher extends TypeSafeMatcher<Node>
{
description.appendText( "Expected relationship named " + relationshipTypeName + " to " +(other==null ? "unspecified": other)+"\r\n got: " );
if (relationships == null) {
description.appendValue("[]");
return;
}
List<String> types = new ArrayList<String>();
for ( Relationship rel : relationships )
{

View File

@@ -23,7 +23,10 @@ import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.NotFoundException;
import org.neo4j.graphdb.NotInTransactionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.graph.neo4j.Friendship;
import org.springframework.data.graph.neo4j.Group;
import org.springframework.data.graph.neo4j.Person;
import org.springframework.data.graph.neo4j.repository.DirectGraphRepositoryFactory;
@@ -43,7 +46,6 @@ import static org.springframework.data.graph.neo4j.support.HasRelationshipMatche
@RunWith( SpringJUnit4ClassRunner.class )
@ContextConfiguration( locations = {"classpath:org/springframework/data/graph/neo4j/support/Neo4jGraphPersistenceTest-context.xml"} )
public class ModificationOutsideOfTransactionTest
{
@@ -71,7 +73,6 @@ public class ModificationOutsideOfTransactionTest
}
@Test
@Ignore("ignored until subgraph persisting is added")
public void testCreateSubgraphOutsideOfTransactionPersistInDirectionOfRel() {
Person michael = new Person("Michael", 35);
Person emil = new Person("Emil", 31);
@@ -87,6 +88,48 @@ public class ModificationOutsideOfTransactionTest
}
@Test
public void testCreateSubgraphOutsideOfTransactionPersistWithImmediateCycle() {
Person michael = new Person("Michael", 35);
Person emil = new Person("Emil", 31);
michael.setBoss(emil);
emil.setBoss(michael);
assertEquals(emil, michael.getBoss());
assertEquals(michael, emil.getBoss());
assertFalse(hasPersistentState(michael));
assertFalse(hasPersistentState(emil));
michael.persist();
assertThat(nodeFor(michael), hasRelationship("boss", nodeFor(emil)));
assertThat(nodeFor(emil), hasRelationship("boss", nodeFor(michael)));
}
@Test
public void testCreateSubgraphOutsideOfTransactionPersistWithCycle() {
Person michael = new Person("Michael", 35);
Person david = new Person("David", 27);
Person emil = new Person("Emil", 31);
michael.setBoss(emil);
david.setBoss(michael);
emil.setBoss(david);
assertEquals(emil, michael.getBoss());
assertEquals(michael, david.getBoss());
assertEquals(david, emil.getBoss());
assertFalse(hasPersistentState(michael));
assertFalse(hasPersistentState(david));
assertFalse(hasPersistentState(emil));
michael.persist();
assertThat(nodeFor(michael), hasRelationship("boss", nodeFor(emil)));
assertThat(nodeFor(michael), hasRelationship("boss", nodeFor(david)));
assertThat(nodeFor(david), hasRelationship("boss", nodeFor(michael)));
assertThat(nodeFor(david), hasRelationship("boss", nodeFor(emil)));
assertThat(nodeFor(emil), hasRelationship("boss", nodeFor(david)));
assertThat(nodeFor(emil), hasRelationship("boss", nodeFor(michael)));
}
@Ignore("ignored until subgraph persisting is added")
@Test
public void testCreateSubgraphOutsideOfTransactionPersistInReverseDirectionOfRel() {
@@ -103,6 +146,14 @@ public class ModificationOutsideOfTransactionTest
assertThat(nodeFor(emil), hasRelationship("boss", nodeFor(michael)));
}
// TODO: Would be nice if this worked outside of a tx
@Test(expected = NotInTransactionException.class)
public void foo() {
Person p = persistedPerson("Michael", 35);
Person p2 = persistedPerson("David", 26);
Friendship f = p.knows(p2);
}
@Test
public void testSetPropertyOutsideTransaction()
{