Removed backreferences stuff. Added cascading of persist of dirty fields pointing to node entities or collections thereof.
This commit is contained in:
@@ -62,6 +62,4 @@ public interface EntityState<ENTITY extends GraphBacked<STATE>,STATE> {
|
||||
STATE getPersistentState();
|
||||
|
||||
ENTITY persist();
|
||||
|
||||
boolean refersTo(GraphBacked target);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 )
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user