ResourceReferenceSpreadingHelper.java
- /*
- * The coLAB project
- * Copyright (C) 2021-2023 AlbaSim, MEI, HEIG-VD, HES-SO
- *
- * Licensed under the MIT License
- */
- package ch.colabproject.colab.api.controller.document;
- import ch.colabproject.colab.api.controller.RequestManager;
- import ch.colabproject.colab.api.model.card.AbstractCardType;
- import ch.colabproject.colab.api.model.card.Card;
- import ch.colabproject.colab.api.model.card.CardContent;
- import ch.colabproject.colab.api.model.card.CardTypeRef;
- import ch.colabproject.colab.api.model.document.AbstractResource;
- import ch.colabproject.colab.api.model.document.Resource;
- import ch.colabproject.colab.api.model.document.ResourceRef;
- import ch.colabproject.colab.api.model.document.Resourceable;
- import ch.colabproject.colab.api.persistence.jpa.card.CardTypeDao;
- import ch.colabproject.colab.api.persistence.jpa.document.ResourceDao;
- import ch.colabproject.colab.generator.model.exceptions.HttpErrorMessage;
- import ch.colabproject.colab.generator.model.exceptions.MessageI18nKey;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.Objects;
- import java.util.stream.Collectors;
- import javax.inject.Inject;
- /**
- * Resource and resource reference spread specific logic
- *
- * @author sandra
- */
- public class ResourceReferenceSpreadingHelper {
- // *********************************************************************************************
- // injections
- // *********************************************************************************************
- /**
- * Resource persistence handler
- */
- @Inject
- private ResourceDao resourceDao;
- /**
- * Card type persistence handler
- */
- @Inject
- private CardTypeDao cardTypeDao;
- /**
- * TO sudo
- */
- @Inject
- private RequestManager requestManager;
- // *********************************************************************************************
- // when a resource / resource reference is added, spread it down stream with references
- // *********************************************************************************************
- /**
- * Each child of the resource owner acquires a reference to the resource.
- *
- * @param resourceOrRef the resource to reference
- */
- public void spreadAvailableResourceDown(AbstractResource resourceOrRef) {
- if (resourceOrRef.getAbstractCardType() != null) {
- requestManager.sudo(() -> {
- AbstractCardType resourceOwner = resourceOrRef.getAbstractCardType();
- for (AbstractCardType cardTypeRef : cardTypeDao
- .findDirectReferences(resourceOwner)) {
- makeActiveReference(cardTypeRef, resourceOrRef);
- }
- for (Card implementingCard : resourceOwner.getImplementingCards()) {
- makeActiveReference(implementingCard, resourceOrRef);
- }
- });
- }
- if (resourceOrRef.getCard() != null) {
- requestManager.sudo(() -> {
- Card resourceOwner = resourceOrRef.getCard();
- for (CardContent variant : resourceOwner.getContentVariants()) {
- makeActiveReference(variant, resourceOrRef);
- }
- });
- }
- if (resourceOrRef.getCardContent() != null) {
- requestManager.sudo(() -> {
- CardContent resourceOwner = resourceOrRef.getCardContent();
- for (Card subCard : resourceOwner.getSubCards()) {
- makeActiveReference(subCard, resourceOrRef);
- }
- });
- }
- }
- // *********************************************************************************************
- // when a card type reference / card / card content is added,
- // initialize the references from parent's resources / resource references
- // *********************************************************************************************
- /**
- * Create a resource reference for each resource / resource reference of the parent.
- *
- * @param cardTypeRefToFill A card type reference that need references to the up stream
- * resources
- *
- * @return the resource references that have been created or revived (or let as it is if nothing
- * is needed)
- */
- public List<ResourceRef> extractReferencesFromUp(CardTypeRef cardTypeRefToFill) {
- AbstractCardType targetType = cardTypeRefToFill.getTarget();
- List<ResourceRef> refs = new ArrayList<>();
- for (AbstractResource targetResourceOrRef : targetType.getDirectAbstractResources()) {
- ResourceRef ref = makeActiveReference(cardTypeRefToFill, targetResourceOrRef);
- if (ref != null) {
- refs.add(ref);
- }
- }
- return refs;
- }
- /**
- * Create a resource reference for each resource / resource reference of the parent.
- *
- * @param cardToFill A card that need references to the up stream resources
- */
- public void extractReferencesFromUp(Card cardToFill) {
- CardContent parent = cardToFill.getParent();
- for (AbstractResource parentResourceOrRef : parent.getDirectAbstractResources()) {
- makeActiveReference(cardToFill, parentResourceOrRef);
- }
- if (cardToFill.hasCardType()) {
- AbstractCardType type = cardToFill.getCardType();
- for (AbstractResource typeResourceOrRef : type.getDirectAbstractResources()) {
- makeActiveReference(cardToFill, typeResourceOrRef);
- }
- }
- }
- /**
- * Create a resource reference for each resource / resource reference of the parent.
- *
- * @param cardContentToFill A card content that need references to the up stream resources
- */
- public void extractReferencesFromUp(CardContent cardContentToFill) {
- Card parent = cardContentToFill.getCard();
- for (AbstractResource parentResourceOrRef : parent.getDirectAbstractResources()) {
- makeActiveReference(cardContentToFill, parentResourceOrRef);
- }
- }
- // *********************************************************************************************
- // do it
- // *********************************************************************************************
- /**
- * If the given target resource (or reference) can have references, ensure that there is an
- * active reference to the given target resource (or reference) for the given owner.
- * <p>
- * For that either be sure the already existing reference for the owner and targeting the same
- * final resource is active or make a new reference.
- *
- * @param owner the owner of the wanted reference
- * @param targetResourceOrRef the target of the wanted reference
- *
- * @return the resource reference that has been created or revived (or let as it is if nothing
- * is needed)
- */
- private ResourceRef makeActiveReference(Resourceable owner,
- AbstractResource targetResourceOrRef) {
- if (mustHaveReferences(targetResourceOrRef)) {
- ResourceRef existingMatchingReference = findMatchingResourceRef(owner,
- targetResourceOrRef);
- if (existingMatchingReference != null) {
- ResourceRef aimedResourceRef = reviveAndRetarget(existingMatchingReference,
- targetResourceOrRef);
- spreadAvailableResourceDown(aimedResourceRef);
- return aimedResourceRef;
- } else {
- ResourceRef aimedResourceRef = initNewReference(owner, targetResourceOrRef);
- spreadAvailableResourceDown(aimedResourceRef);
- return aimedResourceRef;
- }
- }
- return null;
- }
- /**
- * Ascertain if there must be resource references down stream for the given target resource (or
- * reference).
- *
- * @param targetResourceOrRef Resource / resource reference
- *
- * @return true iff the resource can have references
- */
- private boolean mustHaveReferences(AbstractResource targetResourceOrRef) {
- Resource concreteTargetResource = targetResourceOrRef.resolve();
- // do not spread unpublished resource
- if (concreteTargetResource != null && !concreteTargetResource.isPublished()) {
- // unless between a card and its card contents
- boolean isResourceLinkedToACard = (targetResourceOrRef instanceof Resource)
- && targetResourceOrRef.getCard() != null;
- if (!isResourceLinkedToACard) {
- return false;
- }
- }
- // do not spread references of a type from a card content to its sub cards
- boolean isResourceOrRefLinkedToACardContent = targetResourceOrRef.getCardContent() != null;
- if (isResourceOrRefLinkedToACardContent) {
- boolean isConcreteResourceLinkedToACardType = concreteTargetResource != null
- && concreteTargetResource.getAbstractCardType() != null;
- return !isConcreteResourceLinkedToACardType;
- }
- // in all other cases, spread
- return true;
- }
- /**
- * Search for an existing resource reference owned by the given owner and targeting the same
- * final resource as the given target.
- * <p>
- * It ensures that the matching reference is unique.
- *
- * @param owner the owner of the wanted reference
- * @param targetResourceOrRef the target of the wanted reference
- *
- * @return the matching resource reference
- */
- private ResourceRef findMatchingResourceRef(Resourceable owner,
- AbstractResource targetResourceOrRef) {
- List<ResourceRef> refsOfOwnerWithSameFinalTarget = owner.getDirectAbstractResources()
- .stream()
- .filter(resOrRef -> resOrRef instanceof ResourceRef)
- .map(resOrRef -> (ResourceRef) resOrRef)
- .filter(ref -> Objects.equals(ref.resolve(), targetResourceOrRef.resolve()))
- .collect(Collectors.toList());
- if (refsOfOwnerWithSameFinalTarget.size() == 1) {
- return refsOfOwnerWithSameFinalTarget.get(0);
- }
- if (refsOfOwnerWithSameFinalTarget.size() > 1) {
- throw HttpErrorMessage.dataError(MessageI18nKey.DATA_INTEGRITY_FAILURE);
- }
- return null;
- }
- /**
- * Update the given resource reference so that it is not set as residual, make the same for its
- * descendants. Make the given resource reference target the given target.
- * <p>
- * It can be done only if the resource reference and the new target have the same final concrete
- * resource.
- *
- * @param resourceReference the resource reference to update
- * @param newDirectTarget the new target of the resource reference
- *
- * @return the resource reference that has been revived (or let as it is if nothing is needed)
- */
- private ResourceRef reviveAndRetarget(ResourceRef resourceReference,
- AbstractResource newDirectTarget) {
- if (!Objects.equals(resourceReference.resolve(), newDirectTarget.resolve())) {
- throw HttpErrorMessage.dataError(MessageI18nKey.DATA_INTEGRITY_FAILURE);
- }
- // revive
- resourceReference.setResidual(false);
- // retarget
- AbstractResource olderTarget = resourceReference.getTarget();
- if (!Objects.equals(olderTarget, newDirectTarget)) {
- resourceReference.setTarget(newDirectTarget);
- }
- return resourceReference;
- }
- /**
- * Make a new resource reference to link to the given owner, targeting the given resource (or
- * reference).
- *
- * @param owner the entity the new resource reference will be linked to
- * @param resourceOrRef the resource (or reference) target of the new resource reference
- *
- * @return the resource reference that has been created
- */
- private ResourceRef initNewReference(Resourceable owner, AbstractResource targetResourceOrRef) {
- ResourceRef newRef = initNewReferenceFrom(targetResourceOrRef);
- newRef.setOwner(owner);
- owner.getDirectAbstractResources().add(newRef);
- newRef.setTarget(targetResourceOrRef);
- return newRef;
- // no need to persist, it will be done all at once
- }
- /**
- * Initialize a new reference which will have the given resource (or reference) as target.
- *
- * @param targetResourceOrRef The target of the new resource reference
- *
- * @return the new resource reference
- */
- private ResourceRef initNewReferenceFrom(AbstractResource targetResourceOrRef) {
- ResourceRef newRef = new ResourceRef();
- newRef.setCategory(targetResourceOrRef.getCategory());
- if (targetResourceOrRef instanceof ResourceRef) {
- ResourceRef targetResourceRef = (ResourceRef) targetResourceOrRef;
- newRef.setRefused(targetResourceRef.isRefused());
- newRef.setResidual(targetResourceRef.isResidual());
- }
- return newRef;
- }
- // *********************************************************************************************
- // when something is moved, mark the former ancestors resource references as residual
- // when something is un-published, mark the resource references as residual
- // *********************************************************************************************
- /**
- * Each resource reference of the given owner that is linked to a resource of the given former
- * related is marked as residual. As well as all its descendants.
- *
- * @param owner the owner of the resource references to mark
- * @param formerRelated the not-any-more-related target of the references
- */
- public void spreadDisableResourceDown(Resourceable owner, Resourceable formerRelated) {
- owner.getDirectAbstractResources().stream()
- .filter(resOrRef -> (resOrRef instanceof ResourceRef))
- .map(resOrRef -> ((ResourceRef) resOrRef))
- .filter(ref -> Objects.equals(ref.getTarget().getOwner(), formerRelated))
- .forEach(ref -> markAsResidualRecursively(ref, true));
- }
- /**
- * Disable = mark as residual.
- * <p>
- * Mark the resource's references as residual. Do it as well for all descendants.
- *
- * @param resource the resource
- */
- public void spreadDisableResourceDown(Resource resource) {
- requestManager.sudo(() -> {
- for (ResourceRef childRef : resourceDao.findDirectReferences(resource)) {
- markAsResidualRecursively(childRef, false);
- }
- });
- }
- /**
- * Disable = mark as residual.
- * <p>
- * Mark the resource's references as residual. Do it as well for all descendants.
- *
- * @param resource the resource
- * @param alwaysMark if the reference must for sure be marked as residual
- */
- public void spreadDisableResourceDown(Resource resource, boolean alwaysMark) {
- requestManager.sudo(() -> {
- for (ResourceRef childRef : resourceDao.findDirectReferences(resource)) {
- markAsResidualRecursively(childRef, alwaysMark);
- }
- });
- }
- /**
- * Mark the resource reference as residual. Do it as well for all its descendants.
- *
- * @param resourceReference the reference to update
- * @param alwaysMark if the reference must for sure be marked as residual
- */
- private void markAsResidualRecursively(ResourceRef resourceReference, boolean alwaysMark) {
- if (alwaysMark || resourceReference.getTarget() == null
- || !mustHaveReferences(resourceReference.getTarget())) {
- resourceReference.setResidual(true);
- }
- requestManager.sudo(() -> {
- for (ResourceRef childRef : resourceDao.findDirectReferences(resourceReference)) {
- markAsResidualRecursively(childRef, alwaysMark);
- }
- });
- }
- // *********************************************************************************************
- //
- // *********************************************************************************************
- /**
- * Mark the resource reference as refused. Do it as well for all its descendants.
- *
- * @param resourceReference the reference to update
- */
- public void refuseRecursively(ResourceRef resourceReference) {
- resourceReference.setRefused(true);
- requestManager.sudo(() -> {
- for (ResourceRef childRef : resourceDao.findDirectReferences(resourceReference)) {
- refuseRecursively(childRef);
- }
- });
- }
- /**
- * Mark the resource reference as not refused. Do it as well for all its descendants.
- *
- * @param resourceReference the reference to update
- */
- public void unRefuseRecursively(ResourceRef resourceReference) {
- resourceReference.setRefused(false);
- requestManager.sudo(() -> {
- for (ResourceRef childRef : resourceDao.findDirectReferences(resourceReference)) {
- unRefuseRecursively(childRef);
- }
- });
- }
- /**
- * Mark the resource reference as not residual. Do it as well for all its descendants.
- *
- * @param resourceReference the reference to update
- */
- public void reviveRecursively(ResourceRef resourceReference) {
- if (resourceReference.getTarget() != null
- && mustHaveReferences(resourceReference.getTarget())) {
- resourceReference.setResidual(false);
- }
- requestManager.sudo(() -> {
- for (ResourceRef childRef : resourceDao.findDirectReferences(resourceReference)) {
- reviveRecursively(childRef);
- }
- });
- }
- // *********************************************************************************************
- //
- // *********************************************************************************************
- }