Record Class CalendarCollection

java.lang.Object
java.lang.Record
org.exoplatform.caldav.client.CalendarCollection
Record Components:
href - the collection's server-absolute raw path, trailing slash as the server sent it
displayName - the collection's display name, or null — data, never identity: nothing may key on it
ctag - the CalendarServer ctag, or null when the server has none
syncToken - the RFC 6578 sync token, or null when unsupported
color - the Apple calendar-color, or null
writable - whether the current user's privilege set carries write — false as well when the server answered no privilege set at all, which privilegesAnswered is there to tell apart
components - the component types the collection declares, empty when the server did not say
owner - the DAV:owner of the collection as a server-absolute raw path, or null when the server answered none — the principal the collection belongs to, which is not always the user listing it (EXO-90235)
privilegesAnswered - whether the server answered a current-user-privilege-set at all; false is the Google shape, where writable says nothing because nothing was said

public record CalendarCollection(String href, String displayName, String ctag, String syncToken, String color, boolean writable, Set<String> components, String owner, boolean privilegesAnswered) extends Record
One calendar collection as a PROPFIND listed it: everything the sync engine binds a pair on, and nothing it does not need. Hrefs are answered as server-absolute raw paths — exactly the shape they travel back out in, so a round trip never re-encodes them.
  • Constructor Details

    • CalendarCollection

      public CalendarCollection(String href, String displayName, String ctag, String syncToken, String color, boolean writable)
      A collection whose server declared no component set.

      The six-argument form every caller used before the set was read: an undeclared set is an empty one, which holdsEvents() reads as "the server did not say" — the RFC 4791 default of supporting everything. It names no owner and answers no privilege set either, so what it describes is a collection nothing has been said about — never a share (see isSharedWith(String)).

      Parameters:
      href - the collection path
      displayName - the name the server gives it
      ctag - the collection change tag
      syncToken - the RFC 6578 sync token
      color - the colour the server carries
      writable - whether the caller may write to it
    • CalendarCollection

      public CalendarCollection(String href, String displayName, String ctag, String syncToken, String color, boolean writable, Set<String> components)
      A collection whose server named no owner and answered no privilege set.

      The seven-argument form every caller used before ownership was read; it keeps meaning what it meant, a collection about which nothing beyond its own properties is known, so a record built by hand never classifies as a share by accident.

      Parameters:
      href - the collection path
      displayName - the name the server gives it
      ctag - the collection change tag
      syncToken - the RFC 6578 sync token
      color - the colour the server carries
      writable - whether the caller may write to it
      components - the component types it declares
    • CalendarCollection

      public CalendarCollection(String href, String displayName, String ctag, String syncToken, String color, boolean writable, Set<String> components, String owner, boolean privilegesAnswered)
      Creates an instance of a CalendarCollection record class.
      Parameters:
      href - the value for the href record component
      displayName - the value for the displayName record component
      ctag - the value for the ctag record component
      syncToken - the value for the syncToken record component
      color - the value for the color record component
      writable - the value for the writable record component
      components - the value for the components record component
      owner - the value for the owner record component
      privilegesAnswered - the value for the privilegesAnswered record component
  • Method Details

    • holdsEvents

      public boolean holdsEvents()
      Whether this collection holds events at all.

      RFC 4791 §5.2.3 makes supported-calendar-component-set optional, and a collection that omits it supports every component. An empty set is therefore a server that did not say, not a server that said "nothing" — both answer true. Only an explicit set without VEVENT answers false, which is what keeps a task list from becoming a calendar.

      Returns:
      true unless the server explicitly excluded events
    • isSharedWith

      public boolean isSharedWith(String currentUserPrincipal)
      Whether the server says this collection is somebody else's, granted to the user rather than owned by them.

      Two signals, either of which is enough, both read from what the server answered and neither guessed from the path. The collection names an owner that is not the user's own principal; or the server answered a privilege set that grants no write. Observed live on Stalwart 0.16 (EXO-90235): after a colleague granted the user DAV:read on her calendar, the Depth-1 listing of the user's own home returned her collection at her path, with DAV:owner naming her principal and read, read-current-user-privilege-set as the whole privilege set — both signals at once. The engine had read neither, and turned her calendar into the user's own, editable one.

      Silence is not a signal. A server that names no owner cannot be compared, and one that answers no privilege set has not said the user may not write — Google answers none, and reading its silence as read-only would turn every Google calendar into a share. So an unknown owner, an unknown principal and an unanswered privilege set each leave their signal off, and a collection nothing was said about is the user's own, exactly as it was before ownership was read. Only what the server stated counts against it.

      Compared as paths, not as strings: Stalwart spells the principal segment with the login percent-encoded (/dav/pal/alice%40stalwart.local/) and nothing forbids a server from answering the owner encoded and the principal not, or one with a trailing slash and the other without. A comparison that failed on either would silently classify the user's own calendars as shares, which is the reverse of this method's defect.

      What this does not tell apart is a share from a subscribed copy on a server that misreports one: BlueMind lists a calendar the user subscribed to under their own home, names them as its owner and grants them the full privilege set, so neither signal fires there. The signal that does hold there is not the server's but this deployment's — the slug of a colleague's eXo calendar carries the anchor eXo exported it under — and it is read beside this method, not inside it, by CaldavOutboundService#ownershipOf (EXO-90234), which the sweep and the read-through both classify with. This method deliberately keeps the two server rules above as the whole of what it reads.

      Parameters:
      currentUserPrincipal - the current-user-principal of the account listing this collection, as a server-absolute path; null or blank when the server named none, which switches the owner signal off
      Returns:
      true when the server said the collection is not the user's own to write into
    • ownerIfAnother

      public String ownerIfAnother(String currentUserPrincipal)
      The owner the server named, when it is somebody other than the user listing the collection — the principal worth naming as "who shared this" (EXO-90237).

      The same comparison as the owner signal of isSharedWith(String), answered as a path rather than a boolean. Needed because a collection can be a share by the privilege signal alone while still naming the user themself as owner — a calendar under their own home the server will not let them write — and naming that owner would tell the user the calendar was shared by themself. Null when the server named no owner, when the account's own principal is unknown (nothing to compare against, so nobody is named rather than possibly the user), and when the owner is the user.

      Parameters:
      currentUserPrincipal - the listing account's principal path, may be null or blank
      Returns:
      the owner's server-absolute raw path when it names another principal, else null
    • principalPathOf

      public static String principalPathOf(String path)
      A principal path in the one shape two answers of the same server can be compared in: percent-decoding undone, trailing slash dropped.

      Decoded through URI rather than URLDecoder, which would also turn a + into a space — a transformation meant for form bodies, not paths. A value URI cannot parse is compared as it came, trimmed of its slash: a malformed owner href is the server's to explain, and treating it as "another owner" would hide a calendar over a spelling.

      Public because it is also the form a connection's own principal is recorded in (EXO-90243): a principal stored in one spelling and an owner compared in another would fail to meet, and the owner of a share would never be recognised as the eXo user connected under that principal.

      Parameters:
      path - a server-absolute raw path, not null
      Returns:
      the comparable form
    • toString

      public final String toString()
      Returns a string representation of this record class. The representation contains the name of the class, followed by the name and value of each of the record components.
      Specified by:
      toString in class Record
      Returns:
      a string representation of this object
    • hashCode

      public final int hashCode()
      Returns a hash code value for this object. The value is derived from the hash code of each of the record components.
      Specified by:
      hashCode in class Record
      Returns:
      a hash code value for this object
    • equals

      public final boolean equals(Object o)
      Indicates whether some other object is "equal to" this one. The objects are equal if the other object is of the same class and if all the record components are equal. Reference components are compared with Objects::equals(Object,Object); primitive components are compared with '=='.
      Specified by:
      equals in class Record
      Parameters:
      o - the object with which to compare
      Returns:
      true if this object is the same as the o argument; false otherwise.
    • href

      public String href()
      Returns the value of the href record component.
      Returns:
      the value of the href record component
    • displayName

      public String displayName()
      Returns the value of the displayName record component.
      Returns:
      the value of the displayName record component
    • ctag

      public String ctag()
      Returns the value of the ctag record component.
      Returns:
      the value of the ctag record component
    • syncToken

      public String syncToken()
      Returns the value of the syncToken record component.
      Returns:
      the value of the syncToken record component
    • color

      public String color()
      Returns the value of the color record component.
      Returns:
      the value of the color record component
    • writable

      public boolean writable()
      Returns the value of the writable record component.
      Returns:
      the value of the writable record component
    • components

      public Set<String> components()
      Returns the value of the components record component.
      Returns:
      the value of the components record component
    • owner

      public String owner()
      Returns the value of the owner record component.
      Returns:
      the value of the owner record component
    • privilegesAnswered

      public boolean privilegesAnswered()
      Returns the value of the privilegesAnswered record component.
      Returns:
      the value of the privilegesAnswered record component