Skip to content

Commit 0d7b52c

Browse files
meistermeiermichael-simons
authored andcommitted
DATAGRAPH-1385 - Support RelationshipProperties in dynamic relationships.
1 parent 828cad1 commit 0d7b52c

15 files changed

+585
-40
lines changed

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

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -266,8 +266,6 @@ public Statement createRelationshipWithPropertiesCreationQuery(Neo4jPersistentEn
266266

267267
Assert.isTrue(relationship.hasRelationshipProperties(),
268268
"Properties required to create a relationship with properties");
269-
Assert.isTrue(!relationship.isDynamic(),
270-
"Creation of relationships with properties is only supported for non-dynamic relationships");
271269

272270
Node startNode = anyNode(START_NODE_NAME);
273271
Node endNode = anyNode(END_NODE_NAME);
@@ -428,15 +426,22 @@ private void generateListFor(RelationshipDescription relationshipDescription, Sy
428426
processedRelationships.add(relationshipDescription);
429427

430428
if (relationshipDescription.isDynamic()) {
431-
Relationship relationship = relationshipDescription.isOutgoing() ? startNode.relationshipTo(endNode)
429+
Relationship relationship = relationshipDescription.isOutgoing()
430+
? startNode.relationshipTo(endNode)
432431
: startNode.relationshipFrom(endNode);
433432
relationship = relationship.named(relationshipTargetName);
434433

434+
MapProjection mapProjection = projectAllPropertiesAndRelationships(endNodeDescription, relationshipFieldName,
435+
new ArrayList<>(processedRelationships));
436+
437+
if (relationshipDescription.hasRelationshipProperties()) {
438+
relationship = relationship.named(RelationshipDescription.NAME_OF_RELATIONSHIP);
439+
mapProjection = mapProjection.and(relationship);
440+
}
441+
435442
addMapProjection(relationshipTargetName,
436-
listBasedOn(relationship).returning(projectAllPropertiesAndRelationships(endNodeDescription,
437-
relationshipFieldName, new ArrayList<>(processedRelationships)).and(
438-
RelationshipDescription.NAME_OF_RELATIONSHIP_TYPE,
439-
Functions.type(relationship))),
443+
listBasedOn(relationship).returning(mapProjection
444+
.and(RelationshipDescription.NAME_OF_RELATIONSHIP_TYPE, Functions.type(relationship))),
440445
mapProjectionLists);
441446

442447
} else {

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,7 @@ private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty p
405405
(Neo4jPersistentEntity) relationshipDescription.getRelationshipPropertiesEntity(),
406406
knownObjects, mappedObject);
407407
relationshipsAndProperties.add(relationshipProperties);
408+
mappedObjectHandler.accept(possibleRelationship.type(), relationshipProperties);
408409
} else {
409410
mappedObjectHandler.accept(possibleRelationship.type(), mappedObject);
410411
}
@@ -429,6 +430,7 @@ private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty p
429430
(Neo4jPersistentEntity) relationshipDescription.getRelationshipPropertiesEntity(),
430431
knownObjects, valueEntry);
431432
relationshipsAndProperties.add(relationshipProperties);
433+
mappedObjectHandler.accept(relatedEntity.get(RelationshipDescription.NAME_OF_RELATIONSHIP_TYPE).asString(), relationshipProperties);
432434
} else {
433435
mappedObjectHandler.accept(relatedEntity.get(RelationshipDescription.NAME_OF_RELATIONSHIP_TYPE).asString(),
434436
valueEntry);

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

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.springframework.data.neo4j.core.schema.TargetNode;
3333
import org.springframework.data.util.Lazy;
3434
import org.springframework.data.util.TypeInformation;
35+
import org.springframework.lang.NonNull;
3536
import org.springframework.lang.Nullable;
3637
import org.springframework.util.Assert;
3738

@@ -88,10 +89,37 @@ protected Association<Neo4jPersistentProperty> createAssociation() {
8889

8990
Neo4jPersistentEntity<?> obverseOwner;
9091

91-
// if the target is a relationship property always take the key type from the map instead of the value type.
92+
boolean dynamicAssociation = this.isDynamicAssociation();
93+
94+
Neo4jPersistentEntity<?> relationshipPropertiesClass = null;
95+
9296
if (this.hasActualTypeAnnotation(RelationshipProperties.class)) {
93-
Class<?> type = this.mappingContext.getPersistentEntity(getActualType()).getPersistentProperty(TargetNode.class).getType();
97+
Class<?> type = getRelationshipPropertiesTargetType(getActualType());
9498
obverseOwner = this.mappingContext.getPersistentEntity(type);
99+
relationshipPropertiesClass = this.mappingContext.getPersistentEntity(getActualType());
100+
} else if (dynamicAssociation) {
101+
102+
TypeInformation<?> mapValueType = this.getTypeInformation().getMapValueType();
103+
104+
boolean relationshipPropertiesCollection =
105+
this.mappingContext.getPersistentEntity(mapValueType.getActualType().getType())
106+
.isRelationshipPropertiesEntity();
107+
108+
boolean relationshipPropertiesScalar =
109+
mapValueType.getType().isAnnotationPresent(RelationshipProperties.class);
110+
111+
if (relationshipPropertiesCollection) {
112+
Class<?> type = getRelationshipPropertiesTargetType(mapValueType.getActualType().getType());
113+
obverseOwner = this.mappingContext.getPersistentEntity(type);
114+
relationshipPropertiesClass = this.mappingContext
115+
.getPersistentEntity(mapValueType.getComponentType().getType());
116+
117+
} else if (relationshipPropertiesScalar) {
118+
obverseOwner = this.mappingContext.getPersistentEntity(this.getAssociationTargetType());
119+
relationshipPropertiesClass = this.mappingContext.getPersistentEntity(mapValueType.getType());
120+
} else {
121+
obverseOwner = this.mappingContext.getPersistentEntity(this.getAssociationTargetType());
122+
}
95123
} else {
96124
obverseOwner = this.mappingContext.getPersistentEntity(this.getAssociationTargetType());
97125
}
@@ -110,15 +138,6 @@ protected Association<Neo4jPersistentProperty> createAssociation() {
110138
direction = outgoingRelationship.direction();
111139
}
112140

113-
boolean dynamicAssociation = this.isDynamicAssociation();
114-
115-
// Because a dynamic association is also represented as a Map, this ensures that the
116-
// relationship properties class will only have a value if it's not a dynamic association.
117-
Neo4jPersistentEntity<?> relationshipPropertiesClass =
118-
this.hasActualTypeAnnotation(RelationshipProperties.class)
119-
? this.mappingContext.getPersistentEntity(getActualType())
120-
: null;
121-
122141
// Try to determine if there is a relationship definition that expresses logically the same relationship
123142
// on the other end.
124143
Optional<RelationshipDescription> obverseRelationshipDescription = obverseOwner.getRelationships().stream()
@@ -135,6 +154,12 @@ protected Association<Neo4jPersistentProperty> createAssociation() {
135154
return relationshipDescription;
136155
}
137156

157+
@NonNull
158+
private Class<?> getRelationshipPropertiesTargetType(Class<?> relationshipPropertiesType) {
159+
return this.mappingContext.getPersistentEntity(relationshipPropertiesType)
160+
.getPersistentProperty(TargetNode.class).getType();
161+
}
162+
138163
@Override
139164
public Class<?> getAssociationTargetType() {
140165

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,7 @@ public interface Neo4jConversionService {
7070
*/
7171
@Nullable
7272
Object readValue(
73-
@Nullable Value source, TypeInformation<?> targetType, @Nullable
74-
Function<Value, Object> conversionOverride
73+
@Nullable Value source, TypeInformation<?> targetType, @Nullable Function<Value, Object> conversionOverride
7574
);
7675

7776
/**
@@ -82,7 +81,6 @@ Object readValue(
8281
* @return A driver compatible value object.
8382
*/
8483
Value writeValue(
85-
@Nullable Object value, TypeInformation<?> sourceType,
86-
@Nullable Function<Object, Value> conversionOverride
84+
@Nullable Object value, TypeInformation<?> sourceType, @Nullable Function<Object, Value> conversionOverride
8785
);
8886
}

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

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.lang.reflect.Modifier;
1919
import java.util.HashMap;
20+
import java.util.List;
2021
import java.util.Locale;
2122
import java.util.Map;
2223
import java.util.Optional;
@@ -269,11 +270,21 @@ public CreateRelationshipStatementHolder createStatement(Neo4jPersistentEntity<?
269270
Long relatedInternalId, Object relatedValue) {
270271

271272
if (relationshipContext.hasRelationshipWithProperties()) {
272-
return createStatementForRelationShipWithProperties(neo4jPersistentEntity,
273-
relationshipContext, relatedInternalId, (MappingSupport.RelationshipPropertiesWithEntityHolder) relatedValue);
273+
MappingSupport.RelationshipPropertiesWithEntityHolder relatedValueEntityHolder =
274+
(MappingSupport.RelationshipPropertiesWithEntityHolder) (
275+
// either this is a scalar entity holder value
276+
// or a dynamic relationship with
277+
// either a list of entity holders
278+
// or a scalar value
279+
relatedValue instanceof MappingSupport.RelationshipPropertiesWithEntityHolder
280+
? relatedValue
281+
: ((Map.Entry<?, ?>) relatedValue).getValue() instanceof List
282+
? ((List<?>) ((Map.Entry<?, ?>) relatedValue).getValue()).get(0)
283+
: ((Map.Entry<?, ?>) relatedValue).getValue());
284+
285+
return createStatementForRelationShipWithProperties(neo4jPersistentEntity, relationshipContext, relatedInternalId, relatedValueEntityHolder);
274286
} else {
275-
return createStatementForRelationshipWithoutProperties(neo4jPersistentEntity,
276-
relationshipContext, relatedInternalId, relatedValue);
287+
return createStatementForRelationshipWithoutProperties(neo4jPersistentEntity, relationshipContext, relatedInternalId, relatedValue);
277288
}
278289
}
279290

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

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.util.ArrayList;
1919
import java.util.Collection;
20+
import java.util.HashMap;
2021
import java.util.List;
2122
import java.util.Map;
2223

@@ -81,13 +82,16 @@ boolean hasRelationshipWithProperties() {
8182
public Object identifyAndExtractRelationshipTargetNode(Object relatedValue) {
8283
Object valueToBeSaved = relatedValue;
8384
if (relatedValue instanceof Map.Entry) {
84-
Map.Entry relatedValueMapEntry = (Map.Entry) relatedValue;
85-
86-
if (this.getInverse().isDynamicAssociation()) {
85+
Map.Entry<?, ?> relatedValueMapEntry = (Map.Entry<?, ?>) relatedValue;
86+
if (this.hasRelationshipWithProperties()) {
87+
Object mapValue = ((Map.Entry<?, ?>) relatedValue).getValue();
88+
// it can be either a scalar entity holder or a list of it
89+
mapValue = mapValue instanceof List ? ((List<?>) mapValue).get(0) : mapValue;
90+
valueToBeSaved = ((MappingSupport.RelationshipPropertiesWithEntityHolder) mapValue).getRelatedEntity();
91+
} else if (this.getInverse().isDynamicAssociation()) {
8792
valueToBeSaved = relatedValueMapEntry.getValue();
8893
}
89-
}
90-
if (this.hasRelationshipWithProperties()) {
94+
} else if (this.hasRelationshipWithProperties()) {
9195
// here comes the entity
9296
valueToBeSaved = ((MappingSupport.RelationshipPropertiesWithEntityHolder) relatedValue).getRelatedEntity();
9397
}
@@ -114,16 +118,44 @@ public static NestedRelationshipContext of(Association<Neo4jPersistentProperty>
114118
if (relationship.hasRelationshipProperties() && value != null) {
115119
Neo4jPersistentEntity<?> relationshipPropertiesEntity = (Neo4jPersistentEntity<?>) relationship.getRelationshipPropertiesEntity();
116120

117-
List<MappingSupport.RelationshipPropertiesWithEntityHolder> relationshipProperties = new ArrayList<>();
118-
119-
for (Object relationshipProperty : ((Collection<Object>) value)) {
120-
121-
MappingSupport.RelationshipPropertiesWithEntityHolder oneOfThem =
122-
new MappingSupport.RelationshipPropertiesWithEntityHolder(relationshipProperty,
123-
getTargetNode(relationshipPropertiesEntity, relationshipProperty));
124-
relationshipProperties.add(oneOfThem);
121+
// If this is dynamic relationship (Map<Object, Object>), extract the keys as relationship names
122+
// and the map values as values.
123+
// The values themself can be either a scalar or a List.
124+
if (relationship.isDynamic()) {
125+
Map<Object, List<MappingSupport.RelationshipPropertiesWithEntityHolder>> relationshipProperties = new HashMap<>();
126+
for (Map.Entry<Object, Object> mapEntry : ((Map<Object, Object>) value).entrySet()) {
127+
List<MappingSupport.RelationshipPropertiesWithEntityHolder> relationshipValues = new ArrayList<>();
128+
// register the relationship type as key
129+
relationshipProperties.put(mapEntry.getKey(), relationshipValues);
130+
Object mapEntryValue = mapEntry.getValue();
131+
132+
if (mapEntryValue instanceof List) {
133+
for (Object relationshipProperty : ((List<Object>) mapEntryValue)) {
134+
MappingSupport.RelationshipPropertiesWithEntityHolder oneOfThem =
135+
new MappingSupport.RelationshipPropertiesWithEntityHolder(relationshipProperty,
136+
getTargetNode(relationshipPropertiesEntity, relationshipProperty));
137+
relationshipValues.add(oneOfThem);
138+
}
139+
} else { // scalar
140+
MappingSupport.RelationshipPropertiesWithEntityHolder oneOfThem =
141+
new MappingSupport.RelationshipPropertiesWithEntityHolder(mapEntryValue,
142+
getTargetNode(relationshipPropertiesEntity, mapEntryValue));
143+
relationshipValues.add(oneOfThem);
144+
}
145+
146+
}
147+
value = relationshipProperties;
148+
} else {
149+
List<MappingSupport.RelationshipPropertiesWithEntityHolder> relationshipProperties = new ArrayList<>();
150+
for (Object relationshipProperty : ((Collection<Object>) value)) {
151+
152+
MappingSupport.RelationshipPropertiesWithEntityHolder oneOfThem =
153+
new MappingSupport.RelationshipPropertiesWithEntityHolder(relationshipProperty,
154+
getTargetNode(relationshipPropertiesEntity, relationshipProperty));
155+
relationshipProperties.add(oneOfThem);
156+
}
157+
value = relationshipProperties;
125158
}
126-
value = relationshipProperties;
127159
}
128160

129161
return new NestedRelationshipContext(inverse, value, relationship, associationTargetType, inverseValueIsEmpty);

0 commit comments

Comments
 (0)