Class CaldavInvitationLandingService
The sweep's own path, never a second one
The eXo event is created by CaldavInboundService.importInto(long, java.lang.String, org.exoplatform.caldav.model.CalendarSync, org.exoplatform.agenda.model.Calendar, java.time.Instant, java.time.Instant) reading
the collection around the event, exactly as the sweep would on its next pass:
the same mapping row (ObjectSync: binding, UID, href, ETag), the same
remote identity agenda records, the same recurrence and override handling.
The read comes first: a mail server that files invitations in the
calendar itself (BlueMind, Google) already holds the object, under whatever
name it chose for it, and the import finds it by the UID inside it. Only an
object the import did not find is written, under a name of this service's
own, created and never overwritten; then read back the same way. What this
service adds is what the sweep cannot know — what the user asked.
What each click does
An invitation accepted or tentatively accepted, or added without an answer,
lands: created, or the copy the user holds rewritten by its organiser's newer
revision. Added without an answer, the copy's line for the user says
NEEDS-ACTION, or keeps the answer the copy they held carried: the PARTSTAT
the sender wrote on that line is the sender's word, and held(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String) reads
the stored line back as the user's answer. A decline creates nothing — a meeting the user turned down and
never added does not belong in their calendar — and sets the answer on a
copy they do hold, found by the same read first (a copy the mail server
filed and the sweep has not mapped yet takes the decline too, or the next
sweep would bring it in accepted), as answering Decline in agenda on a
synced copy does; the copy stays, declined, which is what the server and
every client of it then agree on. A cancellation removes the copy the user holds — the object on the
server, the mapping, the eXo event — as every calendar client does with the
organiser's CANCEL; a cancellation of an event the user never added does
nothing.
Where the answer lives
The answer is written onto the copy here (pushAnswerOnto, offered
every address the copy may name the user by, their mailbox first), and
nowhere else: the import makes the user the creator of the event it brings
in, and this add-on's EventResponseSavedListener, which carries eXo
answers onto copies through CaldavPushService#pushAnswer, declines
every event its user organises — so it never reaches a landed one. The write
is conditioned on the version the server holds now, read on this very
call, not on the one the last sweep recorded: a mail server that filed the
organiser's update itself, or a phone that touched the copy, moved it since,
and the sweep leaves a version unrecorded on purpose when eXo's copy is the
newer one. The answer touches one attendee line and nothing else, which is
what makes that safe. Then it is recorded in agenda like any answer given
in eXo, through AgendaEventAttendeeService.sendEventResponse(long, long, org.exoplatform.agenda.constant.EventAttendeeResponse). A copy
naming the user only by an address outside the offered set is stored as
sent and the answer lives in agenda alone — said at WARN, since nothing
retries it.
What is trusted
The object is the sender's. Only the answering user's own bindings are
consulted, never the mirror ledger; a UID this deployment minted for one of
its own meetings is refused before anything is written, and the UID agenda
mails for one of its own meetings (agenda-event-<id>@<this host>)
lands nothing: the meeting is in agenda already, so adding it hands back
agenda's page as "already in your calendar" when the user may read it and the
message describes it, and an answer or a cancellation carried by such a
message is refused. Only an invitation, a published event
or a cancellation is landed — a REPLY or a COUNTER speaks to an organiser,
not to a calendar. A copy the user
already holds is rewritten or removed only by its own organiser (RFC 5546
section 3.2.2): a message whose ORGANIZER is not the copy's, or whose copy
the user organises themselves, is refused — so an invitee who learnt the
UID of an event the user organises on their own server cannot rewrite or
remove it from a mail. The sender's UID never becomes a path: the object this
service creates is named by a UUID of its own, and found afterwards by the
UID inside it, as the sweep finds everything. The addresses the answer is
written under are the user's own; no attendee line is resolved to anybody.
The link handed back is agenda's own page for the event, built from the
platform's domain and the event's id, never from the object.
Cost and limits
This runs on the request thread, after the REPLY left when there was one: at
most one default-calendar discovery, two reads of the collection's window,
one write of the object, and the answer's read and write. They share one
budget (LANDING_BUDGET_PROPERTY, 20
seconds by default): each request is sent with the smaller of the client's
own timeout and the time left, none is sent once it is spent, and the
landing then fails — told to the user as a calendar that could not be
updated. What a spent budget leaves on the server is what any failed step
already could: an object written whose import did not follow, which the
next sweep brings in. A writer another add-on contributes for the account
(CalendarObjectWriters) is bounded only if it sends through this
add-on's client, or bounds its own calls by
CalDavDeadline.remaining(). A message about one occurrence
of a series (a lone RECURRENCE-ID) is refused: landing it means splicing an
override into a copy and answering an instance, which this path does not do
yet. A newer revision of an event the user holds is written over the
server's copy when the mail's SEQUENCE is strictly higher than the copy's
master's; whether eXo's own copy then follows is the sweep's freshness rule,
as for any change made on the server. A user with no connected account, or
no calendar bound on it, is not this add-on's to land for.
Whether the calendar holds an invitation already
Asked by the mail reader every time it shows an invitation it could add (EXO-90873), so it answers from the mapping: the copy the sweep or a landing mapped under the UID on one of the user's own bindings. No collection is read to find one: a copy the mail server filed and no sweep has mapped yet is not told of — the next sweep maps it, within its period, and a click on "Add to my calendar" meanwhile finds it by the landing's own read first, so nothing is written twice. That one collection query per invitation opened, on the reader's request, is the cost this declines. The mapped copy itself is read, one GET of the object at its known href: the user's answer and the copy's SEQUENCE live on the server only, where a phone or a webmail changed them — the sweep brings an imported event in accepted and never carries a later PARTSTAT back. The same rules as a landing say what is not told: one of this deployment's own meetings, a copy the mirror wrote, a message about one occurrence, an event gone from agenda or cancelled there, a copy the server does not serve, a copy the message's organiser is not the organiser of, and one the user organises. Nothing is written.
-
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final intThe default ofLANDING_BUDGET_PROPERTY: twenty seconds, well above the few round trips a landing makes on a server that answers, and below the client's own 30-second request timeout, so a server that stops answering holds the user's click no longer than one of its requests would.static final StringDeployment property: the seconds one landing's CalDAV calls may take altogether. -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionorg.exoplatform.caldav.model.HeldMailInvitationThe copy of an invitation the user's calendar holds, for the mail reader to say so when it shows the invitation: the mapped copy, read once from the server, with the user's answer on it and its SEQUENCE.booleanholdsCalendarFor(String username) Whether this add-on holds a calendar for the user: a connected account with a calendar bound on it.org.exoplatform.caldav.model.LandedMailInvitationland(org.exoplatform.caldav.model.MailInvitation invitation) Lands the invitation in the user's calendar: creates or updates the event, answers it, or removes it, as the message and the click say.
-
Field Details
-
LANDING_BUDGET_PROPERTY
Deployment property: the seconds one landing's CalDAV calls may take altogether.- See Also:
-
DEFAULT_LANDING_BUDGET
public static final int DEFAULT_LANDING_BUDGETThe default ofLANDING_BUDGET_PROPERTY: twenty seconds, well above the few round trips a landing makes on a server that answers, and below the client's own 30-second request timeout, so a server that stops answering holds the user's click no longer than one of its requests would.- See Also:
-
-
Constructor Details
-
CaldavInvitationLandingService
public CaldavInvitationLandingService()
-
-
Method Details
-
holdsCalendarFor
Whether this add-on holds a calendar for the user: a connected account with a calendar bound on it. Database reads only — this is asked on every read of an invitation.- Parameters:
username- the user's eXo login- Returns:
- true when a landing has somewhere to go
-
land
public org.exoplatform.caldav.model.LandedMailInvitation land(org.exoplatform.caldav.model.MailInvitation invitation) Lands the invitation in the user's calendar: creates or updates the event, answers it, or removes it, as the message and the click say.- Parameters:
invitation- the invitation, the user and what they asked- Returns:
- what was done, or null when the user has no connected account or no calendar bound on it, or when there was nothing to do — a decline, or a cancellation, of an event the user never added
- Throws:
IllegalArgumentException- when the message cannot be landed as it is — unreadable, about another event than the one shown, about one occurrence only, naming no organiser where one is needed, naming a meeting this deployment wrote, or touching a copy of another organiser'sIllegalStateException- when the landing was attempted and failed, its budget spent included
-
held
public org.exoplatform.caldav.model.HeldMailInvitation held(String username, String attendeeAddress, String uid, String recurrenceId, String organizer) The copy of an invitation the user's calendar holds, for the mail reader to say so when it shows the invitation: the mapped copy, read once from the server, with the user's answer on it and its SEQUENCE. Reads only. What it answers never licenses a change:land(org.exoplatform.caldav.model.MailInvitation)changes or removes a copy the user holds only for a mail the reader established as its organiser's (MailInvitation.fromOrganizer()), whatever this answered.- Parameters:
username- the user's eXo login, the reader's authenticated userattendeeAddress- the user's mailbox address, possibly nulluid- the invitation's UIDrecurrenceId- the occurrence the message is about, null for a series or a single eventorganizer- the message's organiser, null for a published event naming none- Returns:
- the copy held, or null when the user has no calendar here, the UID is not mapped on their bindings, or the copy is not one to tell of
- Throws:
IllegalStateException- when the mapped copy could not be read
-