ResourceRef.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.tools.EntityHelper;
import java.util.ArrayList;
import java.util.List;
import javax.json.bind.annotation.JsonbTransient;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Index;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.validation.constraints.NotNull;

/**
 * A reference to another existing abstract resource.
 * <p>
 * There can be a chain of references to aim at a resource.
 * <p>
 * Ref - [Ref -](0..*) Resource
 * <p>
 * The resource reference holds the comments for the resource in the context of a specific card /
 * card type / card type reference / card content.
 *
 * @author sandra
 */
@Entity
@Table(
    indexes = {
        @Index(columnList = "target_id"),
    }
)
@DiscriminatorValue("RESOURCE_REF")
@NamedQuery(name = "ResourceRef.findDirectReferences",
    query = "SELECT ref FROM ResourceRef ref "
        + "WHERE ref.target IS NOT NULL AND ref.target.id = :targetId")
public class ResourceRef extends AbstractResource {

    private static final long serialVersionUID = 1L;

    // ---------------------------------------------------------------------------------------------
    // fields
    // ---------------------------------------------------------------------------------------------

    /**
     * Manually discard the resource = say that the target resource must not be displayed as an
     * active resource at this reference level.
     */
    @NotNull
    private boolean refused;

    /**
     * Automatically discard the resource = say that the target resource must not be displayed as an
     * active resource at this reference level.
     * <p>
     * It can be because it is not published anymore or the reference is a rest of a former
     * ancestor. The resource can be manually "revived".
     * <p>
     * The reference is not destroyed so that a rollback without loss of data is possible.
     */
    @NotNull
    private boolean residual;

    /**
     * The abstract resource this reference aims at
     */
    @ManyToOne(fetch = FetchType.LAZY)
    @NotNull
    @JsonbTransient
    private AbstractResource target;

    /**
     * The abstract resource id (serialization sugar)
     */
    @Transient
    private Long targetId;

    // ---------------------------------------------------------------------------------------------
    // getters and setters
    // ---------------------------------------------------------------------------------------------

    /**
     * @return if the targeted resource was manually marked as to be not displayed as active at this
     *         reference level
     */
    public boolean isRefused() {
        return refused;
    }

    /**
     * @param refused if the targeted resource was manually marked as to be not displayed as active
     *                at this reference level
     */
    public void setRefused(boolean refused) {
        this.refused = refused;
    }

    /**
     * @return if the targeted resource was automatically marked as to be not displayed as active at
     *         this reference level.
     */
    public boolean isResidual() {
        return residual;
    }

    /**
     * @param residual if the targeted resource was automatically marked as to be not displayed as
     *                 active at this reference level.
     */
    public void setResidual(boolean residual) {
        this.residual = residual;
    }

    /**
     * @return the resource (or resource reference) this reference aims at
     */
    public AbstractResource getTarget() {
        return target;
    }

    /**
     * @param target the resource (or resource reference) this reference aims at
     */
    public void setTarget(AbstractResource target) {
        this.target = target;
    }

    /**
     * get the id of the resource (or resource reference) this reference aims at. To be sent to
     * client
     *
     * @return id of the abstract resource or null
     */
    public Long getTargetId() {
        if (target != null) {
            return target.getId();
        } else {
            return targetId;
        }
    }

    /**
     * set the id of the resource (or resource reference) this reference aims at. For serialization
     * only
     *
     * @param targetId the id of the resource or resource reference
     */
    public void setTargetId(Long targetId) {
        this.targetId = targetId;
    }

    // ---------------------------------------------------------------------------------------------
    // helpers
    // ---------------------------------------------------------------------------------------------

    @Override
    public Resource resolve() {
        if (this.target != null) {
            return this.target.resolve();
        }

        return null;
    }

    @Override
    public List<AbstractResource> expand() {
        List<AbstractResource> list = new ArrayList<>();

        list.add(this);
        if (this.target != null) {
            list.addAll(this.target.expand());
        }
        return list;
    }

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

    @Override
    public void mergeToUpdate(ColabEntity other) throws ColabMergeException {
        super.mergeToUpdate(other);

        // residual cannot be changed alone manually. It is handled by ResourceManager
        // refused cannot be changed alone manually. It is handled by ResourceManager

        if (!(other instanceof ResourceRef)) {
            throw new ColabMergeException(this, other);
        }
    }

    @Override
    public void mergeToDuplicate(ColabEntity other) throws ColabMergeException {
        super.mergeToDuplicate(other);

        if (other instanceof ResourceRef) {
            ResourceRef o = (ResourceRef) other;
            this.setRefused(o.isRefused());
            this.setResidual(o.isResidual());
        } else {
            throw new ColabMergeException(this, other);
        }
    }

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

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

    @Override
    public String toString() {
        return "ResourceRef{" + toPartialString() + ", refused=" + refused
            + ", residual=" + residual + ", targetId=" + targetId + "}";
    }

}