Class CaldavServerService

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

@Service public class CaldavServerService extends Object
The registry of CalDAV servers a deployment offers its users: what an administrator declares in the third section of the agenda administration page. Holds all the business logic — who may write (platform administrators), the provider naming that bridges each registration to an agenda remote provider with zero agenda backend change, the seeding that migrates the legacy configuration property, and the URL resolution the per-user connector settings read.
  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    static final String
    Legacy configuration property telling whether the single CalDAV connector is enabled; read once, to give the seed row its initial activation.
    static final String
    Provider name of the seed registration — the name the kernel plugin has always registered on the agenda side — and the prefix every derived provider name starts with.
    static final String
    Legacy configuration property holding the single CalDAV server URL.
    static final String
    Address the Stalwart seed row falls back to when the deployment named none through CALDAV_SERVER_URL_PROPERTY.
    static final String
    The name the Stalwart seed is declared under.
    static final String
    The refusal of a write channel an installed add-on offers, on a registration that add-on does not recognise as one of its servers, as the save would leave it: the channel exists, so the refusal does not send the administrator to standard CalDAV, which would bring back the duplicates the channel avoids.
    static final String
    The refusal of a write channel the server behind the registration cannot speak (EXO-90307): BLUEMIND_IMPORT calls BlueMind's own REST endpoints and is offered to a BlueMind registration only.
  • Constructor Summary

    Constructors
    Constructor
    Description
     
  • Method Summary

    Modifier and Type
    Method
    Description
    boolean
    canEdit(String username)
    Whether a user may edit the registry: platform administrators only.
    Whether each declared provider asks its user for anything, keyed by provider name.
    long
    countServerReferences(long serverId)
    Counts the connected accounts referencing a registration: the user contexts holding a CaldavServerId setting whose value is this row's id.
    org.exoplatform.caldav.model.CaldavServer
    createServer(org.exoplatform.caldav.model.CaldavServer server, String username)
    Declares a new CalDAV server.
    void
    deleteServer(long serverId, String username)
    Deletes a declared server.
    List<org.exoplatform.caldav.model.ForeignWriter>
    getForeignWriters(long serverId, String username)
    Which other eXo deployments have been seen writing meeting copies into this server's accounts (EXO-89824) — what the administration drawer shows under the behaviours this server has been seen having.
    org.exoplatform.caldav.model.CaldavServerProviderConfig
    getProviderConfig(long serverId, String username)
    The provider configuration of a declared server, as an administration screen may see it: every field except the secret ones, which are never read back on this path, and the keys of the secret fields that have a value stored.
    org.exoplatform.caldav.model.CaldavServer
    getServerById(long serverId)
    One registration by its technical identifier.
    The stored image of a registration, as a stream the REST layer serves — email-connector's illustration mechanics.
    List<org.exoplatform.caldav.model.CaldavServer>
     
    The users connected to a server, by their identity: what a change of its provider disconnects.
    boolean
    isConnected(org.exoplatform.caldav.model.CaldavUserSetting settings)
    Whether a stored account is a connection at all - the addon's single definition, which push, inbound, outbound, read, sync, the relay and the connector service all ask rather than restate.
    The credentials provider an account's registration names, when that provider is not installed: the one state in which no account, typed or provider-backed, can be stored on it.
    void
    recordForeignWriter(long serverId, String authority)
    Records that another eXo deployment was seen writing a meeting copy into an account on this server.
    org.exoplatform.caldav.model.CaldavServer
    resolveServer(Long serverId)
    Resolves the registration a user's account reads and pushes through: the row the account references when it references one and that row still exists, else the seed row, else null.
    Resolves the CalDAV base URL a user's account reads and pushes through: the URL of the resolved registration, else null — the caller then falls back to the legacy configuration property, which is today's behaviour for deployments that never touched the registry.
    protected void
    Seeds an EMPTY registry — never over an administrator's rows — with the host's own default and every contributed seed: Stalwart, the host's default, under the fixed legacy provider name so accounts connected before the registry existed keep resolving; its URL comes from the legacy property when set, else the literal default, and its activation from the legacy enabled property (historically enabled). Every contributed seed (EXO-90737), in the order CalendarServerSeedRegistry.effectiveSeeds() gives them — one per product — as a normally-named row whose agenda remote provider is upserted here, since no kernel plugin declares it.
    org.exoplatform.caldav.model.CaldavServer
    setServerActive(long serverId, boolean active, String username)
    Activates or deactivates a declared server, propagating the switch to the agenda remote provider so the connector appears in — or leaves — every user's connectors list.
    void
    Hands the seeding of the registry to the portal container's post-create phase — the same deferral agenda's own provider plugin uses — because a seeded row needs its agenda remote provider written, and agenda's kernel services are only safely callable once the portal container is up.
    The declared provider names whose credentials provider is not installed — an add-on's, while the add-on is not or has not started yet.
    org.exoplatform.caldav.model.CaldavServer
    updateServer(org.exoplatform.caldav.model.CaldavServer server, String username)
    Updates a declared server: name, description, URL, activation, whether the copies pushed to it carry answer links, and the two lists of behaviours this server is excused for.

    Methods inherited from class java.lang.Object

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

    • CALDAV_PROVIDER_NAME

      public static final String CALDAV_PROVIDER_NAME
      Provider name of the seed registration — the name the kernel plugin has always registered on the agenda side — and the prefix every derived provider name starts with.
      See Also:
    • CALDAV_SERVER_URL_PROPERTY

      public static final String CALDAV_SERVER_URL_PROPERTY
      Legacy configuration property holding the single CalDAV server URL. Becomes the seed row, then stays forever as a deprecated fallback for deployments whose registry is empty. Never dropped.
      See Also:
    • CALDAV_ENABLED_PROPERTY

      public static final String CALDAV_ENABLED_PROPERTY
      Legacy configuration property telling whether the single CalDAV connector is enabled; read once, to give the seed row its initial activation.
      See Also:
    • STALWART_SERVER_NAME

      public static final String STALWART_SERVER_NAME
      The name the Stalwart seed is declared under.
      See Also:
    • DEFAULT_STALWART_URL

      public static final String DEFAULT_STALWART_URL
      Address the Stalwart seed row falls back to when the deployment named none through CALDAV_SERVER_URL_PROPERTY.

      It is a placeholder, and it is one on purpose (EXO-89794). This constant used to read http://localhost:8888/dav/cal/{username}/ — the local development rig — so a fresh install shipped an active registration pointing at its own loopback interface, through which a connected user could drive the relay's verbs at a row nobody had typed. A product default must not name a host that exists only on a developer's machine, and it must certainly not arrive switched on.

      The replacement is an RFC 2606 .invalid name, which is guaranteed never to resolve: it cannot be a live target under any deployment's resolver, it reads unmistakably as "replace me", and it fails the address check — which is what makes the row arrive inactive, see seedDefaultServers(). The rig keeps working the documented way, by setting CALDAV_SERVER_URL_PROPERTY (plus the opt-ins the address check reads); it no longer works by being the shipped default.

      See Also:
    • WRITE_CHANNEL_NOT_SUPPORTED_MESSAGE

      public static final String WRITE_CHANNEL_NOT_SUPPORTED_MESSAGE
      The refusal of a write channel the server behind the registration cannot speak (EXO-90307): BLUEMIND_IMPORT calls BlueMind's own REST endpoints and is offered to a BlueMind registration only.
      See Also:
    • WRITE_CHANNEL_NOT_RECOGNISED_MESSAGE

      public static final String WRITE_CHANNEL_NOT_RECOGNISED_MESSAGE
      The refusal of a write channel an installed add-on offers, on a registration that add-on does not recognise as one of its servers, as the save would leave it: the channel exists, so the refusal does not send the administrator to standard CalDAV, which would bring back the duplicates the channel avoids. The add-on alone knows what makes a registration its own (BlueMind's: its name), so the sentence names no rule.
      See Also:
  • Constructor Details

    • CaldavServerService

      public CaldavServerService()
  • Method Details

    • start

      @PostConstruct public void start()
      Hands the seeding of the registry to the portal container's post-create phase — the same deferral agenda's own provider plugin uses — because a seeded row needs its agenda remote provider written, and agenda's kernel services are only safely callable once the portal container is up.

      When that is, exactly. This bean is created while the bridge finishes the Spring contexts one by one in ascending priority, and by then the portal container reports itself started, so the kernel runs a post-create task registered now immediately, on this thread: seeding happens while this web application's context finishes, before the contexts of higher priority — the BlueMind add-on's among them — have created their singletons. A contributed seed is found anyway: the bridge registered every exported bean into this context before any context finished, and reading the contribution creates it in its own context on demand (CalendarServerSeedRegistry).

    • seedDefaultServers

      protected void seedDefaultServers()
      Seeds an EMPTY registry — never over an administrator's rows — with the host's own default and every contributed seed:
      • Stalwart, the host's default, under the fixed legacy provider name so accounts connected before the registry existed keep resolving; its URL comes from the legacy property when set, else the literal default, and its activation from the legacy enabled property (historically enabled).
      • Every contributed seed (EXO-90737), in the order CalendarServerSeedRegistry.effectiveSeeds() gives them — one per product — as a normally-named row whose agenda remote provider is upserted here, since no kernel plugin declares it. The seed says what the row carries: its name, address, excusals, destination calendar and write channel; the host alone decides its activation and its provider. A seed whose name is already taken by a row seeded in this pass is left out, so a pass never writes two rows of one name. A fresh install receives a BlueMind row only from such a seed, the BlueMind add-on's.

        answerLinksInCopy is stated rather than defaulted — the model is built positionally through its all-arguments constructor, so the field initialiser is overwritten whatever the seed passes, and the seed passes true. It happens to be the model's own default and the presets' value too, so they agree; it is simply stated twice rather than once.

      A seeded row is switched ON only when its address passes the same check an administrator's would (EXO-89794). Seeding used to be exempt from the address check of EXO-89774 altogether, which left the loophole that check was written to close: a fresh install carried an active registration at http://localhost:8888/dav/cal/{username}/, so a connected user could drive the relay's allowed verbs at loopback through a row nobody had typed. Neither the check nor the seeds are the thing to drop; what had to go is the row arriving switched on without ever meeting the check.

      So the seeds are still written — they are the pre-filled form an administrator edits, and skipping them would leave a fresh install with an empty administration screen and no hint of what belongs in it — but their activation is the address check's answer, not a constant:

      • An address the deployment actually named and vouched for — through CALDAV_SERVER_URL_PROPERTY, plus whichever of the four exo.agenda.caldav.server.* opt-ins that address needs — passes, and the row is seeded active exactly as before. This is how the local development rig keeps working: through its documented properties, not through a default that walks past them.
      • Anything else — both shipped placeholders included — is seeded inactive, and said out loud in the log (see isDeclarableSeedAddress(String, String)). Inactive is the honest state for an address nobody has vouched for: no user is offered the connector, nothing can be driven at it, and switching it on later is a write that setServerActive(long, boolean, String) already puts through the full check. The administrator edits the row, which is checked too, and turns it on — the check is never bypassed, only deferred to the person who knows the address.

      This governs a fresh registry and nothing else. The guard below is the whole upgrade story: a deployment that already holds rows returns before any of this runs, so an install that has been serving its users for months does not find its servers deactivated — or judged at all — by taking this version — nor finds a row added because a contribution was installed after its first boot. That pass is logged, naming the seeds it did not write.

    • isConnected

      public boolean isConnected(org.exoplatform.caldav.model.CaldavUserSetting settings)
      Whether a stored account is a connection at all - the addon's single definition, which push, inbound, outbound, read, sync, the relay and the connector service all ask rather than restate.

      It used to be written out in each of them as "a username and a password", and that reading is wrong for a connection whose material the platform produces: there is no password to store, so all seven called such an account disconnected while the screen showed it connected, and nothing ever synchronised. The rule is: an account is named, and either it carries a password or its provider, installed, produces the material on its behalf.

      A typed account settles on the spot, without reading the registry: this runs on every sweep.

      Parameters:
      settings - the stored account, possibly null
      Returns:
      true when the account can actually be used
    • connectionRequirements

      public Map<String,Boolean> connectionRequirements()
      Whether each declared provider asks its user for anything, keyed by provider name.

      This is what a browser needs before showing a connect button: a provider that needs nothing must connect in a click rather than open a form the user cannot fill. Only the providers the registry actually names are answered - a user is entitled to know about the connectors offered to them, not about how the instance configures its providers, whose registry is administrators-only.

      Silence means ask. A registration naming no provider contributes nothing, and a seam that is absent answers true: a connector list that cannot tell must send the user to a form, never connect on its own. So does a provider that is not installed — an add-on's, while the add-on is not — which the resolver reports once per name rather than failing the whole listing; such a provider is told apart from one that asks by unavailableProviders(), whose servers cannot be connected at all until it is installed.

      Returns:
      one entry per declared provider name, true when the user must supply something
    • unavailableProviders

      public Set<String> unavailableProviders()
      The declared provider names whose credentials provider is not installed — an add-on's, while the add-on is not or has not started yet. A server configured with one of them cannot be connected, by a click or by a form, until it is: its connect refuses before storing anything. This is what lets a browser tell such a server apart from one whose provider asks the user for credentials, which connectionRequirements() answers alike.
      Returns:
      the declared provider names that are not installed, empty when the credentials seam is absent
    • missingProviderOf

      public String missingProviderOf(Long serverId)
      The credentials provider an account's registration names, when that provider is not installed: the one state in which no account, typed or provider-backed, can be stored on it.
      Parameters:
      serverId - registration the account references, or null for the seed row
      Returns:
      the missing provider's name, or null when the registration resolves to none, names none, or its provider is installed
    • getServers

      public List<org.exoplatform.caldav.model.CaldavServer> getServers()
    • getServerById

      public org.exoplatform.caldav.model.CaldavServer getServerById(long serverId) throws org.exoplatform.commons.exception.ObjectNotFoundException
      One registration by its technical identifier.
      Parameters:
      serverId - technical identifier of the registration
      Returns:
      the registration
      Throws:
      org.exoplatform.commons.exception.ObjectNotFoundException - when no registration carries that id
    • getForeignWriters

      public List<org.exoplatform.caldav.model.ForeignWriter> getForeignWriters(long serverId, String username) throws IllegalAccessException
      Which other eXo deployments have been seen writing meeting copies into this server's accounts (EXO-89824) — what the administration drawer shows under the behaviours this server has been seen having.
      Parameters:
      serverId - technical identifier of the registration
      username - the user asking
      Returns:
      the deployments seen writing here, most recently seen first, empty when none has been
      Throws:
      IllegalAccessException - when the user may not administer servers
    • recordForeignWriter

      public void recordForeignWriter(long serverId, String authority)
      Records that another eXo deployment was seen writing a meeting copy into an account on this server.

      Called from the inbound pass, and never allowed to matter to it. Nothing is imported, skipped, removed or repaired differently on the strength of this record: what to do with a foreign copy is a product decision nobody has taken, and the wrong one destroys real calendar entries. It is written so that an administrator opening the drawer can see what until now existed only as a line in platform.log.

      Parameters:
      serverId - technical identifier of the registration the copy was read from
      authority - how the other deployment names itself in the copies it writes — host and port, as CaldavInboundService read it off the copy's event link
    • getProviderConfig

      public org.exoplatform.caldav.model.CaldavServerProviderConfig getProviderConfig(long serverId, String username) throws IllegalAccessException, org.exoplatform.commons.exception.ObjectNotFoundException
      The provider configuration of a declared server, as an administration screen may see it: every field except the secret ones, which are never read back on this path, and the keys of the secret fields that have a value stored.
      Parameters:
      serverId - technical identifier of the registration
      username - the user asking, checked against the administration ACL - reading a technical account is an administration act
      Returns:
      the stored values without any secret and the stored secrets' keys, both empty when nothing is stored or when the storage is not deployed
      Throws:
      IllegalAccessException - if the user may not administer CalDAV servers
      org.exoplatform.commons.exception.ObjectNotFoundException - when no registration carries that id
    • createServer

      public org.exoplatform.caldav.model.CaldavServer createServer(org.exoplatform.caldav.model.CaldavServer server, String username) throws IllegalAccessException
      Declares a new CalDAV server. The provider name is derived from the row id (agenda.caldavCalendar.<id>), then the matching agenda remote provider is upserted under that name — which is all agenda needs: its existing enabled-check and connected-provider binding key on the name.
      Parameters:
      server - registration to create (id and provider name ignored)
      username - user declaring the server
      Returns:
      the created registration
      Throws:
      IllegalAccessException - when the user is not a platform administrator
      IllegalArgumentException - when the registration, its name or its URL is missing
    • updateServer

      public org.exoplatform.caldav.model.CaldavServer updateServer(org.exoplatform.caldav.model.CaldavServer server, String username) throws IllegalAccessException, org.exoplatform.commons.exception.ObjectNotFoundException
      Updates a declared server: name, description, URL, activation, whether the copies pushed to it carry answer links, and the two lists of behaviours this server is excused for. The provider name never changes, and the activation is propagated to the agenda remote provider — the switch users' connector lists actually read.
      Parameters:
      server - registration carrying the id to update and the new values
      username - user updating the server
      Returns:
      the updated registration
      Throws:
      IllegalAccessException - when the user is not a platform administrator
      IllegalArgumentException - when the registration, its name or its URL is missing
      org.exoplatform.commons.exception.ObjectNotFoundException - when no registration carries that id
    • setServerActive

      public org.exoplatform.caldav.model.CaldavServer setServerActive(long serverId, boolean active, String username) throws IllegalAccessException, org.exoplatform.commons.exception.ObjectNotFoundException
      Activates or deactivates a declared server, propagating the switch to the agenda remote provider so the connector appears in — or leaves — every user's connectors list.
      Parameters:
      serverId - technical identifier of the registration
      active - whether users may connect to this server
      username - user flipping the switch
      Returns:
      the updated registration
      Throws:
      IllegalAccessException - when the user is not a platform administrator
      org.exoplatform.commons.exception.ObjectNotFoundException - when no registration carries that id
      IllegalArgumentException - carrying a message code when the row is being ACTIVATED and its stored address is one the platform must not be made to connect to, or caldav.managed.serverInUse when the row is the one managed mode synchronises everybody with
    • deleteServer

      public void deleteServer(long serverId, String username) throws IllegalAccessException, org.exoplatform.commons.exception.ObjectNotFoundException
      Deletes a declared server. Refused — with the number of blocked accounts — while ANY user's connection references the row: deleting under them would silently re-point their stored credentials at whatever the resolution falls back to, which is a different server. The administrator deactivates instead, users disconnect, then the delete goes through. Legacy accounts (no stored reference) never block a delete: they resolve through the seed then the property, exactly as before the registry. Before the row goes, its agenda remote provider is disabled — agenda has no provider-delete API, so disabling is what actually removes the connector from every user's list; the orphaned provider row stays, disabled and invisible.
      Parameters:
      serverId - technical identifier of the registration
      username - user deleting the server
      Throws:
      IllegalAccessException - when the user is not a platform administrator
      org.exoplatform.commons.exception.ObjectNotFoundException - when no registration carries that id
      IllegalStateException - carrying the message code caldav.server.referenced:<count> when connected accounts still reference the row (the REST layer answers 409)
      IllegalArgumentException - carrying caldav.managed.serverInUse when the row is the one managed mode synchronises everybody with (the REST layer answers 400)
    • getUserIdentitiesOfServer

      public List<Long> getUserIdentitiesOfServer(long serverId)
      The users connected to a server, by their identity: what a change of its provider disconnects. The accounts whose stored server id names the row - and, for the row that carries CALDAV_PROVIDER_NAME, also the accounts that store no server id: connected before the server list existed, every use resolves them to that row (resolveServer(Long) with null), so they are its users too.
      Parameters:
      serverId - the server
      Returns:
      the technical identity identifiers of its users
    • countServerReferences

      public long countServerReferences(long serverId)
      Counts the connected accounts referencing a registration: the user contexts holding a CaldavServerId setting whose value is this row's id. The enumeration is by setting NAME (the settings API offers no by-value filter), each candidate's value then compared — the same walk email-connector does to find a connector's users.
      Parameters:
      serverId - technical identifier of the registration
      Returns:
      number of accounts whose stored connection references the row
    • getServerImageInputStream

      public InputStream getServerImageInputStream(long serverId) throws org.exoplatform.commons.exception.ObjectNotFoundException
      The stored image of a registration, as a stream the REST layer serves — email-connector's illustration mechanics.
      Parameters:
      serverId - technical identifier of the registration
      Returns:
      the image stream, or null when the row holds no image
      Throws:
      org.exoplatform.commons.exception.ObjectNotFoundException - when no registration carries that id
    • resolveServer

      public org.exoplatform.caldav.model.CaldavServer resolveServer(Long serverId)
      Resolves the registration a user's account reads and pushes through: the row the account references when it references one and that row still exists, else the seed row, else null. This single resolution rule is shared by the URL resolution below and by the relay's authorization check — the relay refuses any target that is not the row this method answers for the user.
      Parameters:
      serverId - registration the user's account references, or null
      Returns:
      the resolved registration, or null when the registry answers nothing
    • resolveServerUrl

      public String resolveServerUrl(Long serverId)
      Resolves the CalDAV base URL a user's account reads and pushes through: the URL of the resolved registration, else null — the caller then falls back to the legacy configuration property, which is today's behaviour for deployments that never touched the registry.
      Parameters:
      serverId - registration the user's account references, or null
      Returns:
      the resolved base URL, or null when the registry answers nothing
    • canEdit

      public boolean canEdit(String username)
      Whether a user may edit the registry: platform administrators only. This is the same gate email-connector's admin registry uses (EmailConnectorService.canEdit), minus its blank-username-passes quirk — an anonymous caller is refused here.
      Parameters:
      username - user asking to write
      Returns:
      true when the user is a platform administrator