View Javadoc
1   /*
2    * The coLAB project
3    * Copyright (C) 2021-2023 AlbaSim, MEI, HEIG-VD, HES-SO
4    *
5    * Licensed under the MIT License
6    */
7   package ch.colabproject.colab.api.controller;
8   
9   import ch.colabproject.colab.api.controller.card.CardContentManager;
10  import ch.colabproject.colab.api.controller.document.FileManager;
11  import ch.colabproject.colab.api.controller.document.ResourceReferenceSpreadingHelper;
12  import ch.colabproject.colab.api.controller.document.YjsException;
13  import ch.colabproject.colab.api.controller.document.YjsLexicalCaller;
14  import ch.colabproject.colab.api.exceptions.ColabMergeException;
15  import ch.colabproject.colab.api.model.ColabEntity;
16  import ch.colabproject.colab.api.model.DuplicationParam;
17  import ch.colabproject.colab.api.model.card.*;
18  import ch.colabproject.colab.api.model.document.*;
19  import ch.colabproject.colab.api.model.link.ActivityFlowLink;
20  import ch.colabproject.colab.api.model.link.StickyNoteLink;
21  import ch.colabproject.colab.api.model.project.Project;
22  import ch.colabproject.colab.api.model.team.TeamMember;
23  import ch.colabproject.colab.api.model.team.TeamRole;
24  import ch.colabproject.colab.api.model.team.acl.Assignment;
25  import ch.colabproject.colab.api.model.user.User;
26  import ch.colabproject.colab.generator.model.exceptions.HttpErrorMessage;
27  import ch.colabproject.colab.generator.model.exceptions.MessageI18nKey;
28  import org.slf4j.Logger;
29  import org.slf4j.LoggerFactory;
30  
31  import javax.jcr.RepositoryException;
32  import java.io.IOException;
33  import java.io.InputStream;
34  import java.util.*;
35  import java.util.Map.Entry;
36  
37  /**
38   * Duplication of colab entities.
39   * <p>
40   * Usage :
41   * <ul>
42   * <li>Create a {@link DuplicationManager}</li>
43   * <li>Duplicate the object with any duplicateXXX</li>
44   * <li>Save to JPA database</li>
45   * <li>Call {@link #duplicateDataIntoJCR()} in order to save to JCR
46   * database</li>
47   * </ul>
48   *
49   * @author sandra
50   */
51  public class DuplicationManager {
52  
53      /** logger */
54      private static final Logger logger = LoggerFactory.getLogger(DuplicationManager.class);
55  
56      /** Comparator for sorting data to create objects in the same order */
57      private static final Comparator<ColabEntity> ID_COMPARATOR = Comparator
58              .comparingLong(entity -> entity.getId());
59  
60      /** parameters to fine tune a duplication */
61      private final DuplicationParam params;
62  
63      /** helper for resource references */
64      private final ResourceReferenceSpreadingHelper resourceSpreader;
65  
66      /** File persistence management */
67      private final FileManager fileManager;
68  
69      /** Card content specific logic handling */
70      private final CardContentManager cardContentManager;
71  
72      /** To call the YJS lexical server */
73      private YjsLexicalCaller yjsLexicalCaller;
74  
75      /** Matching between the old id and the new team roles */
76      private Map<Long, TeamRole> teamRoleMatching = new HashMap<>();
77  
78      /** Matching between the old id and the new team members */
79      private Map<Long, TeamMember> teamMemberMatching = new HashMap<>();
80  
81      /** Matching between the old id and the new card types */
82      private Map<Long, AbstractCardType> cardTypeMatching = new HashMap<>();
83  
84      /** Matching between the old id and the new cards */
85      private Map<Long, Card> cardMatching = new HashMap<>();
86  
87      /** Matching between the old id and the new card contents */
88      private Map<Long, CardContent> cardContentMatching = new HashMap<>();
89  
90      /** Matching between the old id and the new resources */
91      private Map<Long, AbstractResource> resourceMatching = new HashMap<>();
92  
93      /** Matching between the old id and the new document */
94      private Map<Long, Document> documentMatching = new HashMap<>();
95  
96      /** the sticky note links to duplicate. They are filled when handling cards */
97      private List<StickyNoteLink> stickyNoteLinksToDuplicate = new ArrayList<>();
98  
99      /** the activity flow links to duplicate. They are filled when handling cards */
100     private List<ActivityFlowLink> activityFlowLinksToDuplicate = new ArrayList<>();
101 
102     /** Document files to process once the ids are here */
103     private Map<Long, DocumentFile> documentFilesToProcessOnceIds = new HashMap<>();
104 
105     /**
106      * @param params             Parameters to fine tune duplication
107      * @param resourceSpreader   Helper for resource references
108      * @param fileManager        File persistence management
109      * @param cardContentManager Card content specific logic handling
110      */
111     public DuplicationManager(DuplicationParam params,
112             ResourceReferenceSpreadingHelper resourceSpreader,
113             FileManager fileManager, CardContentManager cardContentManager) {
114         this.params = params;
115         this.resourceSpreader = resourceSpreader;
116         this.fileManager = fileManager;
117         this.cardContentManager = cardContentManager;
118     }
119 
120     // *********************************************************************************************
121     // duplication
122     // *********************************************************************************************
123 
124     /**
125      * Duplicate the given project. No database action is provided.
126      *
127      * @param originalProject the project to duplicate
128      *
129      * @return the duplicated project
130      */
131     public Project duplicateProject(Project originalProject) {
132         try {
133             Project newProject = new Project();
134             newProject.mergeToDuplicate(originalProject);
135 
136             ////////////////////////////////////////////////////////////////////////////////////////
137             // Team roles
138             if (params.isWithRoles()) {
139                 List<TeamRole> teamRoles = originalProject.getRoles();
140                 teamRoles.sort(ID_COMPARATOR);
141 
142                 for (TeamRole original : teamRoles) {
143                     TeamRole newTeamRole = duplicateTeamRole(original);
144 
145                     newTeamRole.setProject(newProject);
146                     newProject.getRoles().add(newTeamRole);
147                 }
148             } else {
149                 logger.info("param do not duplicate project's team roles");
150             }
151 
152             ////////////////////////////////////////////////////////////////////////////////////////
153             // Team members
154             if (params.isWithTeamMembers()) {
155                 List<TeamMember> teamMembers = originalProject.getTeamMembers();
156                 teamMembers.sort(ID_COMPARATOR);
157 
158                 for (TeamMember original : teamMembers) {
159                     TeamMember newTeamMember = duplicateTeamMember(original);
160 
161                     newTeamMember.setProject(newProject);
162                     newProject.getTeamMembers().add(newTeamMember);
163                 }
164             } else {
165                 logger.info("param do not duplicate project's team members");
166             }
167 
168             ////////////////////////////////////////////////////////////////////////////////////////
169             // instance makers
170             /* are never duplicated */
171 
172             ////////////////////////////////////////////////////////////////////////////////////////
173             // Card types
174             if (params.isWithCardTypes()) {
175                 List<AbstractCardType> cardTypes = originalProject.getElementsToBeDefined();
176                 cardTypes.sort(ID_COMPARATOR);
177 
178                 for (AbstractCardType original : cardTypes) {
179                     AbstractCardType newCardType = duplicateCardType(original);
180 
181                     newCardType.setProject(newProject);
182                     newProject.getElementsToBeDefined().add(newCardType);
183                 }
184             }
185 
186             ////////////////////////////////////////////////////////////////////////////////////////
187             // Cards
188             if (params.isWithCardsStructure()) {
189                 Card original = originalProject.getRootCard();
190                 if (original != null) {
191                     Card newRootCard = duplicateCard(original);
192 
193                     newRootCard.setRootCardProject(newProject);
194                     newProject.setRootCard(newRootCard);
195                 }
196 
197             }
198 
199             ////////////////////////////////////////////////////////////////////////////////////////
200             // sticky notes
201             if (params.isWithStickyNotes()) {
202                 stickyNoteLinksToDuplicate.sort(ID_COMPARATOR);
203 
204                 for (StickyNoteLink original : stickyNoteLinksToDuplicate) {
205                     duplicateStickyNoteLink(original);
206                 }
207             }
208 
209             ////////////////////////////////////////////////////////////////////////////////////////
210             // activity flow
211             if (params.isWithActivityFlow()) {
212                 activityFlowLinksToDuplicate.sort(ID_COMPARATOR);
213 
214                 for (ActivityFlowLink original : activityFlowLinksToDuplicate) {
215                     duplicateActivityFlowLink(original);
216                 }
217             }
218 
219             return newProject;
220         } catch (ColabMergeException e) {
221             throw HttpErrorMessage.dataError(MessageI18nKey.DATA_INTEGRITY_FAILURE);
222         }
223         // TODO sandra work in progress : handle exceptions
224     }
225 
226     private TeamRole duplicateTeamRole(TeamRole original) throws ColabMergeException {
227         TeamRole newTeamRole = new TeamRole();
228         newTeamRole.mergeToDuplicate(original);
229 
230         teamRoleMatching.put(original.getId(), newTeamRole);
231 
232         return newTeamRole;
233     }
234 
235     private TeamMember duplicateTeamMember(TeamMember original) throws ColabMergeException {
236         TeamMember newTeamMember = new TeamMember();
237         newTeamMember.mergeToDuplicate(original);
238 
239         teamMemberMatching.put(original.getId(), newTeamMember);
240 
241         ////////////////////////////////////////////////////////////////////////////////////////////
242         // Team members's user
243         User user = original.getUser();
244         if (user != null) {
245             newTeamMember.setUser(user);
246         }
247 
248         ////////////////////////////////////////////////////////////////////////////////////////////
249         // Team members's roles
250         if (params.isWithRoles()) {
251             List<TeamRole> linkedRoles = original.getRoles();
252             linkedRoles.sort(ID_COMPARATOR);
253 
254             for (TeamRole linkedRole : linkedRoles) {
255                 TeamRole newRole = teamRoleMatching.get(linkedRole.getId());
256 
257                 if (newRole == null) {
258                     throw HttpErrorMessage.dataError(MessageI18nKey.DATA_INTEGRITY_FAILURE);
259                 }
260 
261                 newRole.getMembers().add(newTeamMember);
262                 newTeamMember.getRoles().add(newRole);
263             }
264         }
265 
266         return newTeamMember;
267     }
268 
269     private AbstractCardType duplicateCardType(AbstractCardType original)
270             throws ColabMergeException {
271 
272         if (params.isMakeOnlyCardTypeReferences()) {
273             CardTypeRef newCardTypeRef = new CardTypeRef();
274             newCardTypeRef.setTarget(original);
275             newCardTypeRef.setDeprecated(original.isDeprecated());
276             newCardTypeRef.setPublished(false);
277 
278             cardTypeMatching.put(original.getId(), newCardTypeRef);
279 
280             if (resourceSpreader == null) {
281                 throw new IllegalStateException(
282                         "Dear developer, please define the resource spreader");
283             }
284 
285             List<ResourceRef> createdRefs = resourceSpreader
286                     .extractReferencesFromUp(newCardTypeRef);
287             createdRefs.stream().forEach(ref -> resourceMatching.put(ref.getTarget().getId(), ref));
288 
289             return newCardTypeRef;
290         } else {
291             AbstractCardType newAbstractCardType;
292             if (original instanceof CardType) {
293                 CardType originalCardType = (CardType) original;
294 
295                 CardType newCardType = new CardType();
296                 newCardType.mergeToDuplicate(originalCardType);
297 
298                 cardTypeMatching.put(originalCardType.getId(), newCardType);
299 
300                 TextDataBlock purpose = originalCardType.getPurpose();
301                 if (purpose != null) {
302                     TextDataBlock newPurpose = (TextDataBlock) duplicateDocument(purpose);
303                     newPurpose.setPurposingCardType(newCardType);
304                     newCardType.setPurpose(newPurpose);
305                 }
306 
307                 newAbstractCardType = newCardType;
308             } else if (original instanceof CardTypeRef) {
309                 CardTypeRef originalCardTypeRef = (CardTypeRef) original;
310 
311                 CardTypeRef newCardTypeRef = new CardTypeRef();
312                 newCardTypeRef.mergeToDuplicate(originalCardTypeRef);
313 
314                 cardTypeMatching.put(originalCardTypeRef.getId(), newCardTypeRef);
315 
316                 AbstractCardType originalTarget = originalCardTypeRef.getTarget();
317                 if (originalTarget != null) {
318                     if (originalTarget.getProjectId() != originalCardTypeRef.getProjectId()) {
319                         newCardTypeRef.setTarget(originalTarget);
320                     } else {
321                         throw new IllegalStateException(
322                                 "the target of a card type reference must be outside the project");
323                         // Note for an hypothetical future evolution :
324                         // if we break the condition that, in a project,
325                         // there is only one reference per target type outside the project
326                         // we must process the card types in the appropriate order
327                     }
328                 }
329 
330                 newAbstractCardType = newCardTypeRef;
331             } else {
332                 throw new IllegalStateException("abstract card type implementation not handled");
333             }
334 
335             if (params.isWithResources()) {
336                 List<AbstractResource> originalResources = original.getDirectAbstractResources();
337                 originalResources.sort(ID_COMPARATOR);
338 
339                 for (AbstractResource originalResource : originalResources) {
340                     AbstractResource newResource = duplicateResource(originalResource);
341 
342                     newResource.setAbstractCardType(newAbstractCardType);
343                     newAbstractCardType.getDirectAbstractResources().add(newResource);
344                 }
345             } else {
346                 logger.info("param do not duplicate project's resources");
347             }
348 
349             return newAbstractCardType;
350         }
351     }
352 
353     private Card duplicateCard(Card original) throws ColabMergeException {
354         Card newCard = new Card();
355         newCard.mergeToDuplicate(original);
356 
357         cardMatching.put(original.getId(), newCard);
358 
359         AbstractCardType originalCardType = original.getCardType();
360         if (originalCardType != null) {
361             AbstractCardType newCardType = cardTypeMatching.get(originalCardType.getId());
362 
363             if (newCardType == null) {
364                 throw HttpErrorMessage.dataError(MessageI18nKey.DATA_INTEGRITY_FAILURE);
365             }
366 
367             newCard.setCardType(newCardType);
368         }
369 
370         if (params.isWithResources()) {
371             List<AbstractResource> originalResources = original.getDirectAbstractResources();
372             originalResources.sort(ID_COMPARATOR);
373 
374             for (AbstractResource originalResource : originalResources) {
375                 AbstractResource newResource = duplicateResource(originalResource);
376 
377                 newResource.setCard(newCard);
378                 newCard.getDirectAbstractResources().add(newResource);
379             }
380         } else {
381             logger.info("param do not duplicate project's resources");
382         }
383 
384         List<CardContent> originalContents = original.getContentVariants();
385         originalContents.sort(ID_COMPARATOR);
386 
387         for (CardContent originalCardContent : originalContents) {
388             CardContent newCardContent = duplicateCardContent(originalCardContent);
389 
390             newCardContent.setCard(newCard);
391             newCard.getContentVariants().add(newCardContent);
392         }
393 
394         ////////////////////////////////////////////////////////////////////////////////////////////
395         // Assignments
396 
397         List<Assignment> originalAssignments = original.getAssignments();
398         originalAssignments.sort(ID_COMPARATOR);
399 
400         for (Assignment originalAssignment : originalAssignments) {
401             if ((params.isWithTeamMembers() || originalAssignment.getMember() == null)
402                     && (params.isWithRoles() || originalAssignment.getRole() == null)) {
403 
404                 Assignment newAssignment = duplicateAssignment(originalAssignment);
405 
406                 if (newAssignment != null) { // if we got a null, no need to duplicate
407                     newAssignment.setCard(newCard);
408                     newCard.getAssignments().add(newAssignment);
409                 }
410             }
411 
412         }
413 
414         ////////////////////////////////////////////////////////////////////////////////////////
415         // sticky notes
416         if (params.isWithStickyNotes()) {
417             stickyNoteLinksToDuplicate.addAll(original.getStickyNoteLinksAsDest());
418         }
419 
420         ////////////////////////////////////////////////////////////////////////////////////////
421         // activity flow
422         if (params.isWithActivityFlow()) {
423             activityFlowLinksToDuplicate.addAll(original.getActivityFlowLinksAsPrevious());
424         }
425 
426         return newCard;
427     }
428 
429     private CardContent duplicateCardContent(CardContent original) throws ColabMergeException {
430         CardContent newCardContent = new CardContent();
431         newCardContent.mergeToDuplicate(original);
432 
433         if (params.isResetProgressionData()) {
434             cardContentManager.resetProgression(newCardContent);
435         }
436 
437         cardContentMatching.put(original.getId(), newCardContent);
438 
439         if (params.isWithDeliverables()) {
440             List<Document> originalDeliverables = original.getDeliverables();
441             originalDeliverables.sort(ID_COMPARATOR);
442 
443             for (Document originalDoc : originalDeliverables) {
444                 Document newDoc = duplicateDocument(originalDoc);
445 
446                 newDoc.setOwningCardContent(newCardContent);
447                 newCardContent.getDeliverables().add(newDoc);
448             }
449         } else {
450             logger.info("param do not duplicate project's deliverables");
451         }
452 
453         if (params.isWithResources()) {
454             List<AbstractResource> originalResources = original.getDirectAbstractResources();
455             originalResources.sort(ID_COMPARATOR);
456 
457             for (AbstractResource originalResource : originalResources) {
458                 AbstractResource newResource = duplicateResource(originalResource);
459 
460                 newResource.setCardContent(newCardContent);
461                 newCardContent.getDirectAbstractResources().add(newResource);
462             }
463         } else {
464             logger.info("param do not duplicate project's resources");
465         }
466 
467         List<Card> originalSubCards = original.getSubCards();
468         originalSubCards.sort(ID_COMPARATOR);
469 
470         for (Card originalSubCard : originalSubCards) {
471             Card newSubCard = duplicateCard(originalSubCard);
472 
473             newSubCard.setParent(newCardContent);
474             newCardContent.getSubCards().add(newSubCard);
475         }
476 
477         return newCardContent;
478     }
479 
480     private Document duplicateDocument(Document original) throws ColabMergeException {
481         if (original instanceof DocumentFile) {
482             DocumentFile originalDocumentFile = (DocumentFile) original;
483 
484             DocumentFile newDocumentFile = duplicateDocumentFile(originalDocumentFile);
485 
486             documentMatching.put(originalDocumentFile.getId(), newDocumentFile);
487 
488             return newDocumentFile;
489         } else if (original instanceof ExternalLink) {
490             ExternalLink originalExternalLink = (ExternalLink) original;
491 
492             ExternalLink newExternalLink = new ExternalLink();
493             newExternalLink.mergeToDuplicate(originalExternalLink);
494 
495             documentMatching.put(originalExternalLink.getId(), newExternalLink);
496 
497             return newExternalLink;
498         } else if (original instanceof TextDataBlock) {
499             TextDataBlock originalTextDataBlock = (TextDataBlock) original;
500 
501             TextDataBlock newTextDataBlock = new TextDataBlock();
502             newTextDataBlock.mergeToDuplicate(originalTextDataBlock);
503 
504             documentMatching.put(originalTextDataBlock.getId(), newTextDataBlock);
505 
506             return newTextDataBlock;
507         } else {
508             throw new IllegalStateException("abstract card type implementation not handled");
509         }
510     }
511 
512     private DocumentFile duplicateDocumentFile(DocumentFile original) throws ColabMergeException {
513         DocumentFile newDocumentFile = new DocumentFile();
514         newDocumentFile.mergeToDuplicate(original);
515 
516         documentFilesToProcessOnceIds.put(original.getId(), newDocumentFile);
517 
518         return newDocumentFile;
519     }
520 
521     /**
522      * Duplicate the given resource. No database action is provided.
523      *
524      * @param original the resource to duplicate
525      *
526      * @return the duplicated resource
527      *
528      * @throws ColabMergeException if merging is not possible
529      */
530     public AbstractResource duplicateResource(AbstractResource original)
531             throws ColabMergeException {
532         if (original instanceof Resource) {
533             Resource originalResource = (Resource) original;
534 
535             Resource newResource = new Resource();
536             newResource.mergeToDuplicate(originalResource);
537 
538             resourceMatching.put(originalResource.getId(), newResource);
539 
540             List<Document> originalDocuments = originalResource.getDocuments();
541             originalDocuments.sort(ID_COMPARATOR);
542 
543             for (Document originalDocument : originalDocuments) {
544                 Document newDocument = duplicateDocument(originalDocument);
545 
546                 newDocument.setOwningResource(newResource);
547                 newResource.getDocuments().add(newDocument);
548             }
549 
550             TextDataBlock teaser = originalResource.getTeaser();
551             if (teaser != null) {
552                 TextDataBlock newTeaser = (TextDataBlock) duplicateDocument(teaser);
553 
554                 newTeaser.setTeasingResource(newResource);
555                 newResource.setTeaser(newTeaser);
556             }
557 
558             return newResource;
559         } else if (original instanceof ResourceRef) {
560             ResourceRef originalResourceRef = (ResourceRef) original;
561 
562             ResourceRef newResourceRef = new ResourceRef();
563             newResourceRef.mergeToDuplicate(originalResourceRef);
564 
565             resourceMatching.put(originalResourceRef.getId(), newResourceRef);
566 
567             AbstractResource originalTarget = originalResourceRef.getTarget();
568             if (originalTarget != null) {
569                 AbstractResource newTarget;
570                 if (resourceMatching.containsKey(originalTarget.getId())) {
571                     newTarget = resourceMatching.get(originalTarget.getId());
572                 } else {
573                     newTarget = originalTarget;
574                 }
575                 newResourceRef.setTarget(newTarget);
576             }
577 
578             return newResourceRef;
579 
580         } else {
581             throw new IllegalStateException("abstract card type implementation not handled");
582         }
583     }
584 
585     private Assignment duplicateAssignment(Assignment original)
586             throws ColabMergeException {
587         Assignment newAssignment = new Assignment();
588         newAssignment.mergeToDuplicate(original);
589 
590         if (original.getMember() != null) {
591             TeamMember member = teamMemberMatching.get(original.getMember().getId());
592 
593             if (member == null) {
594                 throw HttpErrorMessage.dataError(MessageI18nKey.DATA_INTEGRITY_FAILURE);
595             }
596 
597             member.getAssignments().add(newAssignment);
598             newAssignment.setMember(member);
599         }
600 
601         if (original.getRole() != null) {
602             TeamRole role = teamRoleMatching.get(original.getRole().getId());
603 
604             if (role == null) {
605                 throw HttpErrorMessage.dataError(MessageI18nKey.DATA_INTEGRITY_FAILURE);
606             }
607 
608             role.getAssignments().add(newAssignment);
609             newAssignment.setRole(role);
610         }
611 
612         return newAssignment;
613     }
614 
615     private StickyNoteLink duplicateStickyNoteLink(StickyNoteLink original)
616             throws ColabMergeException {
617         StickyNoteLink newLink = new StickyNoteLink();
618         newLink.mergeToDuplicate(original);
619 
620         TextDataBlock explanation = original.getExplanation();
621         if (explanation != null) {
622             TextDataBlock newExplanation = (TextDataBlock) duplicateDocument(explanation);
623             newExplanation.setExplainingStickyNoteLink(newLink);
624             newLink.setExplanation(newExplanation);
625         }
626 
627         if (original.getDestinationCard() != null) {
628             Card destinationCard = cardMatching.get(original.getDestinationCard().getId());
629 
630             if (destinationCard == null) {
631                 throw HttpErrorMessage.dataError(MessageI18nKey.DATA_INTEGRITY_FAILURE);
632             }
633 
634             destinationCard.getStickyNoteLinksAsDest().add(newLink);
635             newLink.setDestinationCard(destinationCard);
636         }
637 
638         if (original.getSrcCard() != null) {
639             Card srcCard = cardMatching.get(original.getSrcCard().getId());
640 
641             if (srcCard == null) {
642                 throw HttpErrorMessage.dataError(MessageI18nKey.DATA_INTEGRITY_FAILURE);
643             }
644 
645             srcCard.getStickyNoteLinksAsSrc().add(newLink);
646             newLink.setSrcCard(srcCard);
647         }
648 
649         if (original.getSrcCardContent() != null) {
650             CardContent srcCardContent = cardContentMatching
651                     .get(original.getSrcCardContent().getId());
652 
653             if (srcCardContent == null) {
654                 throw HttpErrorMessage.dataError(MessageI18nKey.DATA_INTEGRITY_FAILURE);
655             }
656 
657             srcCardContent.getStickyNoteLinksAsSrc().add(newLink);
658             newLink.setSrcCardContent(srcCardContent);
659         }
660 
661         if (original.getSrcResourceOrRef() != null) {
662             AbstractResource srcResourceOrRef = resourceMatching
663                     .get(original.getSrcResourceOrRef().getId());
664 
665             if (srcResourceOrRef == null) {
666                 throw HttpErrorMessage.dataError(MessageI18nKey.DATA_INTEGRITY_FAILURE);
667             }
668 
669             srcResourceOrRef.getStickyNoteLinksAsSrc().add(newLink);
670             newLink.setSrcResourceOrRef(srcResourceOrRef);
671         }
672 
673         if (original.getSrcDocument() != null) {
674             Document srcDocument = documentMatching.get(original.getSrcDocument().getId());
675 
676             if (srcDocument == null) {
677                 throw HttpErrorMessage.dataError(MessageI18nKey.DATA_INTEGRITY_FAILURE);
678             }
679 
680             srcDocument.getStickyNoteLinksAsSrc().add(newLink);
681             newLink.setSrcDocument(srcDocument);
682         }
683 
684         return newLink;
685     }
686 
687     private ActivityFlowLink duplicateActivityFlowLink(ActivityFlowLink original)
688             throws ColabMergeException {
689         ActivityFlowLink newLink = new ActivityFlowLink();
690         newLink.mergeToDuplicate(newLink);
691 
692         if (original.getPreviousCard() != null) {
693             Card previousCard = cardMatching.get(original.getPreviousCard().getId());
694 
695             if (previousCard == null) {
696                 throw HttpErrorMessage.dataError(MessageI18nKey.DATA_INTEGRITY_FAILURE);
697             }
698 
699             previousCard.getActivityFlowLinksAsPrevious().add(newLink);
700             newLink.setPreviousCard(previousCard);
701         }
702 
703         if (original.getNextCard() != null) {
704             Card nextCard = cardMatching.get(original.getNextCard().getId());
705 
706             if (nextCard == null) {
707                 throw HttpErrorMessage.dataError(MessageI18nKey.DATA_INTEGRITY_FAILURE);
708             }
709 
710             nextCard.getActivityFlowLinksAsNext().add(newLink);
711             newLink.setNextCard(nextCard);
712         }
713 
714         return newLink;
715     }
716 
717     /**
718      * Duplicate the data in the JCR. It must happen after creating the data in JPA
719      * as long as we
720      * need the ids.
721      */
722     public void duplicateDataIntoJCR() {
723         try {
724             for (Entry<Long, DocumentFile> data : documentFilesToProcessOnceIds.entrySet()) {
725                 duplicateFileDocumentIntoJCR(data.getKey(), data.getValue());
726             }
727         } catch (RepositoryException e) {
728             throw HttpErrorMessage.duplicationError();
729         }
730     }
731 
732     /**
733      * Duplicate the data in the Lexical YJS service.
734      * <p>
735      * That is the text data of the card contents and resources
736      */
737     public void duplicateLexicalData() {
738         if (params.isWithDeliverables()) {
739             for (Entry<Long, CardContent> data : cardContentMatching.entrySet()) {
740 
741                 callDuplicateYjsService(data.getKey(), LexicalDataOwnershipKind.CARD_CONTENT,
742                         data.getValue().getId(), LexicalDataOwnershipKind.CARD_CONTENT);
743             }
744         }
745 
746         if (params.isWithResources()) {
747             for (Entry<Long, AbstractResource> data : resourceMatching.entrySet()) {
748                 if (data.getValue() instanceof Resource) {
749                     callDuplicateYjsService(data.getKey(), LexicalDataOwnershipKind.RESOURCE,
750                             data.getValue().getId(), LexicalDataOwnershipKind.RESOURCE);
751                 }
752             }
753         }
754     }
755 
756     /**
757      * Duplicate lexical data for a specific owner
758      *
759      * @param srcOwnerId    the original owner id
760      * @param srcOwnerKind  the original kind of owner
761      * @param destOwnerId   the new owner id
762      * @param destOwnerKind the new kind of owner
763      */
764     private void callDuplicateYjsService(Long srcOwnerId, LexicalDataOwnershipKind srcOwnerKind,
765             Long destOwnerId, LexicalDataOwnershipKind destOwnerKind) {
766         try {
767             // yjsLexicalCaller must be instanced here, else only 6 texts can be duplicated
768             // please, make it stronger and consistent if you can
769             yjsLexicalCaller = new YjsLexicalCaller();
770 
771             yjsLexicalCaller.sendDuplicationRequest(
772                     srcOwnerId, srcOwnerKind, destOwnerId, destOwnerKind);
773         } catch (YjsException e) {
774             throw HttpErrorMessage.duplicationError();
775         }
776     }
777 
778     /**
779      * Duplicate the file document data into JCR
780      *
781      * @param srcDocId
782      * @param newDocFile
783      */
784     private void duplicateFileDocumentIntoJCR(Long srcDocId, DocumentFile newDocFile)
785             throws RepositoryException {
786         if (fileManager == null) {
787             throw new IllegalStateException("Dear developer, you must have defined a file manager");
788         }
789 
790         if (fileManager.hasFile(srcDocId)) {
791             InputStream fileStream = null;
792             try {
793                 fileStream = fileManager.getFileStream(srcDocId);
794                 fileManager.updateOrCreateFile(newDocFile.getId(), fileStream);
795             } finally {
796                 if (fileStream != null) {
797                     try {
798                         fileStream.close();
799                     } catch (IOException e) {
800                         // not really a problem
801                         // silent ex
802                         logger.warn("Could not close stream", e);
803                     }
804                 }
805             }
806         }
807     }
808 
809     /**
810      * Clear processed data
811      */
812     public void clear() {
813         teamRoleMatching.clear();
814         teamMemberMatching.clear();
815         cardTypeMatching.clear();
816         cardMatching.clear();
817         cardContentMatching.clear();
818         resourceMatching.clear();
819         documentMatching.clear();
820         stickyNoteLinksToDuplicate.clear();
821         activityFlowLinksToDuplicate.clear();
822         documentFilesToProcessOnceIds.clear();
823     }
824 
825 }