Class IcsMerger

java.lang.Object
org.exoplatform.caldav.ics.IcsMerger

@Component public class IcsMerger extends Object
Splices an event into the calendar object a server already holds.

A calendar object is not ours alone. Another client may have written overrides into the same object, with its own properties and its own VTIMEZONE, and a push that replaced the whole document would silently destroy them. So the object is read, the parts this event owns are replaced, and everything else is left exactly as it was found.

Two rules decide what "the parts this event owns" means, and both were learned the hard way. Pushing a master replaces the master — and also prunes any override whose instance the master's EXDATEs now exclude, because an override contradicting an EXDATE keeps a deleted occurrence visible on the client. Pushing an override replaces only the override for that instance.

  • Constructor Details

    • IcsMerger

      public IcsMerger()
  • Method Details

    • merge

      public String merge(String existing, String component, boolean occurrence)
      Merges one component into the object a server holds.
      Parameters:
      existing - the calendar object as fetched from the server
      component - the object this engine just built, holding one VEVENT
      occurrence - whether the component amends one instance rather than being the master
      Returns:
      the object to write back
      Throws:
      IcsParseException - when either document is not readable iCalendar
    • setAttendeeResponse

      public IcsMerger.AnswerRewrite setAttendeeResponse(String existing, Collection<String> addresses, String partStat)
      Rewrites one attendee's participation status in the object a server holds, leaving every other byte of it alone.

      The outward half of answering a meeting. An answer given in eXo — in the agenda screen, or through the tokenised link in the notification mail — only ever reached eXo's own database, so the copy on the user's calendar server went on displaying the answer it last carried. That is a wrong answer shown to its own author, and worse: the copy is also what the verification pass reads a client's answer back from, so a stale one is eventually adopted and the newer answer is silently lost.

      A targeted rewrite rather than a fresh serialisation of the whole event: the object may carry overrides and properties another client wrote, and an answer is not consent to discard them. It is also what makes the write idempotent — an object that already says this comes back with no document to write, which is what stops an answer adopted from the copy from being pushed straight back at it.

      Every VEVENT in the object is visited, master and overrides alike, because agenda propagates an answer to a series onto each of its exceptional occurrences: leaving the overrides on their old value would show the user one answer for the series and another for the instances of it they moved.

      A set of addresses, not one. A copy does not name a person the same way everywhere: their own line carries the address their CalDAV account answers to, so that a client recognises the meeting as an invitation to its owner, while every other line — and every copy written before that rule existed — carries their eXo profile address. Being handed one address and told it is the one is how this silently matched nothing on a live rig: the caller offers every address that could name this person, and whichever the object actually holds is the one rewritten.

      Parameters:
      existing - the calendar object as fetched from the server
      addresses - every address the copy might name this attendee by
      partStat - the RFC 5545 token to set, already validated by IcsText.partStat(String)
      Returns:
      whether the object names the attendee at all, and the document to write back when the answer moved
      Throws:
      IcsParseException - when the document is not readable iCalendar
    • setAttendeeResponse

      public IcsMerger.AnswerRewrite setAttendeeResponse(String existing, Collection<String> addresses, String partStat, Instant occurrence)
      Rewrites one attendee's participation status on one instance of a series, or on the whole object when no instance is named.

      An answer to one occurrence moves the override carrying that occurrence and nothing else (EXO-90489). The master's line is what every other instance of the series inherits, so rewriting it too would answer every date the attendee has not answered yet: they would stop being offered as pending, and a decline would hide the whole series. An object holding no override for that instance comes back not naming the attendee; the caller adds the override first (holdsInstance(java.lang.String, java.time.Instant)).

      Parameters:
      existing - the calendar object as fetched from the server
      addresses - every address the copy might name this attendee by
      partStat - the RFC 5545 token to set, already validated by IcsText.partStat(String)
      occurrence - the instance answered, or null for an answer to the series, which every VEVENT carries
      Returns:
      whether the visited components name the attendee at all, and the document to write back when the answer moved
      Throws:
      IcsParseException - when the document is not readable iCalendar
    • holdsInstance

      public boolean holdsInstance(String existing, Instant occurrence)
      Whether the object carries an override for one instance of its series.
      Parameters:
      existing - the calendar object as fetched from the server
      occurrence - the instance looked for
      Returns:
      true when a VEVENT's RECURRENCE-ID names that instance
      Throws:
      IcsParseException - when the document is not readable iCalendar
    • excludeOccurrence

      public String excludeOccurrence(String existing, Instant occurrence)
      Rewrites a calendar object so that it no longer produces one occurrence.

      Not a deletion of the object: RFC 4791 puts every component of a series in one object, so removing it would remove the whole series. The override carrying that instance is dropped, and the master gains an EXDATE for it — in the value type the master's own DTSTART uses, because RFC 5545 matches instances by identical value. An EXDATE written as a date-time against a date-valued series matches no instance at all, and the deleted occurrence simply reappears.

      Answering null rather than an object means nothing is left: the caller deletes the object instead of writing back a VCALENDAR with no VEVENT in it, which some servers accept and then serve to clients that choke on it.

      Parameters:
      existing - the calendar object as fetched from the server
      occurrence - the instance to exclude
      Returns:
      the object to write back, or null when nothing remains of it
      Throws:
      IcsParseException - when the document is not readable iCalendar