Class CaldavSyncStorage

java.lang.Object
org.exoplatform.caldav.storage.CaldavSyncStorage

@Component public class CaldavSyncStorage extends Object
Maps calendar pairs and their object mappings between their JPA entities and the service-layer DTOs, and is the single place hrefs are made canonical.

No business logic: what a pair means, when it may be created, whether a deletion propagates — all of that belongs to the service. What lives here is the mechanical part the service should not have to remember, above all that an href is stored decoded and without its trailing slash. A binding that compared raw hrefs would lose its pair the first time a server answered a differently-escaped one, and that is not a decision, it is an invariant.

  • Constructor Summary

    Constructors
    Constructor
    Description
     
  • Method Summary

    Modifier and Type
    Method
    Description
    static String
    An href reduced to what identifies the resource: its percent-decoded path, without a trailing slash.
    long
    countObjects(long calendarSyncId)
    How many objects a pair maps.
    void
    deleteObject(long id)
    Forgets one object mapping.
    int
    deleteObjects(long calendarSyncId)
    Drops every object mapping of a pair, leaving the pair itself in place.
    void
    deletePair(long id)
    Removes a pair and, by the foreign key, its object mappings.
    org.springframework.data.domain.Page<Long>
    getDueAccounts(org.exoplatform.caldav.model.CalendarSyncStatus status, Date before, int offset, int limit)
    The users whose bindings are due, oldest waiting first.
    org.springframework.data.domain.Page<org.exoplatform.caldav.model.CalendarSync>
    getDuePairs(org.exoplatform.caldav.model.CalendarSyncStatus status, Date before, int offset, int limit)
    One page of pairs in a given state whose last synchronisation ended before a cutoff, or has never ended.
    getMirrorEventId(long userIdentityId, long serverId, String icsUid)
    The eXo event a copy eXo wrote into this user's mirror stands for.
    org.exoplatform.caldav.model.ObjectSync
    getObjectByEvent(long calendarSyncId, long localEventId)
    The mapping for one eXo event inside a pair.
    org.exoplatform.caldav.model.ObjectSync
    getObjectByUid(long calendarSyncId, String icsUid)
    The mapping for one iCalendar object inside a pair.
    org.springframework.data.domain.Page<org.exoplatform.caldav.model.ObjectSync>
    getObjects(long calendarSyncId, int offset, int limit)
    One page of a pair's object mappings.
    org.springframework.data.domain.Page<org.exoplatform.caldav.model.ObjectSync>
    getObjectsByEvent(long localEventId, int offset, int limit)
    One page of the mappings of a single eXo event, across every user's collections.
    org.exoplatform.caldav.model.CalendarSync
    getPair(long id)
    One binding by its identifier, whoever it belongs to.
    org.exoplatform.caldav.model.CalendarSync
    getPairByLocalCalendar(long userIdentityId, long serverId, String localCalendarSyncUid)
    The pair bound to one local calendar.
    org.exoplatform.caldav.model.CalendarSync
    getPairByRemoteHref(long userIdentityId, long serverId, String remoteHref)
    The pair bound to one remote collection, matched on the canonical path.
    List<org.exoplatform.caldav.model.CalendarSync>
    getPairs(long userIdentityId, long serverId)
    Every pair a user holds on one server.
    List<org.exoplatform.caldav.model.CalendarSync>
    getPairsByOrigin(long userIdentityId, long serverId, org.exoplatform.caldav.model.SyncOrigin origin)
    The pairs of one origin a user holds on a server.
    boolean
    isEventMapped(long localEventId)
    Whether an eXo event is already mapped.
    boolean
    isMirrorOwned(long userIdentityId, long serverId, String icsUid)
    Whether this iCalendar object is a copy eXo itself wrote into the user's mirror.
    mappedEventIds(long userIdentityId, Collection<Long> localEventIds)
    Which of these eXo events already carry a copy.
    mappedEventIds(Collection<Long> localEventIds)
    Which of these eXo events carry a copy for ANYONE.
    org.exoplatform.caldav.model.ObjectSync
    saveObject(org.exoplatform.caldav.model.ObjectSync object)
    Creates or updates an object mapping, canonicalising its href on the way in.
    org.exoplatform.caldav.model.CalendarSync
    savePair(org.exoplatform.caldav.model.CalendarSync pair)
    Creates or updates a pair, canonicalising its href on the way in.

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Constructor Details

    • CaldavSyncStorage

      public CaldavSyncStorage()
  • Method Details

    • getPairs

      public List<org.exoplatform.caldav.model.CalendarSync> getPairs(long userIdentityId, long serverId)
      Every pair a user holds on one server.
      Parameters:
      userIdentityId - identity of the user
      serverId - declared server registration
      Returns:
      the user's pairs on that server
    • getPairByLocalCalendar

      public org.exoplatform.caldav.model.CalendarSync getPairByLocalCalendar(long userIdentityId, long serverId, String localCalendarSyncUid)
      The pair bound to one local calendar.
      Parameters:
      userIdentityId - identity of the user
      serverId - declared server registration
      localCalendarSyncUid - agenda's immutable calendar anchor
      Returns:
      the pair, or null when the calendar is not bound
    • getPairByRemoteHref

      public org.exoplatform.caldav.model.CalendarSync getPairByRemoteHref(long userIdentityId, long serverId, String remoteHref)
      The pair bound to one remote collection, matched on the canonical path.

      Resolved in memory over the user's own pairs rather than by a query: the href column is too long to index on MySQL under utf8mb4, and a person's pairs are few. Querying it would have meant either a scan or a schema contortion, for a set that fits in a handful of rows.

      Parameters:
      userIdentityId - identity of the user
      serverId - declared server registration
      remoteHref - the collection href, in any spelling
      Returns:
      the pair, or null when the collection is not bound
    • getPairsByOrigin

      public List<org.exoplatform.caldav.model.CalendarSync> getPairsByOrigin(long userIdentityId, long serverId, org.exoplatform.caldav.model.SyncOrigin origin)
      The pairs of one origin a user holds on a server. A list even for SyncOrigin.MIRROR, which should be single: the database cannot enforce that uniqueness, so the caller is given what is actually there rather than the first of several.
      Parameters:
      userIdentityId - identity of the user
      serverId - declared server registration
      origin - which side created the collection
      Returns:
      the matching pairs
    • getDuePairs

      public org.springframework.data.domain.Page<org.exoplatform.caldav.model.CalendarSync> getDuePairs(org.exoplatform.caldav.model.CalendarSyncStatus status, Date before, int offset, int limit)
      One page of pairs in a given state whose last synchronisation ended before a cutoff, or has never ended.
      Parameters:
      status - the state to select
      before - pairs last synchronised strictly before this instant
      offset - page offset, in pages
      limit - page size
      Returns:
      one page of due pairs, oldest synchronisation first
    • getDueAccounts

      public org.springframework.data.domain.Page<Long> getDueAccounts(org.exoplatform.caldav.model.CalendarSyncStatus status, Date before, int offset, int limit)
      The users whose bindings are due, oldest waiting first. The account-wise form of getDuePairs(org.exoplatform.caldav.model.CalendarSyncStatus, java.util.Date, int, int), and what the sweep asks: batching bindings let one user's collections fill a whole run, so no other account was reached at all.
      Parameters:
      status - the binding state that counts as sweepable
      before - bindings last synchronised strictly before this instant
      offset - page index
      limit - how many users one page carries
      Returns:
      one page of user identities
    • getPair

      public org.exoplatform.caldav.model.CalendarSync getPair(long id)
      One binding by its identifier, whoever it belongs to.

      Returns it without checking ownership on purpose: the check belongs to the caller that knows who is asking, and hiding it here would make a service look safe while the storage quietly decided for it.

      Parameters:
      id - the binding's identifier
      Returns:
      the binding, or null when there is none
    • savePair

      @Transactional public org.exoplatform.caldav.model.CalendarSync savePair(org.exoplatform.caldav.model.CalendarSync pair)
      Creates or updates a pair, canonicalising its href on the way in.
      Parameters:
      pair - the pair to persist
      Returns:
      the persisted pair, carrying its identifier
    • deletePair

      @Transactional public void deletePair(long id)
      Removes a pair and, by the foreign key, its object mappings.
      Parameters:
      id - technical identifier of the pair
    • getObjectByUid

      public org.exoplatform.caldav.model.ObjectSync getObjectByUid(long calendarSyncId, String icsUid)
      The mapping for one iCalendar object inside a pair.
      Parameters:
      calendarSyncId - the pair
      icsUid - the iCalendar UID
      Returns:
      the mapping, or null when the object is unknown
    • isMirrorOwned

      public boolean isMirrorOwned(long userIdentityId, long serverId, String icsUid)
      Whether this iCalendar object is a copy eXo itself wrote into the user's mirror.

      Ownership expressed per object rather than per collection. Until now a mirror copy was protected by where it lived — the inbound sweep skipped the dedicated collection wholesale — so pointing the mirror at a calendar the inbound half also reads would have removed the protection entirely, and eXo would have imported its own copies back as duplicate personal events.

      The mapping table is the authority because its row is saved in the same flow as the PUT that created the object: by the time any inbound pass can see the object, the row exists. The one window it does not cover is an interruption between the two — the PUT went through and the row was never saved — which leaves an unowned copy the next push reconciles.

      Parameters:
      userIdentityId - identity of the user whose copies are asked about
      serverId - the declared server registration
      icsUid - the iCalendar UID being imported
      Returns:
      true when one of this user's mirror pairs already maps that UID
    • getMirrorEventId

      public Long getMirrorEventId(long userIdentityId, long serverId, String icsUid)
      The eXo event a copy eXo wrote into this user's mirror stands for.

      The companion of isMirrorOwned(long, long, String) and asked in the same breath (EXO-89807): the inbound half recognises one of eXo's own copies and drops it, but the owner's answer is written on that copy and has to be recorded against something. The mapping that knows which event lives on the MIRROR pair, and the pair reading the collection is a different one, so nothing pair-scoped can answer this.

      Two mirror pairs holding the same UID is not a state this connector creates — a user has one mirror per server — and if it ever arises, the copies stand for different events and no answer can be attributed to either without guessing. Ambiguity therefore answers "no event", the same as "never pushed": nothing is recorded rather than something recorded against the wrong meeting.

      Parameters:
      userIdentityId - identity of the user whose copies are asked about
      serverId - the declared server registration
      icsUid - the iCalendar UID being read
      Returns:
      the event the copy stands for, or null when it is not ours, names no event, or names more than one
    • getObjectByEvent

      public org.exoplatform.caldav.model.ObjectSync getObjectByEvent(long calendarSyncId, long localEventId)
      The mapping for one eXo event inside a pair.
      Parameters:
      calendarSyncId - the pair
      localEventId - the eXo event
      Returns:
      the mapping, or null when the event has never been pushed
    • getObjects

      public org.springframework.data.domain.Page<org.exoplatform.caldav.model.ObjectSync> getObjects(long calendarSyncId, int offset, int limit)
      One page of a pair's object mappings.
      Parameters:
      calendarSyncId - the pair
      offset - page offset, in pages
      limit - page size
      Returns:
      one page of mappings, by identifier
    • getObjectsByEvent

      public org.springframework.data.domain.Page<org.exoplatform.caldav.model.ObjectSync> getObjectsByEvent(long localEventId, int offset, int limit)
      One page of the mappings of a single eXo event, across every user's collections.

      The set an edit of that event is allowed to reach — every copy that already exists, and nothing else.

      Parameters:
      localEventId - the eXo event
      offset - page offset, in pages
      limit - page size
      Returns:
      one page of mappings, by identifier
    • countObjects

      public long countObjects(long calendarSyncId)
      How many objects a pair maps.
      Parameters:
      calendarSyncId - the pair
      Returns:
      the mapping count
    • isEventMapped

      public boolean isEventMapped(long localEventId)
      Whether an eXo event is already mapped. The question the backfill asks before creating a row, and the reason re-running it creates nothing.
      Parameters:
      localEventId - the eXo event
      Returns:
      true when a mapping exists
    • mappedEventIds

      public Set<Long> mappedEventIds(long userIdentityId, Collection<Long> localEventIds)
      Which of these eXo events already carry a copy. The batch form of isEventMapped(long), for callers holding a list: the seeding pass asks about a user's whole upcoming window on every sweep, and asking one event at a time made the steady state — where every one of them is already mapped — cost a query per meeting to learn there was nothing to do.
      Parameters:
      userIdentityId - the identity whose mappings count, so the copy made for the first attendee does not answer for every other attendee
      localEventIds - the eXo events to ask about
      Returns:
      the identifiers among them that are mapped, empty when none are
    • mappedEventIds

      public Set<Long> mappedEventIds(Collection<Long> localEventIds)
      Which of these eXo events carry a copy for ANYONE. Kept for the callers that genuinely ask the global question; a per-user pass wants mappedEventIds(long, Collection) instead, or the first attendee copied answers for every other attendee.
      Parameters:
      localEventIds - the eXo events to ask about
      Returns:
      the identifiers among them that are mapped
    • saveObject

      @Transactional public org.exoplatform.caldav.model.ObjectSync saveObject(org.exoplatform.caldav.model.ObjectSync object)
      Creates or updates an object mapping, canonicalising its href on the way in.
      Parameters:
      object - the mapping to persist
      Returns:
      the persisted mapping, carrying its identifier
    • deleteObjects

      @Transactional public int deleteObjects(long calendarSyncId)
      Drops every object mapping of a pair, leaving the pair itself in place.
      Parameters:
      calendarSyncId - the pair
      Returns:
      how many mappings were removed
    • deleteObject

      public void deleteObject(long id)
      Forgets one object mapping.

      What a mapping with nothing behind it gets: dropping it is how the object becomes importable again, rather than being skipped for ever by a row that describes an event no longer there.

      Parameters:
      id - the mapping's identifier
    • canonicalHref

      public static String canonicalHref(String href)
      An href reduced to what identifies the resource: its percent-decoded path, without a trailing slash.

      The Java counterpart of the browser connector's collectionPath. The same collection is written %40 by one server and @ by a client, reported with and without a trailing slash, and reached through more than one host — none of which makes it a different collection. An href that cannot be parsed is returned trimmed rather than rejected: this is a normalisation, not a validation, and refusing to store an odd href would lose the binding rather than protect it.

      Parameters:
      href - a collection or object href, absolute or relative
      Returns:
      the canonical path, or the trimmed input when it cannot be parsed