Package org.exoplatform.caldav.client
Class HttpCalDavClient
java.lang.Object
org.exoplatform.caldav.client.HttpCalDavClient
- All Implemented Interfaces:
CalDavClient
CalDAV over the JDK's own HTTP client, modelled line for line on
email-connector's
HttpCardDavClient — the hand-written client that
already works against the same BlueMind that defeated the browser — with
the disciplines the relay established carried over:
- registry-only targets — endpoints are minted from the server
registry alone (
endpoint(Long, String)), hrefs resolve against that endpoint's authority, and a foreign-host href is refused; - no redirects — a declared server must not be able to bounce this client's credentialed requests to another host, so the transport never follows a 3xx (which also rules the well-known discovery flow out by design: discovery starts at the registered base path instead);
- explicit timeouts and a bounded body read, both deployment-tunable;
- no auth material anywhere near a log or an exception — every message names the method and the URI, never a header.
-
Constructor Summary
ConstructorsConstructorDescriptionHttpCalDavClient(CaldavServerService caldavServerService) The client Spring builds. -
Method Summary
Modifier and TypeMethodDescriptioncalendarQuery(CalDavEndpoint endpoint, String collectionHref, Instant start, Instant end, String username, String password) Runs a calendar-query REPORT for the VEVENTs of a time window — the tier-3 floor for servers whose listings carry no usable ETags, and the ranged read the events endpoint will serve from.intdeleteObject(CalDavEndpoint endpoint, String href, String ifMatch, String username, String password) Deletes one object, conditionally when a version is given.discoverCalendarHome(CalDavEndpoint endpoint, String username, String password) Walks discovery from the endpoint base: asks who the authenticated user is (current-user-principal), then where that principal's calendars live (calendar-home-set).Mints the one kind of target this client will talk to: the account base of a declared server, resolved exclusively from the server registry — the row the account references, else the seed row, else the legacy deployment property — with the{username}placeholder replaced by the account's DAV username, percent-encoded for its path position.fetchObject(CalDavEndpoint endpoint, String href, String username, String password) Reads one object's current data and version in a single GET — what a conflict re-read asks, so the three-way runs against the object as the server holds it NOW rather than as the last sync remembers it.getCtag(CalDavEndpoint endpoint, String href, String username, String password) Reads the collection's current ctag, the cheap tier-2 question asked first on every run.listCalendars(CalDavEndpoint endpoint, String homeHref, String username, String password) Lists the calendar collections of a home with every property the sync engine binds on: display name, resource type, ctag, sync token, supported reports, the user's privileges, and the calendar colour.listResourceEtags(CalDavEndpoint endpoint, String collectionHref, String username, String password) Lists every object in the collection with its version — thelistResourceEtagspattern the plan reuses verbatim from email-connector, and what tells the engine who is new, who changed and who is gone.mkCalendar(CalDavEndpoint endpoint, String href, String displayName, String color, String username, String password) Asks the server to create a calendar collection, display name and optional colour set atomically.multiget(CalDavEndpoint endpoint, String collectionHref, List<String> hrefs, String username, String password) Fetches the calendar data of the given objects in one calendar-multiget REPORT — why a changed collection costs one round trip per batch rather than one per event.probeCapabilities(CalDavEndpoint endpoint, String collectionHref, String username, String password) Probes which report tiers a collection's server can serve, in one Depth:0 PROPFIND.Creates one object — always underIf-None-Match: *, with no unconditional variant on purpose: the server itself then guarantees the PUT can only CREATE, and an object already at that path answers 412 instead of being overwritten, whatever race led there.readCalendar(CalDavEndpoint endpoint, String href, String username, String password) Reads one collection's properties at Depth:0 — what a bound pair re-reads on each run without paying a full home listing.syncCollection(CalDavEndpoint endpoint, String collectionHref, String syncToken, String username, String password) Runs an RFC 6578 sync-collection REPORT with a stored token — tier 1, one round trip for everything changed or deleted since that token.updateObject(CalDavEndpoint endpoint, String href, String icsData, String ifMatch, String username, String password) Replaces one existing object — guarded the opposite way fromCalDavClient.putObject(org.exoplatform.caldav.client.CalDavEndpoint, java.lang.String, java.lang.String, java.lang.String, java.lang.String):If-Matchwith the version the caller just read means the server only accepts the write when nobody changed the object since that read.
-
Constructor Details
-
HttpCalDavClient
The client Spring builds. Redirects are NEVER followed: the target host comes from the registry, and a compromised or misconfigured registered server must not be able to bounce credentialed requests anywhere else.- Parameters:
caldavServerService- the registry every endpoint is resolved from
-
-
Method Details
-
endpoint
Description copied from interface:CalDavClientMints the one kind of target this client will talk to: the account base of a declared server, resolved exclusively from the server registry — the row the account references, else the seed row, else the legacy deployment property — with the{username}placeholder replaced by the account's DAV username, percent-encoded for its path position.- Specified by:
endpointin interfaceCalDavClient- Parameters:
serverId- registry row the user's account references, or null for the seed-row / legacy-property resolutiondavUsername- the account's username on the CalDAV server- Returns:
- the endpoint every other method addresses
-
discoverCalendarHome
Description copied from interface:CalDavClientWalks discovery from the endpoint base: asks who the authenticated user is (current-user-principal), then where that principal's calendars live (calendar-home-set). Started at the registered base path directly — never at/.well-known/caldav, whose contract is a redirect and this client follows none.- Specified by:
discoverCalendarHomein interfaceCalDavClient- Parameters:
endpoint- the declared server to discover onusername- the account to authenticate aspassword- that account's password- Returns:
- the calendar home's server-absolute raw path
-
listCalendars
public List<CalendarCollection> listCalendars(CalDavEndpoint endpoint, String homeHref, String username, String password) Description copied from interface:CalDavClientLists the calendar collections of a home with every property the sync engine binds on: display name, resource type, ctag, sync token, supported reports, the user's privileges, and the calendar colour.- Specified by:
listCalendarsin interfaceCalDavClient- Parameters:
endpoint- the declared serverhomeHref- the calendar home's server-absolute pathusername- the account to authenticate aspassword- that account's password- Returns:
- the calendar collections, never null — non-calendar members of the home (the home itself included) are filtered out here by resource type, never guessed from the path
-
readCalendar
public CalendarCollection readCalendar(CalDavEndpoint endpoint, String href, String username, String password) Description copied from interface:CalDavClientReads one collection's properties at Depth:0 — what a bound pair re-reads on each run without paying a full home listing.- Specified by:
readCalendarin interfaceCalDavClient- Parameters:
endpoint- the declared serverhref- the collection's server-absolute pathusername- the account to authenticate aspassword- that account's password- Returns:
- the collection, or null when the resource exists but is not a calendar collection
-
getCtag
Description copied from interface:CalDavClientReads the collection's current ctag, the cheap tier-2 question asked first on every run.- Specified by:
getCtagin interfaceCalDavClient- Parameters:
endpoint- the declared serverhref- the collection's server-absolute pathusername- the account to authenticate aspassword- that account's password- Returns:
- the current ctag, or null when the server does not implement it
-
listResourceEtags
public Map<String,String> listResourceEtags(CalDavEndpoint endpoint, String collectionHref, String username, String password) Description copied from interface:CalDavClientLists every object in the collection with its version — thelistResourceEtagspattern the plan reuses verbatim from email-connector, and what tells the engine who is new, who changed and who is gone. Also the mirror-verification listing of the metadata design.- Specified by:
listResourceEtagsin interfaceCalDavClient- Parameters:
endpoint- the declared servercollectionHref- the collection's server-absolute pathusername- the account to authenticate aspassword- that account's password- Returns:
- object path to object version, insertion-ordered, never null — the collection itself and any member without an ETag are skipped by that one rule, never by guessing from the path
-
calendarQuery
public List<CalendarObject> calendarQuery(CalDavEndpoint endpoint, String collectionHref, Instant start, Instant end, String username, String password) Description copied from interface:CalDavClientRuns a calendar-query REPORT for the VEVENTs of a time window — the tier-3 floor for servers whose listings carry no usable ETags, and the ranged read the events endpoint will serve from.- Specified by:
calendarQueryin interfaceCalDavClient- Parameters:
endpoint- the declared servercollectionHref- the collection's server-absolute pathstart- window start, inclusive, or null for an unbounded startend- window end, exclusive, or null for an unbounded endusername- the account to authenticate aspassword- that account's password- Returns:
- the matching objects with their calendar data, never null
-
multiget
public List<CalendarObject> multiget(CalDavEndpoint endpoint, String collectionHref, List<String> hrefs, String username, String password) Description copied from interface:CalDavClientFetches the calendar data of the given objects in one calendar-multiget REPORT — why a changed collection costs one round trip per batch rather than one per event.- Specified by:
multigetin interfaceCalDavClient- Parameters:
endpoint- the declared servercollectionHref- the collection's server-absolute pathhrefs- the object paths to fetchusername- the account to authenticate aspassword- that account's password- Returns:
- the objects the server returned, never null; an href it did not return is simply absent, which the caller must tolerate
-
syncCollection
public SyncCollectionResult syncCollection(CalDavEndpoint endpoint, String collectionHref, String syncToken, String username, String password) Description copied from interface:CalDavClientRuns an RFC 6578 sync-collection REPORT with a stored token — tier 1, one round trip for everything changed or deleted since that token.- Specified by:
syncCollectionin interfaceCalDavClient- Parameters:
endpoint- the declared servercollectionHref- the collection's server-absolute pathsyncToken- the stored token, or null/blank for the initial sync that establishes oneusername- the account to authenticate aspassword- that account's password- Returns:
- the changes and the token to store — or the invalid-token result when the server rejected the presented token, which the caller answers by falling through to the listing tier for this run
-
probeCapabilities
public ServerCapabilities probeCapabilities(CalDavEndpoint endpoint, String collectionHref, String username, String password) Description copied from interface:CalDavClientProbes which report tiers a collection's server can serve, in one Depth:0 PROPFIND. Stateless: the answer is bind-time information whose caching belongs to the caller's pair state, not to this client.- Specified by:
probeCapabilitiesin interfaceCalDavClient- Parameters:
endpoint- the declared servercollectionHref- the collection's server-absolute pathusername- the account to authenticate aspassword- that account's password- Returns:
- the capability answer, never null
-
fetchObject
public CalendarObject fetchObject(CalDavEndpoint endpoint, String href, String username, String password) Description copied from interface:CalDavClientReads one object's current data and version in a single GET — what a conflict re-read asks, so the three-way runs against the object as the server holds it NOW rather than as the last sync remembers it.- Specified by:
fetchObjectin interfaceCalDavClient- Parameters:
endpoint- the declared serverhref- the object's server-absolute pathusername- the account to authenticate aspassword- that account's password- Returns:
- the object as the server returned it, or null when the server
says there is no such object — including BlueMind's way of
saying it, a 500 for an .ics that is simply not there;
reporting "absent" on a server error is safe rather than
optimistic because every creating write keeps its
If-None-Match: *, so the worst case is a refused write, never a silently overwritten one
-
putObject
public PutResult putObject(CalDavEndpoint endpoint, String href, String icsData, String username, String password) Description copied from interface:CalDavClientCreates one object — always underIf-None-Match: *, with no unconditional variant on purpose: the server itself then guarantees the PUT can only CREATE, and an object already at that path answers 412 instead of being overwritten, whatever race led there.- Specified by:
putObjectin interfaceCalDavClient- Parameters:
endpoint- the declared serverhref- the object's server-absolute path to create aticsData- the iCalendar text to storeusername- the account to authenticate aspassword- that account's password- Returns:
- the status and, when the server sent them, the stored version and the object's real location; a 412 is answered, not thrown
-
updateObject
public PutResult updateObject(CalDavEndpoint endpoint, String href, String icsData, String ifMatch, String username, String password) Description copied from interface:CalDavClientReplaces one existing object — guarded the opposite way fromCalDavClient.putObject(org.exoplatform.caldav.client.CalDavEndpoint, java.lang.String, java.lang.String, java.lang.String, java.lang.String):If-Matchwith the version the caller just read means the server only accepts the write when nobody changed the object since that read. A blank precondition is refused here rather than sent as an unconditional overwrite: "every conditional write stays conditional" is the connector doctrine this client enforces structurally.- Specified by:
updateObjectin interfaceCalDavClient- Parameters:
endpoint- the declared serverhref- the object's server-absolute pathicsData- the iCalendar text to storeifMatch- theIf-Matchvalue — the ETag the caller's own read just answered, exactly as sent, quotes and allusername- the account to authenticate aspassword- that account's password- Returns:
- the status and the stored version when the server sent one; a 412 is answered, not thrown
-
deleteObject
public int deleteObject(CalDavEndpoint endpoint, String href, String ifMatch, String username, String password) Description copied from interface:CalDavClientDeletes one object, conditionally when a version is given.- Specified by:
deleteObjectin interfaceCalDavClient- Parameters:
endpoint- the declared serverhref- the object's server-absolute pathifMatch- theIf-Matchvalue to guard the delete with, or null to delete unconditionally — tolerated here (unlikeCalDavClient.updateObject(org.exoplatform.caldav.client.CalDavEndpoint, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String)) because a delete of an already-diverged object is recoverable where an overwrite is not, and some flows legitimately hold no versionusername- the account to authenticate aspassword- that account's password- Returns:
- the status the server answered: 200/204 deleted, 404/410 already gone — a fact, not a fault — and 412 a refused precondition
-
mkCalendar
public MkCalendarResult mkCalendar(CalDavEndpoint endpoint, String href, String displayName, String color, String username, String password) Description copied from interface:CalDavClientAsks the server to create a calendar collection, display name and optional colour set atomically. The request always declaressupported-calendar-component-setwithVEVENT: BlueMind derives the created collection's kind from it, and without it answers 201 while creating nothing (proven live 2026-08-20). The answer is a claim, never a fact — seeMkCalendarResultfor why the one statement of success a caller may trust is the collection's presence in its own fresh listing.- Specified by:
mkCalendarin interfaceCalDavClient- Parameters:
endpoint- the declared serverhref- the collection's server-absolute path to create atdisplayName- the display name to create withcolor- the Apple calendar-color to set, or null to set noneusername- the account to authenticate aspassword- that account's password- Returns:
- the raw outcome, refusals included — a server declining MKCALENDAR gives an answer the caller maps to its degraded states, not an error
-