Skip to content

Commit 71a5129

Browse files
committed
GH-2398 - Fix flaky RelationshipProperties field determination.
Closes #2398
1 parent 32e7ada commit 71a5129

File tree

3 files changed

+87
-30
lines changed

3 files changed

+87
-30
lines changed

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

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -255,22 +255,14 @@ private void verifyDynamicAssociations() {
255255

256256
private void verifyAssociationsWithProperties() {
257257

258-
this.doWithAssociations((Association<Neo4jPersistentProperty> association) -> {
259-
260-
if (association instanceof RelationshipDescription) {
261-
RelationshipDescription relationship = (RelationshipDescription) association;
262-
if (relationship.hasRelationshipProperties()) {
263-
NodeDescription<?> relationshipPropertiesEntity = relationship.getRelationshipPropertiesEntity();
264-
Supplier<String> messageSupplier = () -> String.format(
265-
"The target class `%s` for the properties of the relationship `%s` "
258+
if (this.isRelationshipPropertiesEntity()) {
259+
Supplier<String> messageSupplier = () -> String.format(
260+
"The class `%s` for the properties of a relationship "
266261
+ "is missing a property for the generated, internal ID (`@Id @GeneratedValue Long id`) "
267262
+ "which is needed for safely updating properties.",
268-
relationshipPropertiesEntity.getUnderlyingClass().getName(),
269-
relationship.getType());
270-
Assert.state(relationshipPropertiesEntity.getIdDescription() != null && relationshipPropertiesEntity.getIdDescription().isInternallyGeneratedId(), messageSupplier);
271-
}
272-
}
273-
});
263+
this.getUnderlyingClass().getName());
264+
Assert.state(this.getIdDescription() != null && this.getIdDescription().isInternallyGeneratedId(), messageSupplier);
265+
}
274266
}
275267

276268
private void verifyDynamicLabels() {

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

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.data.neo4j.core.mapping;
1717

18+
import java.lang.reflect.Field;
1819
import java.util.Optional;
1920
import java.util.function.Function;
2021

@@ -23,7 +24,6 @@
2324
import org.springframework.data.mapping.Association;
2425
import org.springframework.data.mapping.MappingException;
2526
import org.springframework.data.mapping.PersistentEntity;
26-
import org.springframework.data.mapping.PersistentProperty;
2727
import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty;
2828
import org.springframework.data.mapping.model.Property;
2929
import org.springframework.data.mapping.model.SimpleTypeHolder;
@@ -35,6 +35,7 @@
3535
import org.springframework.data.neo4j.core.schema.TargetNode;
3636
import org.springframework.data.util.ClassTypeInformation;
3737
import org.springframework.data.util.Lazy;
38+
import org.springframework.data.util.ReflectionUtils;
3839
import org.springframework.data.util.TypeInformation;
3940
import org.springframework.lang.NonNull;
4041
import org.springframework.lang.Nullable;
@@ -114,9 +115,9 @@ protected Association<Neo4jPersistentProperty> createAssociation() {
114115
Neo4jPersistentEntity<?> relationshipPropertiesClass = null;
115116

116117
if (this.hasActualTypeAnnotation(RelationshipProperties.class)) {
117-
TypeInformation<?> type = getRelationshipPropertiesTargetType(getActualType());
118-
obverseOwner = this.mappingContext.getPersistentEntity(type);
119-
relationshipPropertiesClass = this.mappingContext.getPersistentEntity(getActualType());
118+
Class<?> type = getRelationshipPropertiesTargetType(getActualType());
119+
obverseOwner = this.mappingContext.addPersistentEntity(ClassTypeInformation.from(type)).get();
120+
relationshipPropertiesClass = this.mappingContext.addPersistentEntity(ClassTypeInformation.from(getActualType())).get();
120121
} else {
121122
Class<?> associationTargetType = this.getAssociationTargetType();
122123
obverseOwner = this.mappingContext.addPersistentEntity(ClassTypeInformation.from(associationTargetType)).get();
@@ -132,13 +133,13 @@ protected Association<Neo4jPersistentProperty> createAssociation() {
132133
mapValueType.getType().isAnnotationPresent(RelationshipProperties.class);
133134

134135
if (relationshipPropertiesCollection) {
135-
TypeInformation<?> type = getRelationshipPropertiesTargetType(mapValueType.getActualType().getType());
136-
obverseOwner = this.mappingContext.getPersistentEntity(type);
136+
Class<?> type = getRelationshipPropertiesTargetType(mapValueType.getActualType().getType());
137+
obverseOwner = this.mappingContext.addPersistentEntity(ClassTypeInformation.from(type)).get();
137138
relationshipPropertiesClass = this.mappingContext
138-
.getPersistentEntity(mapValueType.getComponentType().getType());
139+
.addPersistentEntity(mapValueType.getComponentType()).get();
139140

140141
} else if (relationshipPropertiesScalar) {
141-
relationshipPropertiesClass = this.mappingContext.getPersistentEntity(mapValueType.getType());
142+
relationshipPropertiesClass = this.mappingContext.addPersistentEntity(mapValueType.getComponentType()).get();
142143
}
143144
}
144145
}
@@ -175,12 +176,16 @@ protected Association<Neo4jPersistentProperty> createAssociation() {
175176
}
176177

177178
@NonNull
178-
private TypeInformation<?> getRelationshipPropertiesTargetType(Class<?> relationshipPropertiesType) {
179-
return this.mappingContext.addPersistentEntity(ClassTypeInformation.from(relationshipPropertiesType))
180-
.map(entity -> entity.getPersistentProperty(TargetNode.class))
181-
.map(PersistentProperty::getTypeInformation)
182-
.orElseThrow(
183-
() -> new MappingException("Missing @TargetNode declaration in " + relationshipPropertiesType));
179+
private Class<?> getRelationshipPropertiesTargetType(Class<?> relationshipPropertiesType) {
180+
181+
Field targetNodeField = ReflectionUtils.findField(relationshipPropertiesType,
182+
field -> field.isAnnotationPresent(TargetNode.class));
183+
184+
if (targetNodeField == null) {
185+
throw new MappingException("Missing @TargetNode declaration in " + relationshipPropertiesType);
186+
}
187+
ClassTypeInformation<?> relationshipPropertiesTypeInformation = ClassTypeInformation.from(relationshipPropertiesType);
188+
return relationshipPropertiesTypeInformation.getProperty(targetNodeField.getName()).getType();
184189
}
185190

186191
@Override

src/test/java/org/springframework/data/neo4j/core/mapping/Neo4jMappingContextTest.java

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ void startupWithoutInternallyGeneratedIDShouldFail() {
8787
Arrays.asList(IrrelevantSourceContainer.class, InvalidRelationshipPropertyContainer.class,
8888
IrrelevantTargetContainer.class)));
8989
schema.initialize();
90-
}).withMessage("The target class `org.springframework.data.neo4j.core.mapping.Neo4jMappingContextTest$InvalidRelationshipPropertyContainer` for the properties of the relationship `RELATIONSHIP_PROPERTY_CONTAINER` is missing a property for the generated, internal ID (`@Id @GeneratedValue Long id`) which is needed for safely updating properties.");
90+
}).withMessage("The class `org.springframework.data.neo4j.core.mapping.Neo4jMappingContextTest$InvalidRelationshipPropertyContainer` for the properties of a relationship is missing a property for the generated, internal ID (`@Id @GeneratedValue Long id`) which is needed for safely updating properties.");
9191
}
9292

9393
@Test // GH-2214
@@ -99,7 +99,7 @@ void startupWithWrongKindOfGeneratedIDShouldFail() {
9999
Arrays.asList(IrrelevantSourceContainer3.class, InvalidRelationshipPropertyContainer2.class,
100100
IrrelevantTargetContainer.class)));
101101
schema.initialize();
102-
}).withMessage("The target class `org.springframework.data.neo4j.core.mapping.Neo4jMappingContextTest$InvalidRelationshipPropertyContainer2` for the properties of the relationship `RELATIONSHIP_PROPERTY_CONTAINER` is missing a property for the generated, internal ID (`@Id @GeneratedValue Long id`) which is needed for safely updating properties.");
102+
}).withMessage("The class `org.springframework.data.neo4j.core.mapping.Neo4jMappingContextTest$InvalidRelationshipPropertyContainer2` for the properties of a relationship is missing a property for the generated, internal ID (`@Id @GeneratedValue Long id`) which is needed for safely updating properties.");
103103
}
104104

105105
@Test // GH-2118
@@ -523,6 +523,14 @@ void shouldNotCreateEntityForConvertedSimpleTypes() {
523523
assertThat(schema.hasPersistentEntityFor(ThingWithCustomTypes.CustomType.class)).isFalse();
524524
}
525525

526+
@Test
527+
void dontFailOnCyclicRelationshipProperties() { // GH-2398
528+
Neo4jMappingContext context = new Neo4jMappingContext();
529+
Neo4jPersistentEntity<?> persistentEntity = context.getPersistentEntity(ARelationship.class);
530+
assertThat(persistentEntity.isRelationshipPropertiesEntity()).isTrue();
531+
assertThat(persistentEntity.getGraphProperties()).hasSize(3);
532+
}
533+
526534
static class DummyIdGenerator implements IdGenerator<Void> {
527535

528536
@Override
@@ -865,6 +873,58 @@ static class SomeInterfaceImpl3b implements SomeInterface3 {
865873
SomeInterface3 related;
866874
}
867875

876+
@Node
877+
public static class SomeBaseEntity {
878+
@Id
879+
@GeneratedValue
880+
public Long internalId;
881+
882+
public String id;
883+
}
884+
885+
@Node
886+
public static class ConcreteEntity extends SomeBaseEntity {
887+
888+
@Relationship("A")
889+
public Set<ARelationship> as;
890+
@Relationship("B")
891+
public Set<BRelationship> bs;
892+
@Relationship("C")
893+
public Set<CRelationship> cs;
894+
895+
896+
}
897+
898+
public static class RelationshipPropertiesBaseClass<T extends SomeBaseEntity> {
899+
@Id
900+
@GeneratedValue
901+
public Long internalId;
902+
903+
public String id;
904+
905+
@TargetNode
906+
public T target;
907+
}
908+
909+
public static abstract class RelationshipPropertiesAbstractClass extends RelationshipPropertiesBaseClass<ConcreteEntity> {
910+
911+
}
912+
913+
@RelationshipProperties
914+
public static class ARelationship extends RelationshipPropertiesAbstractClass {
915+
916+
}
917+
918+
@RelationshipProperties
919+
public static class BRelationship extends RelationshipPropertiesAbstractClass {
920+
921+
}
922+
923+
@RelationshipProperties
924+
public static class CRelationship extends RelationshipPropertiesAbstractClass {
925+
926+
}
927+
868928
static class MissingIdToMapConverter implements Neo4jPersistentPropertyToMapConverter<String, MissingId> {
869929

870930
@Override public Map<String, Value> decompose(MissingId property, Neo4jConversionService conversionService) {

0 commit comments

Comments
 (0)