All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nl.vpro.domain.media.MemberRef Maven / Gradle / Ivy

Go to download

The basic domain classes for 'media', the core of POMS. Also, the 'update' XML bindings for it. It also contains some closely related domain classes like the enum to contain NICAM kijkwijzer settings.

There is a newer version: 8.3.1
Show newest version
package nl.vpro.domain.media;

import lombok.*;

import java.io.Serial;
import java.io.Serializable;
import java.time.Instant;
import java.util.*;

import jakarta.persistence.Entity;
import jakarta.persistence.*;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import jakarta.xml.bind.Unmarshaller;
import jakarta.xml.bind.annotation.*;
import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.annotations.*;

import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

import nl.vpro.domain.*;
import nl.vpro.domain.media.support.MutableOwnable;
import nl.vpro.domain.media.support.OwnerType;
import nl.vpro.domain.user.Editor;
import nl.vpro.jackson2.StringInstantToJsonTimestamp;
import nl.vpro.jackson2.Views;
import nl.vpro.xml.bind.InstantXmlAdapter;

import static java.util.Comparator.comparing;
import static nl.vpro.domain.Changeables.instant;
import static nl.vpro.domain.media.MediaObjectFilters.*;

/**
 * 

* Expresses an association between MediaObjects. MediaObjects can become a * member or episode of other MediaObjects. The member is the member of this * relation which references the aggregating owner. *

*

* Members or episodes can have an ordering if they are a part of an ordered * Group. In that case a MemberRef gets an number value denoting its position in * the group. In all other cases this number should retain a null value. *

*

* Incoming XML has a member-/episodeof element for each member and references * the owner by its URN. The urnRef field holds a temporary reference just for * this purpose and should never be persisted. *

* @author roekoe * @see MediaObject#memberOf * @see Program#episodeOf */ @Entity @FilterDef(name = MR_PUBLICATION_FILTER) @Filter(name = MR_PUBLICATION_FILTER, condition = MR_PUBLICATION_FILTER_CONDITION) @FilterDef(name = MR_EMBARGO_FILTER, parameters = {@ParamDef(name = PARAMETER_BROADCASTERS, type = String.class)}) @Filter(name = MR_EMBARGO_FILTER, condition = MR_EMBARGO_FILTER_CONDITION) @FilterDef(name = MR_DELETED_FILTER) @Filter(name = MR_DELETED_FILTER, condition = """ (select m.workflow from mediaobject m where m.id = owner_id and m.mergedTo_id is null) NOT IN ('MERGED', 'FOR_DELETION', 'DELETED') AND (select m.workflow from mediaobject m where m.id = member_id and m.mergedTo_id is null) NOT IN ('MERGED', 'FOR_DELETION', 'DELETED') """) @XmlAccessorType(XmlAccessType.NONE) @JsonInclude(JsonInclude.Include.NON_NULL) @XmlType(name = "memberRefType") @XmlRootElement(name = "memberRef") @JsonPropertyOrder({ "midRef", "urnRef", "type", "index", "highlighted", "memberOf", "episodeOf", "segmentOf" }) public class MemberRef implements Identifiable, Comparable, Serializable, MutableOwnable, RecursiveParentChildRelation, Accountable { @Serial private static final long serialVersionUID = -2247469313512827819L; @Id @GeneratedValue(strategy = GenerationType.AUTO) @Setter(AccessLevel.PACKAGE) private Long id; @Setter @ManyToOne(optional = false, fetch = FetchType.LAZY) @JsonBackReference protected MediaObject member; @ManyToOne(optional = false, fetch = FetchType.LAZY) @JoinColumn(name="owner_id") protected MediaObject group; @Transient protected String urnRef; @Transient protected String cridRef; @Transient protected String midRef; @Transient protected MediaType typeOfGroup; @Setter @Min(0) protected Integer number; @Setter @Column(nullable = false, updatable = false) protected Instant added; @Column(nullable = false) @NotNull(message = "nl.vpro.constraints.NotNull") protected Boolean highlighted = false; @Enumerated(EnumType.STRING) private OwnerType owner; @Transient private SortedSet memberOf; @Transient private SortedSet episodeOf; @Transient private RecursiveMemberRef segmentOf; @Transient @Getter @Setter(AccessLevel.PACKAGE) private MemberRefType refType; @Column(nullable = true, name="creationDate") @Getter @Setter protected Instant creationInstant = Changeables.instant(); @Column(nullable = true, name="lastModified") protected Instant lastModified; @ManyToOne(optional = true) @JoinColumn(name = "createdby_principalid") @XmlTransient @Getter @Setter protected Editor createdBy; @ManyToOne(optional = true) @JoinColumn(name = "lastmodifiedby_principalid") @XmlTransient @Getter @Setter protected Editor lastModifiedBy; public MemberRef() { } public MemberRef(MediaObject member, MediaObject parent, Integer number, OwnerType owner) { this(null, member, parent, number, owner); } public MemberRef(Long id, MediaObject member, MediaObject parent, Integer number, OwnerType owner) { if(member == null || parent == null) { throw new IllegalArgumentException(String.format("Must supply valid member and group. Got member: %1$s and group: %2$s", member, parent)); } this.id = id; this.member = member; this.group = parent; this.number = number; this.owner = owner; } public MemberRef(String mid) { this(mid, null); } public MemberRef(String mid, Integer number) { if(mid == null) { throw new NullPointerException("Cannot instantiate a memberref with mid null"); } this.midRef = mid; this.number = number; } public MemberRef(MemberRef source) { this(source, source.member, source.getOwner()); } public MemberRef(MemberRef source, MediaObject member, OwnerType owner) { this.member = member; this.group = source.group; this.typeOfGroup = source.typeOfGroup; this.number = source.number; this.owner = owner; this.added = source.added; this.highlighted = source.highlighted; this.cridRef = source.cridRef; this.midRef = source.midRef; this.urnRef = source.urnRef; } @lombok.Builder(builderClassName = "Builder") private MemberRef( Long id, MediaObject member, MediaObject parent, Integer number, Instant added, String midRef, String cridRef, MediaType type, Boolean highlighted, OwnerType owner) { this.id = id; this.member = member; this.group = parent; this.number = number; this.midRef = midRef; this.cridRef = cridRef; this.added = added; this.highlighted = highlighted == null ? false : highlighted; this.typeOfGroup = type; this.owner = owner; } public static MemberRef copy(MemberRef source){ return copy(source, source.member); } public static MemberRef copy(MemberRef source, MediaObject member){ if(source == null) { return null; } MemberRef copy = new MemberRef(source, member, source.getOwner()); copy.added = source.added; copy.episodeOf = source.getEpisodeOf(); copy.memberOf = source.getMemberOf(); copy.segmentOf = source.getSegmentOf(); copy.refType = source.refType; return copy; } public boolean ownerEqualsOnRef(MemberRef that) { if(group != null && that.getGroup() != null) { return MediaObjects.equalsOnAnyId(group, that.getGroup()); } if(group == null && that.getGroup() != null) { MediaObject thatOwner = that.getGroup(); return thatOwner.getUrn() != null && thatOwner.getUrn().equals(getUrnRef()) || thatOwner.getMid() != null && thatOwner.getMid().equals(getMidRef()) || thatOwner.getCrids().contains(getCridRef()); } if(group != null && that.getGroup() == null) { return group.getUrn() != null && group.getUrn().equals(that.getUrnRef()) || group.getMid() != null && group.getMid().equals(that.getMidRef()) || group.getCrids().contains(getCridRef()); } return that.getUrnRef() != null && that.getUrnRef().equals(getUrnRef()) || that.getMidRef() != null && that.getMidRef().equals(getMidRef()) || that.getCridRef() != null && that.getCridRef().equals(getCridRef()); } @Override public Long getId() { return id; } public MediaObject getMember() { return member; } /** * Returns the representing the 'group' role in this member-ref-relation. Note that this likely, but not necessarily is a {@link Group} object, since any {@link MediaObject} may take members. */ @Nullable public MediaObject getGroup() { return group; } public void setGroup(MediaObject owner) { this.urnRef = null; this.cridRef = null; this.group = owner; } /** * Reference to the parent. * The first non null of {@link #getMidRef}, {@link #getCridRef()}, {@link #getUrnRef} * In the API this is equivalent to {@link #getMidRef} */ public String getMediaRef() { String ref = getMidRef(); if(ref != null) { return ref; } ref = getCridRef(); if(ref != null) { return ref; } return getUrnRef(); } public void setMediaRef(String mediaRef) { if (mediaRef == null) { throw new IllegalArgumentException("Mediaref cannot be null"); } if(mediaRef.startsWith("urn:vpro:media:")) { this.urnRef = mediaRef; } else if(mediaRef.startsWith("crid://")) { this.cridRef = mediaRef; } else { this.setMidRef(mediaRef); } } /** * Retrieves a reference to the owning object and parent of the association * by name. This method is primarily here for marshaling purposes. In XML * documents a MemberRef element contains an urnRef attribute referencing * the owner. First this method tries to construct an urnRef based on the * URN of the owner. When this fails, it tries to construct a reference * bases on a CRID from the owner. * * @return reference to the owner by name or null */ @XmlAttribute public String getUrnRef() { if(group != null) { return group.getId() == null ? null : group.getUrn(); } return urnRef; } /** * Set a reference to the owner by name. A reference might consist of an URN * of a CRID identifying the owner. This method should only be used when * unmarshalling from XML. When importing XML the import service should * retrieve the owner with this name and properly initialise this * association. * * @param value a valid URN or CRID */ public void setUrnRef(String value) { if(group != null) { throw new IllegalStateException(); } this.urnRef = value; } @XmlAttribute public String getCridRef() { if(getUrnRef() != null) { return null; } if(group != null && !group.getCrids().isEmpty()) { return group.getCrids().get(0); } if(cridRef != null) { return cridRef; } return null; } public void setCridRef(String crid) { if(group != null || urnRef != null) { throw new IllegalStateException(" "); } cridRef = crid; } /** * The 'parent' of this relation */ @Override @XmlAttribute public String getMidRef() { if(this.group != null) { return group.getMid(); } return midRef; } @Override public String getChildMid() { return member == null ? null : member.getMid(); } public void setMidRef(String midRef) { if(group != null) { throw new IllegalStateException("Call set midRef on the enclosed owner, this is a JAXB only setter"); } this.midRef = midRef; } @Override @XmlAttribute @NonNull public MediaType getType() { return group != null ? MediaType.getMediaType(group) : typeOfGroup; } public void setType(MediaType type) { if(group != null && MediaType.getMediaType(group) != type) { throw new IllegalStateException("Supplied type " + type + " does not match owner type " + MediaType.getMediaType(group)); } typeOfGroup = type; } @Override public void setLastModifiedInstant(Instant lastModified) { this.lastModified = lastModified; } @Override public Instant getLastModifiedInstant() { return this.lastModified; } boolean isValid() { return member != null && group != null && !(group instanceof Group g && g.isOrdered() && (number == null || number < 1)); } @XmlAttribute(name = "index") @XmlSchemaType(name = "nonNegativeInteger") public Integer getNumber() { if (group instanceof Group g && ! g.isOrdered()) { return null; } return number; } @XmlAttribute @XmlJavaTypeAdapter(InstantXmlAdapter.class) @XmlSchemaType(name = "dateTime") @JsonDeserialize(using = StringInstantToJsonTimestamp.Deserializer.class) @JsonSerialize(using = StringInstantToJsonTimestamp.Serializer.class) public Instant getAdded() { return added; } @XmlAttribute(required = true) public Boolean isHighlighted() { return highlighted; } public MemberRef setHighlighted(Boolean highlighted) { this.highlighted = highlighted != null ? highlighted : false; return this; } /** * The object is not serialized to the database, only {@link #getMediaRef()} and {@link #getMidRef()} are filled. */ @XmlTransient public boolean isVirtual() { return group == null || member == null; } /** * @since 5.13 */ @Override @XmlElement(name = "memberOf") @JsonView(Views.Forward.class) @JsonManagedReference public SortedSet getMemberOf() { if (memberOf == null) { if (group != null) { return RecursiveMemberRef.memberOfs(this); } else { memberOf = new TreeSet<>(); } } return memberOf; } @Override public void setMemberOf(SortedSet memberOfList) { this.memberOf = memberOfList; } /** * @since 5.13 */ @Override @XmlElement(name = "episodeOf") @JsonView(Views.Forward.class) @JsonManagedReference public SortedSet getEpisodeOf() { if (episodeOf == null) { if (group != null && group instanceof Program) { return RecursiveMemberRef.episodeOfs(this); } else { episodeOf = new TreeSet<>(); } } return episodeOf; } @Override public void setEpisodeOf(SortedSet episodeOfList) { this.episodeOf = episodeOfList; } /** * @since 5.13 */ @Override @XmlElement(name = "segmentOf") @JsonView(Views.Forward.class) @JsonManagedReference public RecursiveMemberRef getSegmentOf() { if (segmentOf == null) { if (group != null && group instanceof Segment segment) { segmentOf = RecursiveMemberRef.ofSegment((segment)); } } return segmentOf; } @Override public void setSegmentOf(RecursiveMemberRef segmentOf) { this.segmentOf = segmentOf; } @Override public boolean equals(Object object) { if(this == object) { return true; } if(!(object instanceof MemberRef that)) { return false; } // Jaxb does not initialise the owner but a reference to the owner return memberEqualsOnRef(that) && ownerEqualsOnRef(that) && (number == null ? that.getNumber() == null : number.equals(that.getNumber())); } @Override public int hashCode() { if(id == null & member == null && urnRef == null && number == null) { return super.hashCode(); } int hash = 5; hash = 53 * hash + (this.member != null ? this.member.hashCode() : 0); hash = 53 * hash + (this.getMediaRef() != null ? this.getMediaRef().hashCode() : 0); hash = 53 * hash + (this.number != null ? this.number.hashCode() : 0); return hash; } @Override public int compareTo(@NonNull MemberRef memberRef) { if(this.getUrnRef() != null && memberRef.getUrnRef() != null && this.getUrnRef().compareTo(memberRef.getUrnRef()) != 0) { return this.getUrnRef().compareTo(memberRef.getUrnRef()); } if(this.number != null && memberRef.getNumber() != null && !this.number.equals(memberRef.getNumber())) { return this.number - memberRef.getNumber(); } /* if(this.added != null && memberRef.getAdded() != null && !this.added.equals(memberRef.getAdded())) { return this.added.compareTo(memberRef.getAdded()); } */ if(this.getMediaRef() != null && memberRef.getMediaRef() != null) { return getMediaRef().compareTo(memberRef.getMediaRef()); } /* if(this.id != null && memberRef.getId() != null) { return this.getId().compareTo(memberRef.getId()); } */ if (this.equals(memberRef)) { return 0; } return comparing(MemberRef::getUrnRef, Comparator.nullsLast(Comparator.naturalOrder())) .thenComparing(MemberRef::getNumber, Comparator.nullsLast(Comparator.naturalOrder())) .thenComparing(MemberRef::getMidRef, Comparator.nullsLast(Comparator.naturalOrder())) .thenComparing(MemberRef::getMediaRef, Comparator.nullsLast(Comparator.naturalOrder())) .compare(this, memberRef); } void afterUnmarshal(Unmarshaller unmarshaller, Object parent) { if(parent instanceof MediaObject mediaObject) { this.member = mediaObject; } } @Override public String toString() { ToStringBuilder builder = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE); if (id != null) { builder.append("id", id); } if (getGroup() != null) { builder.append("group", getGroup().getMainIdentifier().orElseGet(() -> getGroup().toString())); } if (getUrnRef() != null) { builder.append("urnRef", getUrnRef()); } if (getCridRef() != null) { builder.append("cridRef", getCridRef()); } if (getMidRef() != null) { builder.append("midRef", getMidRef()); } if (getMidRef() == null && getMediaRef() != null) { builder.append("mediaRef", getMediaRef()); } if (getMember() != null) { builder.append("member", getMember().getMainIdentifier().orElseGet(() -> getMember().toString())); } if (number != null) { builder.append("number", number); } if (added != null) { builder.append("added", added); } if (highlighted != null) { builder.append("highlighted", highlighted); } return builder.toString(); } private boolean memberEqualsOnRef(MemberRef that) { MediaObject thatMember = that.getMember(); return member != null && thatMember != null && (member == thatMember || member.getUrn() != null && member.getUrn().equals(thatMember.getUrn()) || member.getMid() != null && member.getMid().equals(thatMember.getMid()) || memberEqualsOnCrid(thatMember)); } private boolean memberEqualsOnCrid(MediaObject thatMember) { return MediaObjects.equalsOnCrid(member, thatMember); } @PrePersist @PreUpdate public void prePersist() { if (this.added == null) { this.added = instant(); } } @NonNull @Override @XmlTransient public OwnerType getOwner() { return owner; } @Override public void setOwner(@NonNull OwnerType owner) { this.owner = owner; } public static class Builder { /** * Calls either {@link #cridRef(String)} or {@link #midRef(String)} * A reference to the 'parent' of the reference object */ public Builder mediaRef(String ref) { if (ref != null) { if (ref.startsWith("crid:")) { return cridRef(ref); } else { return midRef(ref); } } return this; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy