diff --git a/src/main/java/org/springframework/data/neo4j/core/mapping/Neo4jMappingContext.java b/src/main/java/org/springframework/data/neo4j/core/mapping/Neo4jMappingContext.java index 0e13dd5d1..3af5d1a86 100644 --- a/src/main/java/org/springframework/data/neo4j/core/mapping/Neo4jMappingContext.java +++ b/src/main/java/org/springframework/data/neo4j/core/mapping/Neo4jMappingContext.java @@ -289,7 +289,8 @@ public final class Neo4jMappingContext extends AbstractMappingContext superclass = typeInformation.getType().getSuperclass(); + Class type = typeInformation.getType(); + Class superclass = type.getSuperclass(); if (isValidParentNode(superclass)) { synchronized (this) { @@ -303,6 +304,17 @@ public final class Neo4jMappingContext extends AbstractMappingContext typeInterface : type.getInterfaces()) { + if (isValidEntityInterface(typeInterface)) { + super.setStrict(false); + Neo4jPersistentEntity parentNodeDescription = getPersistentEntity(typeInterface); + if (parentNodeDescription != null) { + parentNodeDescription.addChildNodeDescription(newEntity); + } + this.setStrict(strict); + } + } + return newEntity; } @@ -316,6 +328,10 @@ public final class Neo4jMappingContext extends AbstractMappingContext typeInterface) { + return typeInterface.isAnnotationPresent(Node.class); + } + /* * (non-Javadoc) * @see org.springframework.data.mapping.context.AbstractMappingContext#createPersistentProperty(org.springframework.data.mapping.model.Property, org.springframework.data.mapping.model.MutablePersistentEntity, org.springframework.data.mapping.model.SimpleTypeHolder) diff --git a/src/test/java/org/springframework/data/neo4j/integration/imperative/InheritanceMappingIT.java b/src/test/java/org/springframework/data/neo4j/integration/imperative/InheritanceMappingIT.java index a207d6162..16d1b51cb 100644 --- a/src/test/java/org/springframework/data/neo4j/integration/imperative/InheritanceMappingIT.java +++ b/src/test/java/org/springframework/data/neo4j/integration/imperative/InheritanceMappingIT.java @@ -333,6 +333,44 @@ public class InheritanceMappingIT { } } + @Test // GH-2788 + void detectPropertiesAndRelationshipsOfImplementingEntities(@Autowired Neo4jTemplate template) { + String id; + try (Session session = driver.session(bookmarkCapture.createSessionConfig()); Transaction transaction = session.beginTransaction()) { + id = transaction.run("" + + "CREATE (e:`GH-2788-Entity`) " + + "CREATE (e)-[:RELATED_TO]-> (a:`GH-2788-Interface`:`GH-2788-A` {name:'A'}) " + + "CREATE (e)-[:RELATED_TO]-> (b:`GH-2788-Interface`:`GH-2788-B` {name:'B'}) " + + "CREATE (a)-[:RELATED_TO]-> (:`Gh2788ArelatedEntity`) " + + "CREATE (b)-[:RELATED_TO]-> (:`Gh2788BrelatedEntity`) " + + "RETURN elementId(e)") + .single().get(0).asString(); + transaction.commit(); + bookmarkCapture.seedWith(session.lastBookmarks()); + } + + Optional gh2788Entity = transactionTemplate.execute(tx -> + template.findById(id, Inheritance.Gh2788Entity.class)); + + assertThat(gh2788Entity).hasValueSatisfying(v -> { + List relatedTo = v.relatedTo; + assertThat(relatedTo).allSatisfy(relatedElement -> { + if (relatedElement instanceof Inheritance.Gh2788A relatedAelement) { + assertThat(relatedAelement.name).isEqualTo("A"); + assertThat(relatedAelement.relatedTo) + .hasSize(1) + .hasOnlyElementsOfType(Inheritance.Gh2788ArelatedEntity.class); + } else if (relatedElement instanceof Inheritance.Gh2788B relatedBelement) { + assertThat(relatedBelement.name).isEqualTo("B"); + assertThat(relatedBelement.relatedTo) + .hasSize(1) + .hasOnlyElementsOfType(Inheritance.Gh2788BrelatedEntity.class); + } + }); + + }); + } + @Test // GH-2262 void shouldMatchPolymorphicClassesWhenFetchedById(@Autowired DivisionRepository repository) { diff --git a/src/test/java/org/springframework/data/neo4j/integration/shared/common/Inheritance.java b/src/test/java/org/springframework/data/neo4j/integration/shared/common/Inheritance.java index 8332dd16e..e8f98c60c 100644 --- a/src/test/java/org/springframework/data/neo4j/integration/shared/common/Inheritance.java +++ b/src/test/java/org/springframework/data/neo4j/integration/shared/common/Inheritance.java @@ -957,5 +957,84 @@ public class Inheritance { } } + /** + * Entity that has an interface-based relationship. + * For testing that the properties and relationships of the implementing classes will also get fetched. + */ + @Node("GH-2788-Entity") + public static class Gh2788Entity { + @Id @GeneratedValue public String id; + public List relatedTo; + } + + /** + * Interface for relationship + */ + @Node("GH-2788-Interface") + public interface Gh2788Interface { + String getName(); + } + + /** + * First implementation + */ + @Node("GH-2788-A") + public static class Gh2788A implements Gh2788Interface { + @Id @GeneratedValue String id; + + public final String name; + public final String aValue; + public final List relatedTo; + + public Gh2788A(String name, String aValue, List relatedTo) { + this.name = name; + this.aValue = aValue; + this.relatedTo = relatedTo; + } + + @Override + public String getName() { + return name; + } + } + + /** + * Related entity for first implementation + */ + @Node + public static class Gh2788ArelatedEntity { + @Id @GeneratedValue String id; + } + + /** + * Second implementation + */ + @Node("GH-2788-B") + public static class Gh2788B implements Gh2788Interface { + @Id @GeneratedValue String id; + + public final String name; + public final String bValue; + public final List relatedTo; + + public Gh2788B(String name, String bValue, List relatedTo) { + this.name = name; + this.bValue = bValue; + this.relatedTo = relatedTo; + } + + @Override + public String getName() { + return name; + } + } + + /** + * Related entity for second implementation + */ + @Node + public static class Gh2788BrelatedEntity { + @Id @GeneratedValue String id; + } }