Class CaldavServerOwnerService

java.lang.Object
org.exoplatform.caldav.service.CaldavServerOwnerService

@Service public class CaldavServerOwnerService extends Object
Asks the server whose each calendar in an account's view is — once per account per pass at most, and mostly from a cache (EXO-90347).

BlueMind only, deliberately. The owner is read from BlueMind's subscription listing through its own REST API (BlueMindSubscriptionClient.ownersOf(org.exoplatform.caldav.client.CalDavEndpoint), kept by CaldavServerOwnerStorage), because BlueMind is the server on which nothing over CalDAV tells a subscribed colleague's calendar from the account's own (BlueMindContainerNaming). The RFC analogue would be DAV:owner (RFC 3744 §5.1) read per collection, which Stalwart answers truthfully and the classifier already reads through CalendarCollection#isSharedWith; a server that answers it wrongly has, so far, been BlueMind alone, so no second implementation is made here, and a server of any other shape gets AccountCalendarOwners.silent() — its behaviour is unchanged by this witness.

Which servers are BlueMind's: an account whose principal has BlueMind's …/principals/__uids__/<uid>/ spelling (BlueMindContainerNaming.userUidOf(java.lang.String)), the same rule the naming witness is read under. A server of that spelling that is not BlueMind — Apple's retired Calendar Server used it — would be asked and fail to answer, and its eXo-shaped collections that nobody here recognises would then wait rather than be adopted; no such server is known to be in use.

What one pass costs. The witness handed out is deferred: nothing is read until a collection needs it. Its first question is answered from the storage's cache — one hour by default, per account — and reaches the server only on a miss. A calendar the cached listing does not name is the one thing that forces a read within the TTL: once per account per pass, however many such calendars the pass meets, so a share made after the entry was written is classified in the pass that first sees it. A session BlueMind opened as an entry other than the account's recorded principal — credentials re-pointed since the entry was written — spends that same one refresh; if the fresh listing still names another entry, the witness is unavailable.

Fail closed, and say so once. A listing that cannot be had — no login and password to open the REST session with, a refused or unreachable server, a mismatched entry, an answer of the wrong shape — is AccountCalendarOwners.unavailable() for the pass, on which the classifier adopts nothing it does not already recognise; nothing is cached for it, so the next pass tries again. Said once per account per process at warn and at debug after that: the condition can last, and a line every five minutes per user would bury the one that mattered.

Evicted when eXo knows better. A share eXo grants or revokes and a subscription it follows change what the sharee's mailbox sees, and the account itself changes hands on connect, disconnect or new credentials; each drops the entry (evict(long, long)) on the node that made the change. The cache is node-local: another node learns of it within the TTL, or sooner through the miss-refresh above. Connect and disconnect are made by CaldavConnectorServiceImpl, a Kernel component that reaches this bean through the Kernel/Spring bridge — which exports @Service beans and nothing else (KernelContainerLifecyclePlugin), so this class is one, as every sibling resolved through that seam is; a plain @Component would leave those two evictions silently unwired.

The one cost that is per pass. A mismatched entry — the listing names an entry other than the account's recorded principal — spends the pass's refresh, and the fresh listing is kept (@CachePut) whether or not it is believed. When the mismatch is a stale entry, the refresh settles it and the next pass is served from the cache. When it is durable — the REST session and the DAV principal naming two different entries for one credential set, a case not observed — every pass and every agenda open spends one REST read and gets the unavailable witness: the cost the classification had before the cache, said once at warn. Dropping the entry instead would make each such pass two reads rather than one.

The product using the user's stored credentials during their own pass is what every DAV request of that pass already does; nothing here stores, logs or forwards them, and the session is closed by the client.

  • Constructor Details

    • CaldavServerOwnerService

      public CaldavServerOwnerService()
  • Method Details

    • ownersOf

      public AccountCalendarOwners ownersOf(long userIdentityId, CalDavEndpoint endpoint, String principal)
      The server's word on the owners of an account's calendars, to be heard at most once per pass and read from the server at most once more.

      Deferred: nothing is fetched here. The listing is read the first time the classifier asks about a calendar the other witnesses could not settle — from the cache when it holds the account, else from the server — and kept for the rest of the pass; a pass that never asks costs no request (AccountCalendarOwners.deferred(Supplier, Supplier)).

      Parameters:
      userIdentityId - the connected user, whose stored credentials open the session and whose entry the cache keeps
      endpoint - the account's DAV endpoint, minted from the registry for that user's eXo login
      principal - the account's own current-user-principal, as the discovery walk answered it; null when the server named none
      Returns:
      the witness: silent for a server that is not BlueMind's, deferred otherwise
    • evict

      public void evict(long userIdentityId, long serverId)
      Drops what the cache holds for one account, because eXo itself changed what that account sees or who it is.
      Parameters:
      userIdentityId - the connected user
      serverId - the declared server registration, zero for the legacy property