From 50f7b92115d5b0f45b250161f20eed14bfcc791f Mon Sep 17 00:00:00 2001 From: Michael Simons Date: Fri, 8 Jul 2022 13:42:16 +0200 Subject: [PATCH] fix: Handle `null` properties. When using stored procedures the transport of actual `null` properties in entities is possible and leads to a NPE. This changes checks for `NO_VALUE` respectively `NullValue` and filters literal `null` properties afterwards. Closes #909. --- .../org/neo4j/ogm/result/adapter/BaseAdapter.java | 1 + .../ogm/drivers/bolt/driver/BoltEntityAdapter.java | 3 ++- .../embedded/driver/EmbeddedEntityAdapter.java | 3 ++- .../ogm/context/SingleUseEntityMapperTest.java | 13 +++++++++++++ .../neo4j/ogm/domain/cineasts/minimum/Movie.java | 4 ++++ 5 files changed, 22 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/org/neo4j/ogm/result/adapter/BaseAdapter.java b/api/src/main/java/org/neo4j/ogm/result/adapter/BaseAdapter.java index f754af62a..f64a7da3c 100644 --- a/api/src/main/java/org/neo4j/ogm/result/adapter/BaseAdapter.java +++ b/api/src/main/java/org/neo4j/ogm/result/adapter/BaseAdapter.java @@ -32,6 +32,7 @@ public class BaseAdapter { public Map convertArrayPropertiesToCollection(Map properties) { return properties.entrySet().stream() + .filter(e -> e.getValue() != null) .collect(toMap(Map.Entry::getKey, BaseAdapter::convertOrReturnSelf)); } diff --git a/bolt-driver/src/main/java/org/neo4j/ogm/drivers/bolt/driver/BoltEntityAdapter.java b/bolt-driver/src/main/java/org/neo4j/ogm/drivers/bolt/driver/BoltEntityAdapter.java index 5fe4016a8..fa68efce0 100644 --- a/bolt-driver/src/main/java/org/neo4j/ogm/drivers/bolt/driver/BoltEntityAdapter.java +++ b/bolt-driver/src/main/java/org/neo4j/ogm/drivers/bolt/driver/BoltEntityAdapter.java @@ -24,6 +24,7 @@ import org.neo4j.driver.internal.value.ListValue; import org.neo4j.driver.Value; +import org.neo4j.driver.internal.value.NullValue; import org.neo4j.driver.types.Entity; import org.neo4j.driver.types.Node; import org.neo4j.driver.types.Path; @@ -111,7 +112,7 @@ public List relsInPath(Object pathValue) { private Object toMapped(Value value) { - if (value == null) { + if (value == null || value instanceof NullValue) { return null; } diff --git a/embedded-driver/src/main/java/org/neo4j/ogm/drivers/embedded/driver/EmbeddedEntityAdapter.java b/embedded-driver/src/main/java/org/neo4j/ogm/drivers/embedded/driver/EmbeddedEntityAdapter.java index ebe4158db..93a95ef63 100644 --- a/embedded-driver/src/main/java/org/neo4j/ogm/drivers/embedded/driver/EmbeddedEntityAdapter.java +++ b/embedded-driver/src/main/java/org/neo4j/ogm/drivers/embedded/driver/EmbeddedEntityAdapter.java @@ -31,6 +31,7 @@ import org.neo4j.graphdb.Entity; import org.neo4j.graphdb.Relationship; import org.neo4j.ogm.driver.TypeSystem; +import org.neo4j.values.storable.Values; /** * Helper methods for embedded graph entities @@ -116,7 +117,7 @@ public Map getAllProperties(Entity propertyContainer) { private Object toMapped(Object value) { - if (value == null) { + if (value == null || Values.NO_VALUE == value) { return null; } diff --git a/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/context/SingleUseEntityMapperTest.java b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/context/SingleUseEntityMapperTest.java index 79c3c21fc..86e54cb60 100644 --- a/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/context/SingleUseEntityMapperTest.java +++ b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/context/SingleUseEntityMapperTest.java @@ -33,6 +33,7 @@ import org.assertj.core.api.Condition; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.neo4j.ogm.domain.cineasts.minimum.Actor; @@ -359,6 +360,18 @@ public void shouldWorkWithCustomConvertersOnListProperty() { .containsExactlyInAnyOrder("foo", "bar"); } + @Test // GH-909 + @Ignore("This test requires APOC on the server, as there is no other way to create literal null properties on entities.") + public void shouldDealWithArtificalNullValues() { + SingleUseEntityMapper entityMapper = + new SingleUseEntityMapper(sessionFactory.metaData(), + new ReflectionEntityInstantiator(sessionFactory.metaData())); + Movie movie = sessionFactory.openSession() + .queryForObject(Movie.class, "return apoc.create.vNode(['Movie'],{name: null, foo: null})", + Collections.emptyMap()); + assertThat(movie.getName()).isNull(); + } + @Test // GH-718 public void queryResultShouldHandleNodeAndRelationshipEntities() { diff --git a/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/domain/cineasts/minimum/Movie.java b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/domain/cineasts/minimum/Movie.java index a423166c4..1c8e9bc49 100644 --- a/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/domain/cineasts/minimum/Movie.java +++ b/neo4j-ogm-tests/neo4j-ogm-integration-tests/src/test/java/org/neo4j/ogm/domain/cineasts/minimum/Movie.java @@ -32,4 +32,8 @@ public Movie() { public Movie(String name) { this.name = name; } + + public String getName() { + return name; + } }