CardContent.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.card;
import static ch.colabproject.colab.api.model.card.Card.STRUCTURE_SEQUENCE_NAME;
import ch.colabproject.colab.api.exceptions.ColabMergeException;
import ch.colabproject.colab.api.model.ColabEntity;
import ch.colabproject.colab.api.model.WithWebsocketChannels;
import ch.colabproject.colab.api.model.common.ConversionStatus;
import ch.colabproject.colab.api.model.common.DeletionStatus;
import ch.colabproject.colab.api.model.common.Tracking;
import ch.colabproject.colab.api.model.document.AbstractResource;
import ch.colabproject.colab.api.model.document.Document;
import ch.colabproject.colab.api.model.document.Resourceable;
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 java.util.ArrayList;
import java.util.List;
import javax.json.bind.annotation.JsonbTransient;
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.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
/**
* Card content
*
* @author sandra
*/
@Entity
@Table(indexes = {
@Index(columnList = "card_id"), })
public class CardContent implements ColabEntity, WithWebsocketChannels,
Resourceable, StickyNoteSourceable {
private static final long serialVersionUID = 1L;
// ---------------------------------------------------------------------------------------------
// fields
// ---------------------------------------------------------------------------------------------
/**
* Card content ID
*/
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = STRUCTURE_SEQUENCE_NAME)
private 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;
/**
* Title
*/
@Size(max = 255)
private String title;
/**
* Status
*/
@Enumerated(EnumType.STRING)
private CardContentStatus status;
/**
* A frozen content is read-only
*/
@NotNull
private boolean frozen;
/**
* Completion level
*/
@Min(0)
@Max(100)
@NotNull
private int completionLevel;
/**
* Completion mode : how the completion level is filled
*/
@Enumerated(EnumType.STRING)
private CardContentCompletionMode completionMode;
/**
* Conversion status : has the deliverable content been converted
*/
@Enumerated(EnumType.STRING)
private ConversionStatus lexicalConversion;
/**
* The card to which this content belongs
*/
@ManyToOne(fetch = FetchType.LAZY)
@NotNull
@JsonbTransient
private Card card;
/**
* The card ID (serialization sugar)
*/
@Transient
private Long cardId;
/**
* The deliverables of this card content
*/
@OneToMany(mappedBy = "owningCardContent", cascade = CascadeType.ALL)
@JsonbTransient
private List<Document> deliverables = new ArrayList<>();
/**
* The cards contained in there
*/
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
@JsonbTransient
private List<Card> subCards = new ArrayList<>();
/**
* The list of abstract resources directly linked to this card content
*/
@OneToMany(mappedBy = "cardContent", cascade = CascadeType.ALL)
@JsonbTransient
private List<AbstractResource> directAbstractResources = new ArrayList<>();
/**
* The list of sticky note links of which the card content is the source
*/
@OneToMany(mappedBy = "srcCardContent", cascade = CascadeType.ALL)
@JsonbTransient
private List<StickyNoteLink> stickyNoteLinksAsSrc = new ArrayList<>();
// ---------------------------------------------------------------------------------------------
// getters and setters
// ---------------------------------------------------------------------------------------------
/**
* @return the id
*/
@Override
public Long getId() {
return id;
}
/**
* @param id the new 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 title
*/
public String getTitle() {
return title;
}
/**
* @param title the new title
*/
public void setTitle(String title) {
this.title = title;
}
/**
* @return the status
*/
public CardContentStatus getStatus() {
return status;
}
/**
* @param status the new status
*/
public void setStatus(CardContentStatus status) {
this.status = status;
}
/**
* Get the value of frozen
*
* @return the value of frozen
*/
public boolean isFrozen() {
return frozen;
}
/**
* Set the value of frozen
*
* @param frozen new value of frozen
*/
public void setFrozen(boolean frozen) {
this.frozen = frozen;
}
/**
* @return the completionLevel
*/
public int getCompletionLevel() {
return completionLevel;
}
/**
* @param completionLevel the new completionLevel
*/
public void setCompletionLevel(int completionLevel) {
this.completionLevel = completionLevel;
}
/**
* @return the completion mode : how the completion level is filled
*/
public CardContentCompletionMode getCompletionMode() {
return completionMode;
}
/**
* @param completionMode the new completion mode : how the completion level is filled
*/
public void setCompletionMode(CardContentCompletionMode completionMode) {
this.completionMode = completionMode;
}
/**
* @return the conversion status : conversion status of deliverables for lexical
*/
public ConversionStatus getLexicalConversion() {
return lexicalConversion;
}
/**
* @param lexicalConversion the new conversion status : conversion status of deliverables for
* lexical
*/
public void setLexicalConversion(ConversionStatus lexicalConversion) {
this.lexicalConversion = lexicalConversion;
}
/**
* @return the card to which this content belongs
*/
public Card getCard() {
return card;
}
/**
* @param card the new card to which this content belongs
*/
public void setCard(Card card) {
this.card = card;
}
/**
* get the id of the card to which this content belongs. To be sent to client.
*
* @return the ID of the card to which this content belongs
*/
public Long getCardId() {
if (this.card != null) {
return card.getId();
} else {
return cardId;
}
}
/**
* set the id of the card to which this content belongs. For serialization only.
*
* @param cardId the ID of the card to which this content belongs
*/
public void setCardId(Long cardId) {
this.cardId = cardId;
}
/**
* @return the deliverables of this card content
*/
public List<Document> getDeliverables() {
return deliverables;
}
/**
* @param deliverables the deliverables of this card content
*/
public void setDeliverables(List<Document> deliverables) {
this.deliverables = deliverables;
}
/**
* @return the cards contained in there
*/
public List<Card> getSubCards() {
return subCards;
}
/**
* @param subCards the cards contained in there
*/
public void setSubCards(List<Card> subCards) {
this.subCards = subCards;
}
/**
* @return the list of abstract resources directly linked to this card content
*/
@Override
public List<AbstractResource> getDirectAbstractResources() {
return directAbstractResources;
}
/**
* @param abstractResources the list of abstract resources directly linked to this card content
*/
public void setDirectAbstractResources(List<AbstractResource> abstractResources) {
this.directAbstractResources = abstractResources;
}
/**
* @return the list of sticky note links of which the card content is the source
*/
@Override
public List<StickyNoteLink> getStickyNoteLinksAsSrc() {
return stickyNoteLinksAsSrc;
}
/**
* @param stickyNoteLinksAsSrc the list of sticky note links of which the card content 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 CardContent) {
CardContent o = (CardContent) other;
this.setDeletionStatus(o.getDeletionStatus());
this.setTitle(o.getTitle());
this.setStatus(o.getStatus());
this.setFrozen(o.isFrozen());
this.setCompletionLevel(o.getCompletionLevel());
this.setCompletionMode(o.getCompletionMode());
// lexicalConversion must not be merged nor duplicated
} else {
throw new ColabMergeException(this, other);
}
}
/**
* Get the project this content belongs to
*
* @return content owner
*/
@JsonbTransient
public Project getProject() {
if (this.card != null) {
return this.card.getProject();
}
return null;
}
@Override
public ChannelsBuilder getChannelsBuilder() {
if (this.card != null) {
return this.card.getChannelsBuilder();
} else {
// such an orphan shouldn't exist...
return new EmptyChannelBuilder();
}
}
@Override
@JsonbTransient
public Conditions.Condition getReadCondition() {
// genuine hack inside
// any member can read any card and card content of the project
// if a member lacks the read right on a card, it will not be able to read the
// deliverables,
// resources and so on, but it will still be able to view the card "from the
// outside"
return new Conditions.IsCurrentUserMemberOfProject(getProject());
}
@Override
@JsonbTransient
public Conditions.Condition getUpdateCondition() {
if (this.card != null) {
return this.card.getUpdateCondition();
} else {
// orphan content should never happen
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 String toString() {
return "CardContent{" + "id=" + id + ", deletion=" + getDeletionStatus()
+ ", title=" + title + ", status=" + status
+ ", completion=" + completionLevel + ", completionMode=" + completionMode
+ ", lexicalConversion=" + lexicalConversion
+ ", frozen=" + frozen + ", cardId=" + cardId + "}";
}
}