@@ -144,7 +144,7 @@ private <R> MapAccessor determineQueryRoot(MapAccessor mapAccessor, @Nullable Ne
144
144
Node node = value .asNode ();
145
145
if (primaryLabels .stream ().anyMatch (node ::hasLabel )) { // it has a matching label
146
146
// We haven't seen this node yet, so we take it
147
- if (knownObjects .getObject (node .id ()) == null ) {
147
+ if (knownObjects .getObject ("N" + node .id ()) == null ) {
148
148
matchingNodes .add (node );
149
149
} else {
150
150
seenMatchingNodes .add (node );
@@ -263,22 +263,23 @@ private static MapAccessor mergeRootNodeWithRecord(Node node, MapAccessor record
263
263
private <ET > ET map (MapAccessor queryResult , MapAccessor allValues , Neo4jPersistentEntity <ET > nodeDescription ) {
264
264
Collection <Relationship > relationshipsFromResult = extractRelationships (allValues );
265
265
Collection <Node > nodesFromResult = extractNodes (allValues );
266
- return map (queryResult , nodeDescription , null , relationshipsFromResult , nodesFromResult );
266
+ return map (queryResult , nodeDescription , null , null , relationshipsFromResult , nodesFromResult );
267
267
}
268
268
269
269
private <ET > ET map (MapAccessor queryResult , Neo4jPersistentEntity <ET > nodeDescription ,
270
- @ Nullable Object lastMappedEntity , Collection <Relationship > relationshipsFromResult , Collection <Node > nodesFromResult ) {
270
+ @ Nullable Object lastMappedEntity , @ Nullable RelationshipDescription relationshipDescription , Collection <Relationship > relationshipsFromResult , Collection <Node > nodesFromResult ) {
271
271
272
272
// if the given result does not contain an identifier to the mapped object cannot get temporarily saved
273
- Long internalId = getInternalId (queryResult );
273
+ String direction = relationshipDescription != null ? relationshipDescription .getDirection ().name () : null ;
274
+ String internalId = getInternalId (queryResult , direction );
274
275
275
276
Supplier <ET > mappedObjectSupplier = () -> {
276
277
if (knownObjects .isInCreation (internalId )) {
277
278
throw new MappingException (
278
279
String .format (
279
280
"The node with id %s has a logical cyclic mapping dependency. " +
280
281
"Its creation caused the creation of another node that has a reference to this." ,
281
- internalId )
282
+ internalId . substring ( 1 ) )
282
283
);
283
284
}
284
285
knownObjects .setInCreation (internalId );
@@ -326,7 +327,7 @@ private <ET> ET map(MapAccessor queryResult, Neo4jPersistentEntity<ET> nodeDescr
326
327
}
327
328
328
329
329
- private <ET > void populateProperties (MapAccessor queryResult , Neo4jPersistentEntity <ET > nodeDescription , Long internalId ,
330
+ private <ET > void populateProperties (MapAccessor queryResult , Neo4jPersistentEntity <ET > nodeDescription , String internalId ,
330
331
ET mappedObject , @ Nullable Object lastMappedEntity ,
331
332
Collection <Relationship > relationshipsFromResult , Collection <Node > nodesFromResult , boolean objectAlreadyMapped ) {
332
333
@@ -364,12 +365,30 @@ private <ET> void populateProperties(MapAccessor queryResult, Neo4jPersistentEnt
364
365
}
365
366
366
367
@ Nullable
367
- private Long getInternalId (@ NonNull MapAccessor queryResult ) {
368
- return queryResult instanceof Node
369
- ? (Long ) ((Node ) queryResult ).id ()
370
- : queryResult .get (Constants .NAME_OF_INTERNAL_ID ) == null || queryResult .get (Constants .NAME_OF_INTERNAL_ID ).isNull ()
371
- ? null
372
- : queryResult .get (Constants .NAME_OF_INTERNAL_ID ).asLong ();
368
+ private String getInternalId (@ NonNull MapAccessor queryResult , @ Nullable String seed ) {
369
+ if (queryResult instanceof Node ) {
370
+ return "N" + ((Node ) queryResult ).id ();
371
+ } else if (queryResult instanceof Relationship ) {
372
+ Relationship relationshipValue = (Relationship ) queryResult ;
373
+ return "R" + seed + relationshipValue .id ();
374
+ } else if (!(queryResult .get (Constants .NAME_OF_INTERNAL_ID ) == null || queryResult .get (Constants .NAME_OF_INTERNAL_ID ).isNull ())) {
375
+ return "N" + queryResult .get (Constants .NAME_OF_INTERNAL_ID ).asLong ();
376
+ }
377
+
378
+ return null ;
379
+ }
380
+
381
+ @ Nullable
382
+ private Long getInternalIdAsLong (@ NonNull MapAccessor queryResult ) {
383
+ if (queryResult instanceof Node ) {
384
+ return ((Node ) queryResult ).id ();
385
+ } else if (queryResult instanceof Relationship ) {
386
+ return ((Relationship ) queryResult ).id ();
387
+ } else if (!(queryResult .get (Constants .NAME_OF_INTERNAL_ID ) == null || queryResult .get (Constants .NAME_OF_INTERNAL_ID ).isNull ())) {
388
+ return queryResult .get (Constants .NAME_OF_INTERNAL_ID ).asLong ();
389
+ }
390
+
391
+ return null ;
373
392
}
374
393
375
394
@ NonNull
@@ -538,6 +557,23 @@ private AssociationHandler<Neo4jPersistentProperty> populateFrom(MapAccessor que
538
557
boolean populatedScalarValue = !persistentProperty .isCollectionLike () && !persistentProperty .isMap ()
539
558
&& propertyValueNotNull ;
540
559
560
+ if (populatedCollection ) {
561
+ createInstanceOfRelationships (persistentProperty , queryResult , (RelationshipDescription ) association , baseDescription , relationshipsFromResult , nodesFromResult , false )
562
+ .ifPresent (value -> {
563
+ Collection <?> providedCollection = (Collection <?>) value ;
564
+ Collection <?> existingValue = (Collection <?>) propertyValue ;
565
+ Collection <Object > newValue = CollectionFactory .createCollection (existingValue .getClass (), providedCollection .size () + existingValue .size ());
566
+
567
+ RelationshipDescription relationshipDescription = (RelationshipDescription ) association ;
568
+ Map <Object , Object > mergedValues = new HashMap <>();
569
+ mergeCollections (relationshipDescription , existingValue , mergedValues );
570
+ mergeCollections (relationshipDescription , providedCollection , mergedValues );
571
+
572
+ newValue .addAll (mergedValues .values ());
573
+ propertyAccessor .setProperty (persistentProperty , newValue );
574
+ });
575
+ }
576
+
541
577
boolean propertyAlreadyPopulated = populatedCollection || populatedMap || populatedScalarValue ;
542
578
543
579
// avoid unnecessary re-assignment of values
@@ -550,9 +586,33 @@ private AssociationHandler<Neo4jPersistentProperty> populateFrom(MapAccessor que
550
586
};
551
587
}
552
588
589
+ private void mergeCollections (RelationshipDescription relationshipDescription , Collection <?> values , Map <Object , Object > mergedValues ) {
590
+ for (Object existingValueInCollection : values ) {
591
+ if (relationshipDescription .hasRelationshipProperties ()) {
592
+ Object existingIdPropertyValue = ((Neo4jPersistentEntity <?>) relationshipDescription .getRelationshipPropertiesEntity ())
593
+ .getPropertyAccessor (existingValueInCollection )
594
+ .getProperty (((Neo4jPersistentEntity <?>) relationshipDescription .getRelationshipPropertiesEntity ()).getIdProperty ());
595
+
596
+ mergedValues .put (existingIdPropertyValue , existingValueInCollection );
597
+ } else if (!relationshipDescription .isDynamic ()) { // should not happen because this is all inside populatedCollection (but better safe than sorry)
598
+ Object existingIdPropertyValue = ((Neo4jPersistentEntity <?>) relationshipDescription .getTarget ())
599
+ .getPropertyAccessor (existingValueInCollection )
600
+ .getProperty (((Neo4jPersistentEntity <?>) relationshipDescription .getTarget ()).getIdProperty ());
601
+
602
+ mergedValues .put (existingIdPropertyValue , existingValueInCollection );
603
+ }
604
+ }
605
+ }
606
+
607
+ private Optional <Object > createInstanceOfRelationships (Neo4jPersistentProperty persistentProperty , MapAccessor values ,
608
+ RelationshipDescription relationshipDescription , NodeDescription <?> baseDescription , Collection <Relationship > relationshipsFromResult ,
609
+ Collection <Node > nodesFromResult ) {
610
+ return createInstanceOfRelationships (persistentProperty , values , relationshipDescription , baseDescription , relationshipsFromResult , nodesFromResult , true );
611
+ }
612
+
553
613
private Optional <Object > createInstanceOfRelationships (Neo4jPersistentProperty persistentProperty , MapAccessor values ,
554
614
RelationshipDescription relationshipDescription , NodeDescription <?> baseDescription , Collection <Relationship > relationshipsFromResult ,
555
- Collection <Node > nodesFromResult ) {
615
+ Collection <Node > nodesFromResult , boolean fetchMore ) {
556
616
557
617
String typeOfRelationship = relationshipDescription .getType ();
558
618
String targetLabel = relationshipDescription .getTarget ().getPrimaryLabel ();
@@ -592,7 +652,7 @@ private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty p
592
652
List <Object > relationshipsAndProperties = new ArrayList <>();
593
653
594
654
if (Values .NULL .equals (list )) {
595
- Long sourceNodeId = getInternalId (values );
655
+ Long sourceNodeId = getInternalIdAsLong (values );
596
656
597
657
Function <Relationship , Long > sourceIdSelector = relationshipDescription .isIncoming () ? Relationship ::endNodeId : Relationship ::startNodeId ;
598
658
Function <Relationship , Long > targetIdSelector = relationshipDescription .isIncoming () ? Relationship ::startNodeId : Relationship ::endNodeId ;
@@ -619,22 +679,37 @@ private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty p
619
679
// If this relationship got processed twice (OUTGOING, INCOMING), it is never needed again
620
680
// and therefor should not be in the list.
621
681
// Otherwise, for highly linked data it could potentially cause a StackOverflowError.
622
- if (knownObjects .hasProcessedRelationshipCompletely (possibleRelationship .id ())) {
682
+ String direction = relationshipDescription .getDirection ().name ();
683
+ if (knownObjects .hasProcessedRelationshipCompletely ("R" + direction + possibleRelationship .id ())) {
623
684
relationshipsFromResult .remove (possibleRelationship );
624
685
}
625
686
// If the target is the same(equal) node, get the related object from the cache.
626
687
// Avoiding the call to the map method also breaks an endless cycle of trying to finish
627
688
// the property population of _this_ object.
628
689
// The initial population will happen at the end of this mapping. This is sufficient because
629
690
// it only affects properties not changing the instance of the object.
630
- Object mappedObject = sourceNodeId != null && sourceNodeId .equals (targetNodeId )
631
- ? knownObjects .getObject (sourceNodeId )
632
- : map (possibleValueNode , concreteTargetNodeDescription , null , relationshipsFromResult , nodesFromResult );
633
- if (relationshipDescription .hasRelationshipProperties ()) {
691
+ Object mappedObject ;
692
+ if (fetchMore ) {
693
+ mappedObject = sourceNodeId != null && sourceNodeId .equals (targetNodeId )
694
+ ? knownObjects .getObject ("N" + sourceNodeId )
695
+ : map (possibleValueNode , concreteTargetNodeDescription , null , null , relationshipsFromResult , nodesFromResult );
696
+ } else {
697
+ Object objectFromStore = knownObjects .getObject ("N" + targetNodeId );
698
+ mappedObject = objectFromStore != null
699
+ ? objectFromStore
700
+ : map (possibleValueNode , concreteTargetNodeDescription , null , null , relationshipsFromResult , nodesFromResult );
701
+ }
634
702
635
- Object relationshipProperties = map (possibleRelationship ,
636
- (Neo4jPersistentEntity <?>) relationshipDescription .getRelationshipPropertiesEntity (),
637
- mappedObject , relationshipsFromResult , nodesFromResult );
703
+ if (relationshipDescription .hasRelationshipProperties ()) {
704
+ Object relationshipProperties ;
705
+ if (fetchMore ) {
706
+ relationshipProperties = map (possibleRelationship , (Neo4jPersistentEntity <?>) relationshipDescription .getRelationshipPropertiesEntity (), mappedObject , relationshipDescription , relationshipsFromResult , nodesFromResult );
707
+ } else {
708
+ Object objectFromStore = knownObjects .getObject (getInternalId (possibleRelationship , relationshipDescription .getDirection ().name ()));
709
+ relationshipProperties = objectFromStore != null
710
+ ? objectFromStore
711
+ : map (possibleRelationship , (Neo4jPersistentEntity <?>) relationshipDescription .getRelationshipPropertiesEntity (), mappedObject , relationshipDescription , relationshipsFromResult , nodesFromResult );
712
+ }
638
713
relationshipsAndProperties .add (relationshipProperties );
639
714
mappedObjectHandler .accept (possibleRelationship .type (), relationshipProperties );
640
715
} else {
@@ -651,7 +726,15 @@ private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty p
651
726
Neo4jPersistentEntity <?> concreteTargetNodeDescription =
652
727
getMostConcreteTargetNodeDescription (genericTargetNodeDescription , relatedEntity );
653
728
654
- Object valueEntry = map (relatedEntity , concreteTargetNodeDescription , null , relationshipsFromResult , nodesFromResult );
729
+ Object valueEntry ;
730
+ if (fetchMore ) {
731
+ valueEntry = map (relatedEntity , concreteTargetNodeDescription , null , null , relationshipsFromResult , nodesFromResult );
732
+ } else {
733
+ Object objectFromStore = knownObjects .getObject (getInternalId (relatedEntity , null ));
734
+ valueEntry = objectFromStore != null
735
+ ? objectFromStore
736
+ : map (relatedEntity , concreteTargetNodeDescription , null , null , relationshipsFromResult , nodesFromResult );
737
+ }
655
738
656
739
if (relationshipDescription .hasRelationshipProperties ()) {
657
740
String sourceLabel = relationshipDescription .getSource ().getMostAbstractParentLabel (baseDescription );
@@ -660,9 +743,16 @@ private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty p
660
743
Relationship relatedEntityRelationship = relatedEntity .get (relationshipSymbolicName )
661
744
.asRelationship ();
662
745
663
- Object relationshipProperties = map (relatedEntityRelationship ,
664
- (Neo4jPersistentEntity <?>) relationshipDescription .getRelationshipPropertiesEntity (),
665
- valueEntry , relationshipsFromResult , nodesFromResult );
746
+ Object relationshipProperties ;
747
+ if (fetchMore ) {
748
+ relationshipProperties = map (relatedEntityRelationship , (Neo4jPersistentEntity <?>) relationshipDescription .getRelationshipPropertiesEntity (), valueEntry , relationshipDescription , relationshipsFromResult , nodesFromResult );
749
+ } else {
750
+ Object objectFromStore = knownObjects .getObject (getInternalId (relatedEntityRelationship , relationshipDescription .getDirection ().name ()));
751
+ relationshipProperties = objectFromStore != null
752
+ ? objectFromStore
753
+ : map (relatedEntityRelationship , (Neo4jPersistentEntity <?>) relationshipDescription .getRelationshipPropertiesEntity (), valueEntry , relationshipDescription , relationshipsFromResult , nodesFromResult );
754
+ }
755
+
666
756
relationshipsAndProperties .add (relationshipProperties );
667
757
mappedObjectHandler .accept (relatedEntity .get (RelationshipDescription .NAME_OF_RELATIONSHIP_TYPE ).asString (), relationshipProperties );
668
758
} else {
@@ -779,14 +869,14 @@ static class KnownObjects {
779
869
private final Lock read = lock .readLock ();
780
870
private final Lock write = lock .writeLock ();
781
871
782
- private final Map <Long , Object > internalIdStore = new HashMap <>();
783
- private final Map <Long , Boolean > internalCurrentRecord = new HashMap <>();
784
- private final Set <Long > previousRecords = new HashSet <>();
785
- private final Set <Long > idsInCreation = new HashSet <>();
872
+ private final Map <String , Object > internalIdStore = new HashMap <>();
873
+ private final Map <String , Boolean > internalCurrentRecord = new HashMap <>();
874
+ private final Set <String > previousRecords = new HashSet <>();
875
+ private final Set <String > idsInCreation = new HashSet <>();
786
876
787
- private final Map <Long , Integer > processedRelationships = new HashMap <>();
877
+ private final Map <String , Integer > processedRelationships = new HashMap <>();
788
878
789
- private void storeObject (@ Nullable Long internalId , Object object ) {
879
+ private void storeObject (@ Nullable String internalId , Object object ) {
790
880
if (internalId == null ) {
791
881
return ;
792
882
}
@@ -800,7 +890,7 @@ private void storeObject(@Nullable Long internalId, Object object) {
800
890
}
801
891
}
802
892
803
- private void setInCreation (@ Nullable Long internalId ) {
893
+ private void setInCreation (@ Nullable String internalId ) {
804
894
if (internalId == null ) {
805
895
return ;
806
896
}
@@ -812,7 +902,7 @@ private void setInCreation(@Nullable Long internalId) {
812
902
}
813
903
}
814
904
815
- private boolean isInCreation (@ Nullable Long internalId ) {
905
+ private boolean isInCreation (@ Nullable String internalId ) {
816
906
if (internalId == null ) {
817
907
return false ;
818
908
}
@@ -825,7 +915,7 @@ private boolean isInCreation(@Nullable Long internalId) {
825
915
}
826
916
827
917
@ Nullable
828
- private Object getObject (@ Nullable Long internalId ) {
918
+ private Object getObject (@ Nullable String internalId ) {
829
919
if (internalId == null ) {
830
920
return null ;
831
921
}
@@ -845,7 +935,7 @@ private Object getObject(@Nullable Long internalId) {
845
935
return null ;
846
936
}
847
937
848
- private void removeFromInCreation (@ Nullable Long internalId ) {
938
+ private void removeFromInCreation (@ Nullable String internalId ) {
849
939
if (internalId == null ) {
850
940
return ;
851
941
}
@@ -857,7 +947,7 @@ private void removeFromInCreation(@Nullable Long internalId) {
857
947
}
858
948
}
859
949
860
- private boolean alreadyMappedInPreviousRecord (@ Nullable Long internalId ) {
950
+ private boolean alreadyMappedInPreviousRecord (@ Nullable String internalId ) {
861
951
if (internalId == null ) {
862
952
return false ;
863
953
}
@@ -877,7 +967,7 @@ private boolean alreadyMappedInPreviousRecord(@Nullable Long internalId) {
877
967
* It increases the process count of relationships (mapped by their ids)
878
968
* AND checks if it was already processed twice (INCOMING/OUTGOING).
879
969
*/
880
- private boolean hasProcessedRelationshipCompletely (Long relationshipId ) {
970
+ private boolean hasProcessedRelationshipCompletely (String relationshipId ) {
881
971
try {
882
972
write .lock ();
883
973
0 commit comments