Skip to content

Commit 8c22618

Browse files
committed
GH-2788 - Fix implementation class detection for Node annotated interfaces.
Closes #2788
1 parent cb867a2 commit 8c22618

File tree

3 files changed

+134
-1
lines changed

3 files changed

+134
-1
lines changed

src/main/java/org/springframework/data/neo4j/core/mapping/Neo4jMappingContext.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,8 @@ protected <T> Neo4jPersistentEntity<?> createPersistentEntity(TypeInformation<T>
289289
}
290290

291291
// determine super class to create the node hierarchy
292-
Class<? super T> superclass = typeInformation.getType().getSuperclass();
292+
Class<T> type = typeInformation.getType();
293+
Class<? super T> superclass = type.getSuperclass();
293294

294295
if (isValidParentNode(superclass)) {
295296
synchronized (this) {
@@ -303,6 +304,17 @@ protected <T> Neo4jPersistentEntity<?> createPersistentEntity(TypeInformation<T>
303304
}
304305
}
305306

307+
for (Class<?> typeInterface : type.getInterfaces()) {
308+
if (isValidEntityInterface(typeInterface)) {
309+
super.setStrict(false);
310+
Neo4jPersistentEntity<?> parentNodeDescription = getPersistentEntity(typeInterface);
311+
if (parentNodeDescription != null) {
312+
parentNodeDescription.addChildNodeDescription(newEntity);
313+
}
314+
this.setStrict(strict);
315+
}
316+
}
317+
306318
return newEntity;
307319
}
308320

@@ -316,6 +328,10 @@ private static boolean isValidParentNode(@Nullable Class<?> parentClass) {
316328
parentClass.isAnnotationPresent(Node.class);
317329
}
318330

331+
private static boolean isValidEntityInterface(Class<?> typeInterface) {
332+
return typeInterface.isAnnotationPresent(Node.class);
333+
}
334+
319335
/*
320336
* (non-Javadoc)
321337
* @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)

src/test/java/org/springframework/data/neo4j/integration/imperative/InheritanceMappingIT.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,44 @@ void mixedInterfaces(@Autowired Neo4jTemplate template) {
333333
}
334334
}
335335

336+
@Test // GH-2788
337+
void detectPropertiesAndRelationshipsOfImplementingEntities(@Autowired Neo4jTemplate template) {
338+
String id;
339+
try (Session session = driver.session(bookmarkCapture.createSessionConfig()); Transaction transaction = session.beginTransaction()) {
340+
id = transaction.run("" +
341+
"CREATE (e:`GH-2788-Entity`) " +
342+
"CREATE (e)-[:RELATED_TO]-> (a:`GH-2788-Interface`:`GH-2788-A` {name:'A'}) " +
343+
"CREATE (e)-[:RELATED_TO]-> (b:`GH-2788-Interface`:`GH-2788-B` {name:'B'}) " +
344+
"CREATE (a)-[:RELATED_TO]-> (:`Gh2788ArelatedEntity`) " +
345+
"CREATE (b)-[:RELATED_TO]-> (:`Gh2788BrelatedEntity`) " +
346+
"RETURN elementId(e)")
347+
.single().get(0).asString();
348+
transaction.commit();
349+
bookmarkCapture.seedWith(session.lastBookmarks());
350+
}
351+
352+
Optional<Inheritance.Gh2788Entity> gh2788Entity = transactionTemplate.execute(tx ->
353+
template.findById(id, Inheritance.Gh2788Entity.class));
354+
355+
assertThat(gh2788Entity).hasValueSatisfying(v -> {
356+
List<Inheritance.Gh2788Interface> relatedTo = v.relatedTo;
357+
assertThat(relatedTo).allSatisfy(relatedElement -> {
358+
if (relatedElement instanceof Inheritance.Gh2788A relatedAelement) {
359+
assertThat(relatedAelement.name).isEqualTo("A");
360+
assertThat(relatedAelement.relatedTo)
361+
.hasSize(1)
362+
.hasOnlyElementsOfType(Inheritance.Gh2788ArelatedEntity.class);
363+
} else if (relatedElement instanceof Inheritance.Gh2788B relatedBelement) {
364+
assertThat(relatedBelement.name).isEqualTo("B");
365+
assertThat(relatedBelement.relatedTo)
366+
.hasSize(1)
367+
.hasOnlyElementsOfType(Inheritance.Gh2788BrelatedEntity.class);
368+
}
369+
});
370+
371+
});
372+
}
373+
336374
@Test // GH-2262
337375
void shouldMatchPolymorphicClassesWhenFetchedById(@Autowired DivisionRepository repository) {
338376

src/test/java/org/springframework/data/neo4j/integration/shared/common/Inheritance.java

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -983,5 +983,84 @@ public ChildClassWithRelationship(Long id, Continent continent) {
983983
}
984984
}
985985

986+
/**
987+
* Entity that has an interface-based relationship.
988+
* For testing that the properties and relationships of the implementing classes will also get fetched.
989+
*/
990+
@Node("GH-2788-Entity")
991+
public static class Gh2788Entity {
992+
@Id @GeneratedValue public String id;
993+
public List<Gh2788Interface> relatedTo;
994+
}
995+
996+
/**
997+
* Interface for relationship
998+
*/
999+
@Node("GH-2788-Interface")
1000+
public interface Gh2788Interface {
1001+
String getName();
1002+
}
1003+
1004+
/**
1005+
* First implementation
1006+
*/
1007+
@Node("GH-2788-A")
1008+
public static class Gh2788A implements Gh2788Interface {
1009+
@Id @GeneratedValue String id;
1010+
1011+
public final String name;
1012+
public final String aValue;
1013+
public final List<Gh2788ArelatedEntity> relatedTo;
1014+
1015+
public Gh2788A(String name, String aValue, List<Gh2788ArelatedEntity> relatedTo) {
1016+
this.name = name;
1017+
this.aValue = aValue;
1018+
this.relatedTo = relatedTo;
1019+
}
1020+
1021+
@Override
1022+
public String getName() {
1023+
return name;
1024+
}
1025+
}
1026+
1027+
/**
1028+
* Related entity for first implementation
1029+
*/
1030+
@Node
1031+
public static class Gh2788ArelatedEntity {
1032+
@Id @GeneratedValue String id;
1033+
}
1034+
1035+
/**
1036+
* Second implementation
1037+
*/
1038+
@Node("GH-2788-B")
1039+
public static class Gh2788B implements Gh2788Interface {
1040+
@Id @GeneratedValue String id;
1041+
1042+
public final String name;
1043+
public final String bValue;
1044+
public final List<Gh2788BrelatedEntity> relatedTo;
1045+
1046+
public Gh2788B(String name, String bValue, List<Gh2788BrelatedEntity> relatedTo) {
1047+
this.name = name;
1048+
this.bValue = bValue;
1049+
this.relatedTo = relatedTo;
1050+
}
1051+
1052+
@Override
1053+
public String getName() {
1054+
return name;
1055+
}
1056+
}
1057+
1058+
/**
1059+
* Related entity for second implementation
1060+
*/
1061+
@Node
1062+
public static class Gh2788BrelatedEntity {
1063+
@Id @GeneratedValue String id;
1064+
}
9861065

9871066
}

0 commit comments

Comments
 (0)