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

nl.vpro.domain.PublicationReason Maven / Gradle / Ivy

Go to download

Several domains like 'media', pages' and 'subtitles' in the POMS system share some common properties which are collected here

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

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

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

import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;

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

import nl.vpro.jackson2.StringInstantToJsonTimestamp;
import nl.vpro.util.DateUtils;
import nl.vpro.xml.bind.InstantXmlAdapter;

import static java.util.Comparator.*;

/**
 * Indicates a reason for publication. This part of the a {@code MediaChange} object.
 * In ElasticSearch is it written directly into the {@code _doc} representing the {@code MediaObject} itself though.
 * 

* So a reason in the 'repubReason' field in the database looks like * {@code [\t][..]]} * So, multiple reasons are joined with {@link #REASON_SPLITTER} *

* Resulting in two reason in the api, where the strings are joined with a string representing the mechanism and a timestamp. (The two fields in {@link PublicationReason}). * Encoded {@code republication|␟1686051496927} * and {@code republication|␟1686051496927} (constructed with {@link #toRecord(Instant)}). *

* While transferring multiple such reasons to the publisher multiple of such string representations are joined with {@link #RECORD_SPLITTER}. *

* * * * @author Michiel Meeuwissen * @since 7.1 */ @Getter @XmlAccessorType(XmlAccessType.NONE) @XmlType(name = "publicationReasonType", namespace = Xmlns.API_NAMESPACE) @Slf4j public class PublicationReason implements Serializable, Comparable { @Serial private static final long serialVersionUID = -5898117026516765909L; /** * Multiple reasons can be joined with this, to encode them in one String. *

* Uses ASCII Record separator RS */ public static final String RECORD_SPLITTER = "␞"; /** * A reason can be joined with its publication time. Not in the database, just in the string representation of a * set of reasons (as put on headers). *

* Uses ASCII Unit separator US */ public static final String FIELD_SPLITTER = "␟"; /** * Multiple reasons can be collected in the database, in that case they are joined with this. */ public static final String REASON_SPLITTER = "\t"; /** * An explicit reason can be stored in the database, but they get prefixed by the mechanism that caused them */ public static final String REASON_PREFIX_SPLITTER = "|"; /** * The pattern for a valid reason in the database. */ public static final String REASON_PATTERN = "[^" + RECORD_SPLITTER + FIELD_SPLITTER + "]*"; /** * The string representing the reason for publication. This may just be some mechanism, like the ones found in nl.vpro.domain.media.PublicationUpdate.Reasons}, but this may also be a prefix for some explicit reason set in the database (seperated by {@link #REASON_PREFIX_SPLITTER}). */ @XmlValue String value; @XmlAttribute @XmlJavaTypeAdapter(InstantXmlAdapter.class) @XmlSchemaType(name = "dateTime") @JsonSerialize(using = StringInstantToJsonTimestamp.Serializer.class) @JsonDeserialize(using = StringInstantToJsonTimestamp.Deserializer.class) private Instant publishDate; protected PublicationReason() { } public PublicationReason( @NonNull String value, @Nullable Instant publicationDate) { this.value = value; this.publishDate = publicationDate; } public PublicationReason parent() { return new PublicationReason(parentReason(value), getPublishDate()); } public String toRecord(Instant now) { return value + FIELD_SPLITTER + (publishDate == null ? now.toEpochMilli() : publishDate.toEpochMilli()); } @Override public String toString() { return value + ":" + publishDate; } public static String parentReason(String reason) { return "parent: " + reason; } public static String toRecords(Instant now, List reasons, long maxLength, String mid, boolean mergeDuplicates) { final StringBuilder builder = new StringBuilder(); PublicationReason prevReason = null; for (PublicationReason reason : reasons) { boolean needsSplitter = !builder.isEmpty(); if (prevReason != null) { if (DateUtils.isAfter(prevReason.publishDate, reason.publishDate)) { log.warn("The list seems to be ordered wrong"); } if (mergeDuplicates && Objects.equals(prevReason.getValue(), reason.getValue())) { builder.delete(builder.length() - prevReason.toRecord(now).length(), builder.length()); needsSplitter = false; } } if (needsSplitter){ builder.append(RECORD_SPLITTER); } String s = reason.toRecord(now); builder.append(s); prevReason = reason; if (builder.length() >= maxLength) { log.error("Reason header for {} is ridiculously long ({} reasons, {} chars). Will be truncated", mid, reasons.size(), builder.length()); break; } } return builder.toString(); } public static PublicationReason parseOne(final String string, String logContext) { final String[] reasonAndDate = string.split(FIELD_SPLITTER, 2); final Instant instant; if (reasonAndDate.length > 1 && !reasonAndDate[1].isEmpty()) { instant = Instant.ofEpochMilli(Long.parseLong(reasonAndDate[1])); } else { log.warn("{} No time found in '{}'. Creating a publication reason without publish date", logContext, string); instant = null; } return new PublicationReason(reasonAndDate[0], instant); } /** * @param string The string to parse * @param logContext Something which will be added to the warnings in the log if something is odd. This may give some context to the person encountering the issue. */ public static PublicationReason[] parseList( final String string, final String logContext) { List result = new ArrayList<>(); if (string != null && !string.isEmpty()) { for (var s : string.split(RECORD_SPLITTER)) { try { result.add(parseOne(s, logContext + "(" + string + " )")); } catch(Exception e) { log.warn("{} Couldn't parse {}: {} {}", logContext, s, e.getClass().getName(), e.getMessage()); } } } else { log.warn("{} No reasons found in {}", logContext, string); } return result.toArray(PublicationReason[]::new); } private static final Comparator COMPARATOR = comparing( PublicationReason::getPublishDate, nullsLast(naturalOrder())) .thenComparing( PublicationReason::getValue, nullsLast(naturalOrder()) ); @Override public int compareTo(@NonNull PublicationReason o) { return COMPARATOR.compare(this, o); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; PublicationReason that = (PublicationReason) o; if (!Objects.equals(value, that.value)) return false; return Objects.equals(publishDate, that.publishDate); } @Override public int hashCode() { int result = value != null ? value.hashCode() : 0; result = 31 * result + (publishDate != null ? publishDate.hashCode() : 0); return result; } /** * Known reasons for publication. *

* @since 7.10 (in {@code nl.vpro.domain.media.PublicationUpdate} before) */ public static abstract class Reasons { private Reasons() { // no instances. Just a constants holder } public static final String DIRECT = "direct"; public static final String NOTIFY_ONLINE = "notify online"; public static final String NOTIFY_OFFLINE = "notify offline"; /** * Republished because received a restriction xml for the object. */ public static final String RESTRICTION = "restriction"; /** * Republished because embargo (activation/deactivation) on one (or more) of the restrictions */ public static final String RESTRICTIONS = "restrictions"; public static final String NOTIFY = "notify"; public static final String SORT_DATE = "sortdate"; public static final String PUBLICATION = "publication"; public static final String REPUBLICATION = "republication"; public static final String EMBARGO_PUBLISH = "embargo publication"; public static final String EMBARGO_REVOKE = "embargo revoke"; public static final String DELETION = "deletion"; public static final String NO_SCHEDULE_EVENTS = "no scheduleevents left"; public static final String SCHEDULE_EVENT = "scheduleevent"; public static final String IMAGE_EMBARGO = "image embargo"; public static final String PARENT_REVOKED = "parent revoked"; public static final String PARENT_PUBLISHED = "parent published"; public static final String AVAILABLE_SUBTITLES = "available subtitles"; public static final String MERGED = "merged %s>%s"; public static final String PARENT_MERGED = "parent merged >%s"; public static final String EXPLICIT = "explicit fill"; public static final String UNDELETED_PROGRAM_IMPORTER = "Undeleted by program importer"; public static final String SEGMENT = "segment change"; public static final String LOCATION_REVOKE = "location revoke"; public static final String LOCATION_PUBLISH = "location publish"; public static final String RCRS = "rcrs"; public static final String TYPE_CHANGED = "type of parent changed"; public static final String NUMBER_CHANGED = "number changed"; public static final String REALIZED_PREDICTION = "realized prediction %s"; public static final String REVOKED_PREDICTION = "revoked prediction %s"; /** * type:mid:reason */ public static final String REPUBLISHING_DESCENDANTS_TEMPLATE = "republishing descendants of %s:%s:%s"; public static final String NEP_EXISTS = "nep notify for non existing object"; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy