Skip to content

Commit fdc914a

Browse files
committed
GH-2398 - Fix flaky RelationshipProperties field determination.
Closes #2398
1 parent b15aba6 commit fdc914a

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

@@ -24,7 +25,6 @@
2425
import org.springframework.data.mapping.Association;
2526
import org.springframework.data.mapping.MappingException;
2627
import org.springframework.data.mapping.PersistentEntity;
27-
import org.springframework.data.mapping.PersistentProperty;
2828
import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty;
2929
import org.springframework.data.mapping.model.Property;
3030
import org.springframework.data.mapping.model.SimpleTypeHolder;
@@ -36,6 +36,7 @@
3636
import org.springframework.data.neo4j.core.schema.TargetNode;
3737
import org.springframework.data.util.ClassTypeInformation;
3838
import org.springframework.data.util.Lazy;
39+
import org.springframework.data.util.ReflectionUtils;
3940
import org.springframework.data.util.TypeInformation;
4041
import org.springframework.lang.NonNull;
4142
import org.springframework.lang.Nullable;
@@ -115,9 +116,9 @@ protected Association<Neo4jPersistentProperty> createAssociation() {
115116
Neo4jPersistentEntity<?> relationshipPropertiesClass = null;
116117

117118
if (this.hasActualTypeAnnotation(RelationshipProperties.class)) {
118-
TypeInformation<?> type = getRelationshipPropertiesTargetType(getActualType());
119-
obverseOwner = this.mappingContext.getPersistentEntity(type);
120-
relationshipPropertiesClass = this.mappingContext.getPersistentEntity(getActualType());
119+
Class<?> type = getRelationshipPropertiesTargetType(getActualType());
120+
obverseOwner = this.mappingContext.addPersistentEntity(ClassTypeInformation.from(type)).get();
121+
relationshipPropertiesClass = this.mappingContext.addPersistentEntity(ClassTypeInformation.from(getActualType())).get();
121122
} else {
122123
Class<?> associationTargetType = this.getAssociationTargetType();
123124
obverseOwner = this.mappingContext.addPersistentEntity(ClassTypeInformation.from(associationTargetType)).orElse(null);
@@ -134,13 +135,13 @@ protected Association<Neo4jPersistentProperty> createAssociation() {
134135
mapValueType.getType().isAnnotationPresent(RelationshipProperties.class);
135136

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

142143
} else if (relationshipPropertiesScalar) {
143-
relationshipPropertiesClass = this.mappingContext.getPersistentEntity(mapValueType.getType());
144+
relationshipPropertiesClass = this.mappingContext.addPersistentEntity(mapValueType.getComponentType()).get();
144145
}
145146
}
146147
}
@@ -177,12 +178,16 @@ protected Association<Neo4jPersistentProperty> createAssociation() {
177178
}
178179

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

188193
@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)