Skip to content

Commit

Permalink
GH-640 - Store visited nodes under their native graph id if possible. (
Browse files Browse the repository at this point in the history
…#642)

This change tries to store visited nodes under their native graph id in the compile context if possible. Thus, they are recognized even when a user uses a different instance of the same entity for persisting state.

This fixes #640.
  • Loading branch information
michael-simons authored Jul 2, 2019
1 parent 7be19d3 commit bfcadad
Show file tree
Hide file tree
Showing 7 changed files with 371 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public class EntityGraphMapper implements EntityMapper {

private final MetaData metaData;
private final MappingContext mappingContext;
private final Compiler compiler = new MultiStatementCypherCompiler();
private final Compiler compiler;
/**
* Default supplier for write protection: Always write all the stuff.
*/
Expand All @@ -73,6 +73,7 @@ public class EntityGraphMapper implements EntityMapper {
public EntityGraphMapper(MetaData metaData, MappingContext mappingContext) {
this.metaData = metaData;
this.mappingContext = mappingContext;
this.compiler = new MultiStatementCypherCompiler(mappingContext::nativeId);
}

public void addWriteProtection(BiFunction<WriteProtectionTarget, Class<?>, Predicate<Object>> writeProtectionSupplier) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@
*/
public class CypherContext implements CompileContext {

/**
* Stores the the builder and the horizon of visited objects. The key is either the native graph id of the entity
* if the id is available (that is not null and greater 0L) or otherwise the entity itself.
*/
private final Map<Object, NodeBuilderHorizonPair> visitedObjects = new IdentityHashMap<>();
private final Set<Long> visitedRelationshipEntities = new HashSet<>();

Expand All @@ -52,18 +56,26 @@ public class CypherContext implements CompileContext {

private final Compiler compiler;

public CypherContext(Compiler compiler) {
private final Function<Object, Long> nativeIdProvider;

public CypherContext(Compiler compiler, Function<Object, Long> nativeIdProvider) {
this.compiler = compiler;
this.nativeIdProvider = nativeIdProvider;
}

private Object getIdentity(Object entity) {
Long nativeId = this.nativeIdProvider.apply(entity);
return (nativeId == null || nativeId < 0) ? entity : nativeId;
}

public boolean visited(Object entity, int horizon) {
NodeBuilderHorizonPair pair = visitedObjects.get(entity);
NodeBuilderHorizonPair pair = visitedObjects.get(getIdentity(entity));
return pair != null && (horizon < 0 || pair.getHorizon() > horizon);
}

@Override
public void visit(Object entity, NodeBuilder nodeBuilder, int horizon) {
this.visitedObjects.put(entity, new NodeBuilderHorizonPair(nodeBuilder, horizon));
this.visitedObjects.put(getIdentity(entity), new NodeBuilderHorizonPair(nodeBuilder, horizon));
}

public void registerRelationship(Mappable mappedRelationship) {
Expand All @@ -76,7 +88,7 @@ public boolean removeRegisteredRelationship(Mappable mappedRelationship) {

@Override
public NodeBuilder visitedNode(Object entity) {
NodeBuilderHorizonPair pair = this.visitedObjects.get(entity);
NodeBuilderHorizonPair pair = this.visitedObjects.get(getIdentity(entity));
return pair != null ? pair.getNodeBuilder() : null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
*
* @author Luanne Misquitta
* @author Mark Angrish
* @author Michael J. Simons
*/
public class MultiStatementCypherCompiler implements Compiler {

Expand All @@ -62,8 +63,8 @@ public class MultiStatementCypherCompiler implements Compiler {
private final List<RelationshipBuilder> deletedRelationshipEntityBuilders;
private StatementFactory statementFactory;

public MultiStatementCypherCompiler() {
this.context = new CypherContext(this);
public MultiStatementCypherCompiler(Function<Object, Long> nativeIdProvider) {
this.context = new CypherContext(this, nativeIdProvider);
this.newNodeBuilders = new ArrayList<>();
this.newRelationshipBuilders = new ArrayList<>();
this.existingNodeBuilders = new ArrayList<>();
Expand Down
115 changes: 115 additions & 0 deletions test/src/test/java/org/neo4j/ogm/domain/gh640/MyNode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* Copyright (c) 2002-2019 "Neo4j,"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* 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.neo4j.ogm.domain.gh640;

import static org.neo4j.ogm.annotation.Relationship.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import org.neo4j.ogm.annotation.GeneratedValue;
import org.neo4j.ogm.annotation.Id;
import org.neo4j.ogm.annotation.NodeEntity;
import org.neo4j.ogm.annotation.Relationship;

/**
* @author Michael J. Simons
*/
@NodeEntity
public class MyNode {

@Id
@GeneratedValue
private Long id;

private String name;

@Relationship(type = "REL_ONE", direction = INCOMING)
private MyNode refOne;

@Relationship(type = "REL_TWO", direction = UNDIRECTED)
private List<MyNode> refTwo = new ArrayList<>();

public MyNode(String name) {
this.name = name;
}

public MyNode() {
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Long getId() {
return id;
}

public MyNode getRefOne() {
return refOne;
}

public void setRefOne(MyNode refOne) {
this.refOne = refOne;
}

public List<MyNode> getRefTwo() {
return refTwo;
}

public void setRefTwo(List<MyNode> refTwo) {
this.refTwo = refTwo;
}

public MyNode copy() {
MyNode n = new MyNode();
n.id = id;
n.name = name;
n.setRefOne(refOne);
n.setRefTwo(refTwo);
return n;
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
MyNode myNode = (MyNode) o;
return Objects.equals(id, myNode.id);
}

@Override
public int hashCode() {
return Objects.hash(id);
}

@Override public String toString() {
return "MyNode{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright (c) 2002-2019 "Neo4j,"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* 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.neo4j.ogm.domain.gh640;

import static org.neo4j.ogm.annotation.Relationship.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import org.neo4j.ogm.annotation.GeneratedValue;
import org.neo4j.ogm.annotation.Id;
import org.neo4j.ogm.annotation.NodeEntity;
import org.neo4j.ogm.annotation.Relationship;

/**
* @author Michael J. Simons
*/
@NodeEntity
public class MyNodeWithAssignedId {

@Id
private String name;

@Relationship(type = "REL_ONE", direction = INCOMING)
private MyNodeWithAssignedId refOne;

@Relationship(type = "REL_TWO", direction = UNDIRECTED)
private List<MyNodeWithAssignedId> refTwo = new ArrayList<>();

public MyNodeWithAssignedId(String name) {
this.name = name;
}

public MyNodeWithAssignedId() {
}

public String getName() {
return name;
}

public MyNodeWithAssignedId getRefOne() {
return refOne;
}

public void setRefOne(MyNodeWithAssignedId refOne) {
this.refOne = refOne;
}

public List<MyNodeWithAssignedId> getRefTwo() {
return refTwo;
}

public void setRefTwo(List<MyNodeWithAssignedId> refTwo) {
this.refTwo = refTwo;
}

public MyNodeWithAssignedId copy() {
MyNodeWithAssignedId n = new MyNodeWithAssignedId();
n.name = name;
n.setRefOne(refOne);
n.setRefTwo(refTwo);
return n;
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
MyNodeWithAssignedId myNode = (MyNodeWithAssignedId) o;
return Objects.equals(name, myNode.name);
}

@Override
public int hashCode() {
return Objects.hash(name);
}

@Override public String toString() {
return "MyNodeWithAssignedId{" +
"name='" + name + '\'' +
'}';
}
}
Loading

0 comments on commit bfcadad

Please sign in to comment.