ResourceReferenceSpreadingHelper.java

  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.document;

  8. import ch.colabproject.colab.api.controller.RequestManager;
  9. import ch.colabproject.colab.api.model.card.AbstractCardType;
  10. import ch.colabproject.colab.api.model.card.Card;
  11. import ch.colabproject.colab.api.model.card.CardContent;
  12. import ch.colabproject.colab.api.model.card.CardTypeRef;
  13. import ch.colabproject.colab.api.model.document.AbstractResource;
  14. import ch.colabproject.colab.api.model.document.Resource;
  15. import ch.colabproject.colab.api.model.document.ResourceRef;
  16. import ch.colabproject.colab.api.model.document.Resourceable;
  17. import ch.colabproject.colab.api.persistence.jpa.card.CardTypeDao;
  18. import ch.colabproject.colab.api.persistence.jpa.document.ResourceDao;
  19. import ch.colabproject.colab.generator.model.exceptions.HttpErrorMessage;
  20. import ch.colabproject.colab.generator.model.exceptions.MessageI18nKey;
  21. import java.util.ArrayList;
  22. import java.util.List;
  23. import java.util.Objects;
  24. import java.util.stream.Collectors;
  25. import javax.inject.Inject;

  26. /**
  27.  * Resource and resource reference spread specific logic
  28.  *
  29.  * @author sandra
  30.  */
  31. public class ResourceReferenceSpreadingHelper {

  32.     // *********************************************************************************************
  33.     // injections
  34.     // *********************************************************************************************

  35.     /**
  36.      * Resource persistence handler
  37.      */
  38.     @Inject
  39.     private ResourceDao resourceDao;

  40.     /**
  41.      * Card type persistence handler
  42.      */
  43.     @Inject
  44.     private CardTypeDao cardTypeDao;

  45.     /**
  46.      * TO sudo
  47.      */
  48.     @Inject
  49.     private RequestManager requestManager;

  50.     // *********************************************************************************************
  51.     // when a resource / resource reference is added, spread it down stream with references
  52.     // *********************************************************************************************

  53.     /**
  54.      * Each child of the resource owner acquires a reference to the resource.
  55.      *
  56.      * @param resourceOrRef the resource to reference
  57.      */
  58.     public void spreadAvailableResourceDown(AbstractResource resourceOrRef) {
  59.         if (resourceOrRef.getAbstractCardType() != null) {
  60.             requestManager.sudo(() -> {
  61.                 AbstractCardType resourceOwner = resourceOrRef.getAbstractCardType();

  62.                 for (AbstractCardType cardTypeRef : cardTypeDao
  63.                     .findDirectReferences(resourceOwner)) {
  64.                     makeActiveReference(cardTypeRef, resourceOrRef);
  65.                 }

  66.                 for (Card implementingCard : resourceOwner.getImplementingCards()) {
  67.                     makeActiveReference(implementingCard, resourceOrRef);
  68.                 }
  69.             });
  70.         }

  71.         if (resourceOrRef.getCard() != null) {
  72.             requestManager.sudo(() -> {
  73.                 Card resourceOwner = resourceOrRef.getCard();

  74.                 for (CardContent variant : resourceOwner.getContentVariants()) {
  75.                     makeActiveReference(variant, resourceOrRef);
  76.                 }
  77.             });
  78.         }

  79.         if (resourceOrRef.getCardContent() != null) {
  80.             requestManager.sudo(() -> {
  81.                 CardContent resourceOwner = resourceOrRef.getCardContent();

  82.                 for (Card subCard : resourceOwner.getSubCards()) {
  83.                     makeActiveReference(subCard, resourceOrRef);
  84.                 }
  85.             });
  86.         }
  87.     }

  88.     // *********************************************************************************************
  89.     // when a card type reference / card / card content is added,
  90.     // initialize the references from parent's resources / resource references
  91.     // *********************************************************************************************

  92.     /**
  93.      * Create a resource reference for each resource / resource reference of the parent.
  94.      *
  95.      * @param cardTypeRefToFill A card type reference that need references to the up stream
  96.      *                          resources
  97.      *
  98.      * @return the resource references that have been created or revived (or let as it is if nothing
  99.      *         is needed)
  100.      */
  101.     public List<ResourceRef> extractReferencesFromUp(CardTypeRef cardTypeRefToFill) {
  102.         AbstractCardType targetType = cardTypeRefToFill.getTarget();
  103.         List<ResourceRef> refs = new ArrayList<>();

  104.         for (AbstractResource targetResourceOrRef : targetType.getDirectAbstractResources()) {
  105.             ResourceRef ref = makeActiveReference(cardTypeRefToFill, targetResourceOrRef);
  106.             if (ref != null) {
  107.                 refs.add(ref);
  108.             }
  109.         }

  110.         return refs;
  111.     }

  112.     /**
  113.      * Create a resource reference for each resource / resource reference of the parent.
  114.      *
  115.      * @param cardToFill A card that need references to the up stream resources
  116.      */
  117.     public void extractReferencesFromUp(Card cardToFill) {
  118.         CardContent parent = cardToFill.getParent();

  119.         for (AbstractResource parentResourceOrRef : parent.getDirectAbstractResources()) {
  120.             makeActiveReference(cardToFill, parentResourceOrRef);
  121.         }

  122.         if (cardToFill.hasCardType()) {
  123.             AbstractCardType type = cardToFill.getCardType();

  124.             for (AbstractResource typeResourceOrRef : type.getDirectAbstractResources()) {
  125.                 makeActiveReference(cardToFill, typeResourceOrRef);
  126.             }
  127.         }
  128.     }

  129.     /**
  130.      * Create a resource reference for each resource / resource reference of the parent.
  131.      *
  132.      * @param cardContentToFill A card content that need references to the up stream resources
  133.      */
  134.     public void extractReferencesFromUp(CardContent cardContentToFill) {
  135.         Card parent = cardContentToFill.getCard();

  136.         for (AbstractResource parentResourceOrRef : parent.getDirectAbstractResources()) {
  137.             makeActiveReference(cardContentToFill, parentResourceOrRef);
  138.         }
  139.     }

  140.     // *********************************************************************************************
  141.     // do it
  142.     // *********************************************************************************************

  143.     /**
  144.      * If the given target resource (or reference) can have references, ensure that there is an
  145.      * active reference to the given target resource (or reference) for the given owner.
  146.      * <p>
  147.      * For that either be sure the already existing reference for the owner and targeting the same
  148.      * final resource is active or make a new reference.
  149.      *
  150.      * @param owner               the owner of the wanted reference
  151.      * @param targetResourceOrRef the target of the wanted reference
  152.      *
  153.      * @return the resource reference that has been created or revived (or let as it is if nothing
  154.      *         is needed)
  155.      */
  156.     private ResourceRef makeActiveReference(Resourceable owner,
  157.         AbstractResource targetResourceOrRef) {

  158.         if (mustHaveReferences(targetResourceOrRef)) {

  159.             ResourceRef existingMatchingReference = findMatchingResourceRef(owner,
  160.                 targetResourceOrRef);

  161.             if (existingMatchingReference != null) {
  162.                 ResourceRef aimedResourceRef = reviveAndRetarget(existingMatchingReference,
  163.                     targetResourceOrRef);

  164.                 spreadAvailableResourceDown(aimedResourceRef);

  165.                 return aimedResourceRef;

  166.             } else {
  167.                 ResourceRef aimedResourceRef = initNewReference(owner, targetResourceOrRef);

  168.                 spreadAvailableResourceDown(aimedResourceRef);

  169.                 return aimedResourceRef;
  170.             }
  171.         }

  172.         return null;
  173.     }

  174.     /**
  175.      * Ascertain if there must be resource references down stream for the given target resource (or
  176.      * reference).
  177.      *
  178.      * @param targetResourceOrRef Resource / resource reference
  179.      *
  180.      * @return true iff the resource can have references
  181.      */
  182.     private boolean mustHaveReferences(AbstractResource targetResourceOrRef) {
  183.         Resource concreteTargetResource = targetResourceOrRef.resolve();

  184.         // do not spread unpublished resource
  185.         if (concreteTargetResource != null && !concreteTargetResource.isPublished()) {
  186.             // unless between a card and its card contents
  187.             boolean isResourceLinkedToACard = (targetResourceOrRef instanceof Resource)
  188.                 && targetResourceOrRef.getCard() != null;
  189.             if (!isResourceLinkedToACard) {
  190.                 return false;
  191.             }
  192.         }

  193.         // do not spread references of a type from a card content to its sub cards
  194.         boolean isResourceOrRefLinkedToACardContent = targetResourceOrRef.getCardContent() != null;
  195.         if (isResourceOrRefLinkedToACardContent) {

  196.             boolean isConcreteResourceLinkedToACardType = concreteTargetResource != null
  197.                 && concreteTargetResource.getAbstractCardType() != null;

  198.             return !isConcreteResourceLinkedToACardType;
  199.         }

  200.         // in all other cases, spread
  201.         return true;
  202.     }

  203.     /**
  204.      * Search for an existing resource reference owned by the given owner and targeting the same
  205.      * final resource as the given target.
  206.      * <p>
  207.      * It ensures that the matching reference is unique.
  208.      *
  209.      * @param owner               the owner of the wanted reference
  210.      * @param targetResourceOrRef the target of the wanted reference
  211.      *
  212.      * @return the matching resource reference
  213.      */
  214.     private ResourceRef findMatchingResourceRef(Resourceable owner,
  215.         AbstractResource targetResourceOrRef) {
  216.         List<ResourceRef> refsOfOwnerWithSameFinalTarget = owner.getDirectAbstractResources()
  217.             .stream()
  218.             .filter(resOrRef -> resOrRef instanceof ResourceRef)
  219.             .map(resOrRef -> (ResourceRef) resOrRef)
  220.             .filter(ref -> Objects.equals(ref.resolve(), targetResourceOrRef.resolve()))
  221.             .collect(Collectors.toList());

  222.         if (refsOfOwnerWithSameFinalTarget.size() == 1) {
  223.             return refsOfOwnerWithSameFinalTarget.get(0);
  224.         }

  225.         if (refsOfOwnerWithSameFinalTarget.size() > 1) {
  226.             throw HttpErrorMessage.dataError(MessageI18nKey.DATA_INTEGRITY_FAILURE);
  227.         }

  228.         return null;
  229.     }

  230.     /**
  231.      * Update the given resource reference so that it is not set as residual, make the same for its
  232.      * descendants. Make the given resource reference target the given target.
  233.      * <p>
  234.      * It can be done only if the resource reference and the new target have the same final concrete
  235.      * resource.
  236.      *
  237.      * @param resourceReference the resource reference to update
  238.      * @param newDirectTarget   the new target of the resource reference
  239.      *
  240.      * @return the resource reference that has been revived (or let as it is if nothing is needed)
  241.      */
  242.     private ResourceRef reviveAndRetarget(ResourceRef resourceReference,
  243.         AbstractResource newDirectTarget) {
  244.         if (!Objects.equals(resourceReference.resolve(), newDirectTarget.resolve())) {
  245.             throw HttpErrorMessage.dataError(MessageI18nKey.DATA_INTEGRITY_FAILURE);
  246.         }

  247.         // revive
  248.         resourceReference.setResidual(false);

  249.         // retarget
  250.         AbstractResource olderTarget = resourceReference.getTarget();

  251.         if (!Objects.equals(olderTarget, newDirectTarget)) {
  252.             resourceReference.setTarget(newDirectTarget);
  253.         }

  254.         return resourceReference;
  255.     }

  256.     /**
  257.      * Make a new resource reference to link to the given owner, targeting the given resource (or
  258.      * reference).
  259.      *
  260.      * @param owner         the entity the new resource reference will be linked to
  261.      * @param resourceOrRef the resource (or reference) target of the new resource reference
  262.      *
  263.      * @return the resource reference that has been created
  264.      */
  265.     private ResourceRef initNewReference(Resourceable owner, AbstractResource targetResourceOrRef) {
  266.         ResourceRef newRef = initNewReferenceFrom(targetResourceOrRef);

  267.         newRef.setOwner(owner);
  268.         owner.getDirectAbstractResources().add(newRef);

  269.         newRef.setTarget(targetResourceOrRef);

  270.         return newRef;

  271.         // no need to persist, it will be done all at once
  272.     }

  273.     /**
  274.      * Initialize a new reference which will have the given resource (or reference) as target.
  275.      *
  276.      * @param targetResourceOrRef The target of the new resource reference
  277.      *
  278.      * @return the new resource reference
  279.      */
  280.     private ResourceRef initNewReferenceFrom(AbstractResource targetResourceOrRef) {
  281.         ResourceRef newRef = new ResourceRef();

  282.         newRef.setCategory(targetResourceOrRef.getCategory());

  283.         if (targetResourceOrRef instanceof ResourceRef) {
  284.             ResourceRef targetResourceRef = (ResourceRef) targetResourceOrRef;

  285.             newRef.setRefused(targetResourceRef.isRefused());
  286.             newRef.setResidual(targetResourceRef.isResidual());
  287.         }

  288.         return newRef;
  289.     }

  290.     // *********************************************************************************************
  291.     // when something is moved, mark the former ancestors resource references as residual
  292.     // when something is un-published, mark the resource references as residual
  293.     // *********************************************************************************************

  294.     /**
  295.      * Each resource reference of the given owner that is linked to a resource of the given former
  296.      * related is marked as residual. As well as all its descendants.
  297.      *
  298.      * @param owner         the owner of the resource references to mark
  299.      * @param formerRelated the not-any-more-related target of the references
  300.      */
  301.     public void spreadDisableResourceDown(Resourceable owner, Resourceable formerRelated) {
  302.         owner.getDirectAbstractResources().stream()
  303.             .filter(resOrRef -> (resOrRef instanceof ResourceRef))
  304.             .map(resOrRef -> ((ResourceRef) resOrRef))
  305.             .filter(ref -> Objects.equals(ref.getTarget().getOwner(), formerRelated))
  306.             .forEach(ref -> markAsResidualRecursively(ref, true));
  307.     }

  308.     /**
  309.      * Disable = mark as residual.
  310.      * <p>
  311.      * Mark the resource's references as residual. Do it as well for all descendants.
  312.      *
  313.      * @param resource the resource
  314.      */
  315.     public void spreadDisableResourceDown(Resource resource) {
  316.         requestManager.sudo(() -> {
  317.             for (ResourceRef childRef : resourceDao.findDirectReferences(resource)) {
  318.                 markAsResidualRecursively(childRef, false);
  319.             }
  320.         });
  321.     }

  322.     /**
  323.      * Disable = mark as residual.
  324.      * <p>
  325.      * Mark the resource's references as residual. Do it as well for all descendants.
  326.      *
  327.      * @param resource   the resource
  328.      * @param alwaysMark if the reference must for sure be marked as residual
  329.      */
  330.     public void spreadDisableResourceDown(Resource resource, boolean alwaysMark) {
  331.         requestManager.sudo(() -> {
  332.             for (ResourceRef childRef : resourceDao.findDirectReferences(resource)) {
  333.                 markAsResidualRecursively(childRef, alwaysMark);
  334.             }
  335.         });
  336.     }

  337.     /**
  338.      * Mark the resource reference as residual. Do it as well for all its descendants.
  339.      *
  340.      * @param resourceReference the reference to update
  341.      * @param alwaysMark        if the reference must for sure be marked as residual
  342.      */
  343.     private void markAsResidualRecursively(ResourceRef resourceReference, boolean alwaysMark) {
  344.         if (alwaysMark || resourceReference.getTarget() == null
  345.             || !mustHaveReferences(resourceReference.getTarget())) {
  346.             resourceReference.setResidual(true);
  347.         }

  348.         requestManager.sudo(() -> {
  349.             for (ResourceRef childRef : resourceDao.findDirectReferences(resourceReference)) {
  350.                 markAsResidualRecursively(childRef, alwaysMark);
  351.             }
  352.         });
  353.     }

  354.     // *********************************************************************************************
  355.     //
  356.     // *********************************************************************************************

  357.     /**
  358.      * Mark the resource reference as refused. Do it as well for all its descendants.
  359.      *
  360.      * @param resourceReference the reference to update
  361.      */
  362.     public void refuseRecursively(ResourceRef resourceReference) {
  363.         resourceReference.setRefused(true);

  364.         requestManager.sudo(() -> {
  365.             for (ResourceRef childRef : resourceDao.findDirectReferences(resourceReference)) {
  366.                 refuseRecursively(childRef);
  367.             }
  368.         });
  369.     }

  370.     /**
  371.      * Mark the resource reference as not refused. Do it as well for all its descendants.
  372.      *
  373.      * @param resourceReference the reference to update
  374.      */
  375.     public void unRefuseRecursively(ResourceRef resourceReference) {
  376.         resourceReference.setRefused(false);

  377.         requestManager.sudo(() -> {
  378.             for (ResourceRef childRef : resourceDao.findDirectReferences(resourceReference)) {
  379.                 unRefuseRecursively(childRef);
  380.             }
  381.         });
  382.     }

  383.     /**
  384.      * Mark the resource reference as not residual. Do it as well for all its descendants.
  385.      *
  386.      * @param resourceReference the reference to update
  387.      */
  388.     public void reviveRecursively(ResourceRef resourceReference) {
  389.         if (resourceReference.getTarget() != null
  390.             && mustHaveReferences(resourceReference.getTarget())) {
  391.             resourceReference.setResidual(false);
  392.         }

  393.         requestManager.sudo(() -> {
  394.             for (ResourceRef childRef : resourceDao.findDirectReferences(resourceReference)) {
  395.                 reviveRecursively(childRef);
  396.             }
  397.         });
  398.     }

  399.     // *********************************************************************************************
  400.     //
  401.     // *********************************************************************************************

  402. }