GH-2788 - Fix implementation class detection for Node annotated interfaces.

Closes #2788
This commit is contained in:
Gerrit Meier
2023-09-14 14:28:09 +02:00
parent 9ba13deefb
commit 12d43d1fe8
3 changed files with 134 additions and 1 deletions

View File

@@ -289,7 +289,8 @@ public final class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersi
}
// determine super class to create the node hierarchy
Class<? super T> superclass = typeInformation.getType().getSuperclass();
Class<T> type = typeInformation.getType();
Class<? super T> superclass = type.getSuperclass();
if (isValidParentNode(superclass)) {
synchronized (this) {
@@ -303,6 +304,17 @@ public final class Neo4jMappingContext extends AbstractMappingContext<Neo4jPersi
}
}
for (Class<?> 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<Neo4jPersi
parentClass.isAnnotationPresent(Node.class);
}
private static boolean isValidEntityInterface(Class<?> 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)

View File

@@ -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<Inheritance.Gh2788Entity> gh2788Entity = transactionTemplate.execute(tx ->
template.findById(id, Inheritance.Gh2788Entity.class));
assertThat(gh2788Entity).hasValueSatisfying(v -> {
List<Inheritance.Gh2788Interface> 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) {

View File

@@ -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<Gh2788Interface> 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<Gh2788ArelatedEntity> relatedTo;
public Gh2788A(String name, String aValue, List<Gh2788ArelatedEntity> 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<Gh2788BrelatedEntity> relatedTo;
public Gh2788B(String name, String bValue, List<Gh2788BrelatedEntity> 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;
}
}