Document.java

/*
 * The coLAB project
 * Copyright (C) 2021-2023 AlbaSim, MEI, HEIG-VD, HES-SO
 *
 * Licensed under the MIT License
 */
package ch.colabproject.colab.api.model.document;

import ch.colabproject.colab.api.exceptions.ColabMergeException;
import ch.colabproject.colab.api.model.ColabEntity;
import ch.colabproject.colab.api.model.WithIndex;
import ch.colabproject.colab.api.model.WithWebsocketChannels;
import ch.colabproject.colab.api.model.card.CardContent;
import ch.colabproject.colab.api.model.common.DeletionStatus;
import ch.colabproject.colab.api.model.common.Tracking;
import ch.colabproject.colab.api.model.link.StickyNoteLink;
import ch.colabproject.colab.api.model.link.StickyNoteSourceable;
import ch.colabproject.colab.api.model.project.Project;
import ch.colabproject.colab.api.model.tools.EntityHelper;
import ch.colabproject.colab.api.security.permissions.Conditions;
import ch.colabproject.colab.api.ws.channel.tool.ChannelsBuilders.ChannelsBuilder;
import ch.colabproject.colab.api.ws.channel.tool.ChannelsBuilders.EmptyChannelBuilder;
import ch.colabproject.colab.generator.model.tools.PolymorphicDeserializer;
import java.util.ArrayList;
import java.util.List;
import javax.json.bind.annotation.JsonbTransient;
import javax.json.bind.annotation.JsonbTypeDeserializer;
import javax.persistence.CascadeType;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Transient;

/**
 * Any document.
 * <p>
 * The subclass handles the content.
 * <p>
 * A document is owned by either a card content or a resource.
 *
 * @author sandra
 */
@Entity
@Table(
    indexes = {
        @Index(columnList = "owningCardContent_id"),
        @Index(columnList = "owningResource_id"),
    }
)
@Inheritance(strategy = InheritanceType.JOINED)
@JsonbTypeDeserializer(PolymorphicDeserializer.class)
public abstract class Document
    implements ColabEntity, WithWebsocketChannels, WithIndex, StickyNoteSourceable {

    private static final long serialVersionUID = 1L;

    /** Name of the document/resource sequence */
    public static final String DOCUMENT_SEQUENCE_NAME = "document_seq";

    // ---------------------------------------------------------------------------------------------
    // fields
    // ---------------------------------------------------------------------------------------------
    /**
     * Document ID
     */
    @Id
    @SequenceGenerator(name = DOCUMENT_SEQUENCE_NAME, allocationSize = 20)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = DOCUMENT_SEQUENCE_NAME)
    protected Long id;

    /**
     * creation + modification + erasure tracking data
     */
    @Embedded
    private Tracking trackingData;

    /**
     * Is it in a bin or ready to be definitely deleted. Null means active.
     */
    @Enumerated(EnumType.STRING)
    private DeletionStatus deletionStatus;

    /**
     * The index to define the place of the document
     */
    private int index;

    /**
     * The card content for which this document is a deliverable
     */
    @ManyToOne(fetch = FetchType.LAZY)
    @JsonbTransient
    protected CardContent owningCardContent;

    /**
     * The id of the card content for which this document is a deliverable
     */
    @Transient
    private Long owningCardContentId;

    /**
     * The resource this document is part of
     */
    @ManyToOne(fetch = FetchType.LAZY)
    @JsonbTransient
    protected Resource owningResource;

    /**
     * The id of the resource this document is part of
     */
    @Transient
    private Long owningResourceId;

    /**
     * The list of sticky note links of which the document is the source
     */
    @OneToMany(mappedBy = "srcDocument", cascade = CascadeType.ALL)
    @JsonbTransient
    private List<StickyNoteLink> stickyNoteLinksAsSrc = new ArrayList<>();

    // ---------------------------------------------------------------------------------------------
    // getters and setters
    // ---------------------------------------------------------------------------------------------
    /**
     * @return the document id
     */
    @Override
    public Long getId() {
        return id;
    }

    /**
     * @param id the document id
     */
    public void setId(Long id) {
        this.id = id;
    }

    /**
     * Get the tracking data
     *
     * @return tracking data
     */
    @Override
    public Tracking getTrackingData() {
        return trackingData;
    }

    /**
     * Set tracking data
     *
     * @param trackingData new tracking data
     */
    @Override
    public void setTrackingData(Tracking trackingData) {
        this.trackingData = trackingData;
    }

    @Override
    public DeletionStatus getDeletionStatus() {
        return deletionStatus;
    }

    @Override
    public void setDeletionStatus(DeletionStatus status) {
        this.deletionStatus = status;
    }

    /**
     * @return the index to define the place of the document
     */
    @Override
    public int getIndex() {
        return index;
    }

    /**
     * @param index the index to define the place of the document to set
     */
    @Override
    public void setIndex(int index) {
        this.index = index;
    }

    /**
     * @return the card content for which this document is a deliverable
     */
    public CardContent getOwningCardContent() {
        return owningCardContent;
    }

    /**
     * @param cardContent the card content for which this document is a deliverable
     */
    public void setOwningCardContent(CardContent cardContent) {
        this.owningCardContent = cardContent;
    }

    /**
     * @return the id of the card content for which this document is a deliverable
     */
    public Long getOwningCardContentId() {
        if (this.owningCardContent != null) {
            return this.owningCardContent.getId();
        } else {
            return owningCardContentId;
        }
    }

    /**
     * @param cardContentId the id of the card content for which this document is a deliverable
     */
    public void setOwningCardContentId(Long cardContentId) {
        this.owningCardContentId = cardContentId;
    }

    /**
     * @return True if there is an owning card content
     */
    public boolean hasOwningCardContent() {
        return this.owningCardContent != null || this.owningCardContentId != null;
    }

    /**
     * @return the resource this document is part of
     */
    public Resource getOwningResource() {
        return owningResource;
    }

    /**
     * @param resource the resource this document is part of
     */
    public void setOwningResource(Resource resource) {
        this.owningResource = resource;
    }

    /**
     * @return the id of the resource this document is part of
     */
    public Long getOwningResourceId() {
        if (this.owningResource != null) {
            return this.owningResource.getId();
        } else {
            return owningResourceId;
        }
    }

    /**
     * @param resourceId the id of the resource this document is part of
     */
    public void setOwningResourceId(Long resourceId) {
        this.owningResourceId = resourceId;
    }

    /**
     * @return True if there is an owning resource
     */
    public boolean hasOwningResource() {
        return this.owningResource != null || this.owningResourceId != null;
    }

    /**
     * @return the list of sticky note links of which the document is the source
     */
    @Override
    public List<StickyNoteLink> getStickyNoteLinksAsSrc() {
        return stickyNoteLinksAsSrc;
    }

    /**
     * @param stickyNoteLinksAsSrc the list of sticky note links of which the document is the source
     */
    public void setStickyNoteLinksAsSrc(List<StickyNoteLink> stickyNoteLinksAsSrc) {
        this.stickyNoteLinksAsSrc = stickyNoteLinksAsSrc;
    }

    // ---------------------------------------------------------------------------------------------
    // concerning the whole class
    // ---------------------------------------------------------------------------------------------

    @Override
    public void mergeToUpdate(ColabEntity other) throws ColabMergeException {
        if (other instanceof Document) {
            Document o = (Document) other;
            this.setDeletionStatus(o.getDeletionStatus());
            this.setIndex(o.getIndex());
        } else {
            throw new ColabMergeException(this, other);
        }
    }

    /**
     * Get the project this document belongs to
     *
     * @return document owner
     */
    @JsonbTransient
    public Project getProject() {
        if (this.owningCardContent != null) {
            // The document is a deliverable of a card content
            return this.owningCardContent.getProject();
        } else if (this.owningResource != null) {
            // The document is part of a resource
            return this.owningResource.getProject();
        } else {
            // such an orphan shouldn't exist...
            return null;
        }
    }

    @Override
    public ChannelsBuilder getChannelsBuilder() {
        if (this.owningCardContent != null) {
            // The document is a deliverable of a card content
            return this.owningCardContent.getChannelsBuilder();
        } else if (this.owningResource != null) {
            // The document is part of a resource
            return this.owningResource.getChannelsBuilder();
        } else {
            // such an orphan shouldn't exist...
            return new EmptyChannelBuilder();
        }
    }

    @Override
    @JsonbTransient
    public Conditions.Condition getReadCondition() {
        if (this.owningCardContent != null) {
            // The document is a deliverable of a card content
            return new Conditions.HasCardReadRight(this.owningCardContent);
        } else if (this.owningResource != null) {
            // The document is part of a resource
            return this.owningResource.getReadCondition();
        } else {
            // such an orphan shouldn't exist...
            return Conditions.defaultForOrphan;
        }
    }

    @Override
    @JsonbTransient
    public Conditions.Condition getUpdateCondition() {
        if (this.owningCardContent != null) {
            // The document is a deliverable of a card content
            return this.owningCardContent.getUpdateCondition();
        } else if (this.owningResource != null) {
            // The document is part of a resource
            return this.owningResource.getUpdateCondition();
        } else {
            // such an orphan shouldn't exist...
            return Conditions.defaultForOrphan;
        }
    }

    @Override
    public int hashCode() {
        return EntityHelper.hashCode(this);
    }

    @Override
    @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
    public boolean equals(Object obj) {
        return EntityHelper.equals(this, obj);
    }

    @Override
    public abstract String toString();

    /**
     * @return This abstract class fields to mention in the toString implementations
     */
    protected String toPartialString() {
        return "id=" + id + ", deletion=" + getDeletionStatus() + ", index=" + index;
    }

}